From ca42f364625085463f02ac27cbd7d3c86dc88a7c Mon Sep 17 00:00:00 2001 From: Fumiya Tanaka Date: Thu, 2 Feb 2023 00:21:12 +0900 Subject: [PATCH] Update csv builder with swift-syntax (#38) * Remove example of CsvComposition from public API * Add `CsvCompositionParser` * Add comment * Add txt file --- .../Csv2ImageApp.xcodeproj/project.pbxproj | 7 + .../xcshareddata/swiftpm/Package.resolved | 9 ++ .../project.pbxproj | 50 ++++--- .../xcshareddata/swiftpm/Package.resolved | 24 +++- .../CsvBuilderExample/ContentView.swift | 10 +- .../CsvBuilderExampleApp.swift | 11 +- .../CsvCompositionExample.swift | 11 ++ .../CsvCompositionExample.txt | 11 ++ .../CsvBuilderExample/SecondContentView.swift | 38 ++++++ Package.resolved | 9 ++ Package.swift | 11 +- Sources/Csv2Img/CsvColumn.swift | 4 + Sources/CsvBuilder/CsvCompositionParser.swift | 124 ++++++++++++++++++ Sources/CsvBuilder/Example.swift | 19 +-- Sources/CsvBuilder/NewCsvComposition.swift | 24 ++++ 15 files changed, 317 insertions(+), 45 deletions(-) create mode 100644 Examples/CsvBuilderExample/CsvBuilderExample/CsvCompositionExample.swift create mode 100644 Examples/CsvBuilderExample/CsvBuilderExample/CsvCompositionExample.txt create mode 100644 Examples/CsvBuilderExample/CsvBuilderExample/SecondContentView.swift create mode 100644 Sources/CsvBuilder/CsvCompositionParser.swift create mode 100644 Sources/CsvBuilder/NewCsvComposition.swift diff --git a/Csv2ImageApp/Csv2ImageApp.xcodeproj/project.pbxproj b/Csv2ImageApp/Csv2ImageApp.xcodeproj/project.pbxproj index a49e59d..d694c46 100644 --- a/Csv2ImageApp/Csv2ImageApp.xcodeproj/project.pbxproj +++ b/Csv2ImageApp/Csv2ImageApp.xcodeproj/project.pbxproj @@ -14,6 +14,7 @@ D359BADC28621BFE006DFDCE /* Type.swift in Sources */ = {isa = PBXBuildFile; fileRef = D359BADB28621BFE006DFDCE /* Type.swift */; }; D35DB0D4285FA754009C252D /* CsvViewer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D35DB0D3285FA754009C252D /* CsvViewer.swift */; }; D35DB0D6285FADA5009C252D /* HistoryModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D35DB0D5285FADA5009C252D /* HistoryModel.swift */; }; + D3776F22298A589200A09A90 /* CsvBuilder in Frameworks */ = {isa = PBXBuildFile; productRef = D3776F21298A589200A09A90 /* CsvBuilder */; }; D3A4EB18284F11A3002E3499 /* Csv2ImageAppApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3A4EB17284F11A3002E3499 /* Csv2ImageAppApp.swift */; }; D3A4EB1C284F11A4002E3499 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D3A4EB1B284F11A4002E3499 /* Assets.xcassets */; }; D3A4EB1F284F11A4002E3499 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D3A4EB1E284F11A4002E3499 /* Preview Assets.xcassets */; }; @@ -108,6 +109,7 @@ buildActionMask = 2147483647; files = ( D3095CC6286394720086EEEE /* Csv2Img in Frameworks */, + D3776F22298A589200A09A90 /* CsvBuilder in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -310,6 +312,7 @@ name = Csv2ImageApp; packageProductDependencies = ( D3095CC5286394720086EEEE /* Csv2Img */, + D3776F21298A589200A09A90 /* CsvBuilder */, ); productName = Csv2ImageApp; productReference = D3A4EB14284F11A3002E3499 /* Csv2ImageApp.app */; @@ -818,6 +821,10 @@ isa = XCSwiftPackageProductDependency; productName = Csv2Img; }; + D3776F21298A589200A09A90 /* CsvBuilder */ = { + isa = XCSwiftPackageProductDependency; + productName = CsvBuilder; + }; /* End XCSwiftPackageProductDependency section */ /* Begin XCVersionGroup section */ diff --git a/Csv2ImageApp/Csv2ImageApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Csv2ImageApp/Csv2ImageApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 9976ada..5ca3e0f 100644 --- a/Csv2ImageApp/Csv2ImageApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Csv2ImageApp/Csv2ImageApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -18,6 +18,15 @@ "revision": "3303b164430d9a7055ba484c8ead67a52f7b74f6", "version": "1.0.0" } + }, + { + "package": "SwiftSyntax", + "repositoryURL": "https://github.com/apple/swift-syntax", + "state": { + "branch": "main", + "revision": "dfb8deb846e16d98e1d5b2261ed608121e096037", + "version": null + } } ] }, diff --git a/Examples/CsvBuilderExample/CsvBuilderExample.xcodeproj/project.pbxproj b/Examples/CsvBuilderExample/CsvBuilderExample.xcodeproj/project.pbxproj index e5b57bc..1854783 100644 --- a/Examples/CsvBuilderExample/CsvBuilderExample.xcodeproj/project.pbxproj +++ b/Examples/CsvBuilderExample/CsvBuilderExample.xcodeproj/project.pbxproj @@ -11,8 +11,12 @@ D308AB9228B67E1900ECA831 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D308AB9128B67E1900ECA831 /* ContentView.swift */; }; D308AB9428B67E1A00ECA831 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D308AB9328B67E1A00ECA831 /* Assets.xcassets */; }; D308AB9828B67E1A00ECA831 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D308AB9728B67E1A00ECA831 /* Preview Assets.xcassets */; }; - D34D4D3928B67F3200A018FB /* Csv2Img in Frameworks */ = {isa = PBXBuildFile; productRef = D34D4D3828B67F3200A018FB /* Csv2Img */; }; - D34D4D3B28B67F3200A018FB /* CsvBuilder in Frameworks */ = {isa = PBXBuildFile; productRef = D34D4D3A28B67F3200A018FB /* CsvBuilder */; }; + D3776F25298A58D500A09A90 /* Csv2Img in Frameworks */ = {isa = PBXBuildFile; productRef = D3776F24298A58D500A09A90 /* Csv2Img */; }; + D3776F27298A58D500A09A90 /* CsvBuilder in Frameworks */ = {isa = PBXBuildFile; productRef = D3776F26298A58D500A09A90 /* CsvBuilder */; }; + D3776F28298A5A1900A09A90 /* CsvCompositionExample.swift in Resources */ = {isa = PBXBuildFile; fileRef = D3C6FFB7298A543A00CAF771 /* CsvCompositionExample.swift */; }; + D3776F2B298A5A5E00A09A90 /* CsvCompositionExample.txt in Resources */ = {isa = PBXBuildFile; fileRef = D3776F29298A5A3E00A09A90 /* CsvCompositionExample.txt */; }; + D3776F2D298A7A7400A09A90 /* SecondContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3776F2C298A7A7400A09A90 /* SecondContentView.swift */; }; + D3C6FFB8298A543A00CAF771 /* CsvCompositionExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3C6FFB7298A543A00CAF771 /* CsvCompositionExample.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -22,7 +26,10 @@ D308AB9328B67E1A00ECA831 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; D308AB9528B67E1A00ECA831 /* CsvBuilderExample.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = CsvBuilderExample.entitlements; sourceTree = ""; }; D308AB9728B67E1A00ECA831 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; - D308AB9F28B67E3700ECA831 /* Csv2Img */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Csv2Img; path = ../..; sourceTree = ""; }; + D3776F23298A58C800A09A90 /* Csv2Img */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Csv2Img; path = ../..; sourceTree = ""; }; + D3776F29298A5A3E00A09A90 /* CsvCompositionExample.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = CsvCompositionExample.txt; sourceTree = ""; }; + D3776F2C298A7A7400A09A90 /* SecondContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecondContentView.swift; sourceTree = ""; }; + D3C6FFB7298A543A00CAF771 /* CsvCompositionExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CsvCompositionExample.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -30,8 +37,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - D34D4D3928B67F3200A018FB /* Csv2Img in Frameworks */, - D34D4D3B28B67F3200A018FB /* CsvBuilder in Frameworks */, + D3776F25298A58D500A09A90 /* Csv2Img in Frameworks */, + D3776F27298A58D500A09A90 /* CsvBuilder in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -41,7 +48,7 @@ D308AB8328B67E1900ECA831 = { isa = PBXGroup; children = ( - D308AB9E28B67E3700ECA831 /* Packages */, + D3C6FFBC298A580E00CAF771 /* Packages */, D308AB8E28B67E1900ECA831 /* CsvBuilderExample */, D308AB8D28B67E1900ECA831 /* Products */, D34D4D3728B67F3200A018FB /* Frameworks */, @@ -59,10 +66,13 @@ D308AB8E28B67E1900ECA831 /* CsvBuilderExample */ = { isa = PBXGroup; children = ( - D308AB8F28B67E1900ECA831 /* CsvBuilderExampleApp.swift */, + D308AB9528B67E1A00ECA831 /* CsvBuilderExample.entitlements */, D308AB9128B67E1900ECA831 /* ContentView.swift */, + D308AB8F28B67E1900ECA831 /* CsvBuilderExampleApp.swift */, + D3C6FFB7298A543A00CAF771 /* CsvCompositionExample.swift */, + D3776F2C298A7A7400A09A90 /* SecondContentView.swift */, + D3776F29298A5A3E00A09A90 /* CsvCompositionExample.txt */, D308AB9328B67E1A00ECA831 /* Assets.xcassets */, - D308AB9528B67E1A00ECA831 /* CsvBuilderExample.entitlements */, D308AB9628B67E1A00ECA831 /* Preview Content */, ); path = CsvBuilderExample; @@ -76,19 +86,19 @@ path = "Preview Content"; sourceTree = ""; }; - D308AB9E28B67E3700ECA831 /* Packages */ = { + D34D4D3728B67F3200A018FB /* Frameworks */ = { isa = PBXGroup; children = ( - D308AB9F28B67E3700ECA831 /* Csv2Img */, ); - name = Packages; + name = Frameworks; sourceTree = ""; }; - D34D4D3728B67F3200A018FB /* Frameworks */ = { + D3C6FFBC298A580E00CAF771 /* Packages */ = { isa = PBXGroup; children = ( + D3776F23298A58C800A09A90 /* Csv2Img */, ); - name = Frameworks; + name = Packages; sourceTree = ""; }; /* End PBXGroup section */ @@ -108,8 +118,8 @@ ); name = CsvBuilderExample; packageProductDependencies = ( - D34D4D3828B67F3200A018FB /* Csv2Img */, - D34D4D3A28B67F3200A018FB /* CsvBuilder */, + D3776F24298A58D500A09A90 /* Csv2Img */, + D3776F26298A58D500A09A90 /* CsvBuilder */, ); productName = CsvBuilderExample; productReference = D308AB8C28B67E1900ECA831 /* CsvBuilderExample.app */; @@ -139,6 +149,8 @@ Base, ); mainGroup = D308AB8328B67E1900ECA831; + packageReferences = ( + ); productRefGroup = D308AB8D28B67E1900ECA831 /* Products */; projectDirPath = ""; projectRoot = ""; @@ -153,6 +165,8 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + D3776F2B298A5A5E00A09A90 /* CsvCompositionExample.txt in Resources */, + D3776F28298A5A1900A09A90 /* CsvCompositionExample.swift in Resources */, D308AB9828B67E1A00ECA831 /* Preview Assets.xcassets in Resources */, D308AB9428B67E1A00ECA831 /* Assets.xcassets in Resources */, ); @@ -165,6 +179,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + D3776F2D298A7A7400A09A90 /* SecondContentView.swift in Sources */, + D3C6FFB8298A543A00CAF771 /* CsvCompositionExample.swift in Sources */, D308AB9228B67E1900ECA831 /* ContentView.swift in Sources */, D308AB9028B67E1900ECA831 /* CsvBuilderExampleApp.swift in Sources */, ); @@ -382,11 +398,11 @@ /* End XCConfigurationList section */ /* Begin XCSwiftPackageProductDependency section */ - D34D4D3828B67F3200A018FB /* Csv2Img */ = { + D3776F24298A58D500A09A90 /* Csv2Img */ = { isa = XCSwiftPackageProductDependency; productName = Csv2Img; }; - D34D4D3A28B67F3200A018FB /* CsvBuilder */ = { + D3776F26298A58D500A09A90 /* CsvBuilder */ = { isa = XCSwiftPackageProductDependency; productName = CsvBuilder; }; diff --git a/Examples/CsvBuilderExample/CsvBuilderExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Examples/CsvBuilderExample/CsvBuilderExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index ff3d932..8f7edc5 100644 --- a/Examples/CsvBuilderExample/CsvBuilderExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Examples/CsvBuilderExample/CsvBuilderExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -6,8 +6,8 @@ "repositoryURL": "https://github.com/apple/swift-argument-parser", "state": { "branch": null, - "revision": "df9ee6676cd5b3bf5b330ec7568a5644f547201b", - "version": "1.1.3" + "revision": "9f39744e025c7d377987f30b03770805dcb0bcd1", + "version": "1.1.4" } }, { @@ -15,9 +15,27 @@ "repositoryURL": "https://github.com/apple/swift-docc-plugin", "state": { "branch": null, - "revision": "3303b164430d9a7055ba484c8ead67a52f7b74f6", + "revision": "10bc670db657d11bdd561e07de30a9041311b2b1", + "version": "1.1.0" + } + }, + { + "package": "SymbolKit", + "repositoryURL": "https://github.com/apple/swift-docc-symbolkit", + "state": { + "branch": null, + "revision": "b45d1f2ed151d057b54504d653e0da5552844e34", "version": "1.0.0" } + }, + { + "package": "SwiftSyntax", + "repositoryURL": "https://github.com/apple/swift-syntax", + "state": { + "branch": "main", + "revision": "dfb8deb846e16d98e1d5b2261ed608121e096037", + "version": null + } } ] }, diff --git a/Examples/CsvBuilderExample/CsvBuilderExample/ContentView.swift b/Examples/CsvBuilderExample/CsvBuilderExample/ContentView.swift index ef1e0c8..687f5ef 100644 --- a/Examples/CsvBuilderExample/CsvBuilderExample/ContentView.swift +++ b/Examples/CsvBuilderExample/CsvBuilderExample/ContentView.swift @@ -1,16 +1,9 @@ -// -// ContentView.swift -// CsvBuilderExample -// -// Created by Fumiya Tanaka on 2022/08/25. -// - import SwiftUI import CsvBuilder struct ContentView: View { - @State private var composition: ExampleComposition = .init() + @State private var composition: CsvCompositionExample = .init() @State private var image: CGImage? var body: some View { @@ -27,6 +20,7 @@ struct ContentView: View { } .padding() .task { + composition = .init() composition.ages.append(contentsOf: ["98", "99", "100"]) composition.names.append(contentsOf: ["Yamada", "Tanaka", "Sato"]) let csv = try! composition.build() diff --git a/Examples/CsvBuilderExample/CsvBuilderExample/CsvBuilderExampleApp.swift b/Examples/CsvBuilderExample/CsvBuilderExample/CsvBuilderExampleApp.swift index cf20df3..08d4515 100644 --- a/Examples/CsvBuilderExample/CsvBuilderExample/CsvBuilderExampleApp.swift +++ b/Examples/CsvBuilderExample/CsvBuilderExample/CsvBuilderExampleApp.swift @@ -11,7 +11,16 @@ import SwiftUI struct CsvBuilderExampleApp: App { var body: some Scene { WindowGroup { - ContentView() + TabView { + ContentView() + .tabItem { + Text("Example 1") + } + SecondContentView() + .tabItem { + Text("Example 2") + } + } } } } diff --git a/Examples/CsvBuilderExample/CsvBuilderExample/CsvCompositionExample.swift b/Examples/CsvBuilderExample/CsvBuilderExample/CsvCompositionExample.swift new file mode 100644 index 0000000..2c5815f --- /dev/null +++ b/Examples/CsvBuilderExample/CsvBuilderExample/CsvCompositionExample.swift @@ -0,0 +1,11 @@ +import CsvBuilder + +struct CsvCompositionExample: CsvComposition { + @CsvRows(column: "age") + var ages: [String] + + @CsvRows(column: "name") + var names: [String] + + init() { } +} diff --git a/Examples/CsvBuilderExample/CsvBuilderExample/CsvCompositionExample.txt b/Examples/CsvBuilderExample/CsvBuilderExample/CsvCompositionExample.txt new file mode 100644 index 0000000..2c5815f --- /dev/null +++ b/Examples/CsvBuilderExample/CsvBuilderExample/CsvCompositionExample.txt @@ -0,0 +1,11 @@ +import CsvBuilder + +struct CsvCompositionExample: CsvComposition { + @CsvRows(column: "age") + var ages: [String] + + @CsvRows(column: "name") + var names: [String] + + init() { } +} diff --git a/Examples/CsvBuilderExample/CsvBuilderExample/SecondContentView.swift b/Examples/CsvBuilderExample/CsvBuilderExample/SecondContentView.swift new file mode 100644 index 0000000..db60c18 --- /dev/null +++ b/Examples/CsvBuilderExample/CsvBuilderExample/SecondContentView.swift @@ -0,0 +1,38 @@ +import SwiftUI +import Csv2Img +import CsvBuilder + + +struct SecondContentView: View { + @State private var composition: CsvCompositionExample = .init() + @State private var image: CGImage? + + var body: some View { + VStack { + Image(systemName: "globe") + .imageScale(.large) + .foregroundColor(.accentColor) + Text("Hello, world!") + if let image = image { + Image(nsImage: NSImage(cgImage: image, size: CGSize(width: image.width, height: image.height))) + .resizable() + .aspectRatio(contentMode: .fit) + } + } + .padding() + .task { + let yamada = Csv.Row(index: 0, values: ["98", "Yamada"]) + let tanaka = Csv.Row(index: 0, values: ["99", "Tanaka"]) + let sato = Csv.Row(index: 0, values: ["100", "Sato"]) + let csv = try! CsvCompositionParser.parse(type: CsvCompositionExample.self, rows: [yamada, tanaka, sato,]) + let data = try! await csv.generate(fontSize: 20, exportType: .png) + self.image = data.base as! CGImage + } + } +} + +struct SecondContentView_Previews: PreviewProvider { + static var previews: some View { + SecondContentView() + } +} diff --git a/Package.resolved b/Package.resolved index ed9e418..eb2448d 100644 --- a/Package.resolved +++ b/Package.resolved @@ -18,6 +18,15 @@ "revision": "3303b164430d9a7055ba484c8ead67a52f7b74f6", "version": "1.0.0" } + }, + { + "package": "SwiftSyntax", + "repositoryURL": "https://github.com/apple/swift-syntax", + "state": { + "branch": "main", + "revision": "dfb8deb846e16d98e1d5b2261ed608121e096037", + "version": null + } } ] }, diff --git a/Package.swift b/Package.swift index 8c7ec92..fee5d3d 100644 --- a/Package.swift +++ b/Package.swift @@ -10,6 +10,10 @@ let argumentParser: PackageDescription.Package.Dependency = .package( let docc: PackageDescription.Package.Dependency = .package( url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0" ) +let swiftSyntax = PackageDescription.Package.Dependency.package( + url: "https://github.com/apple/swift-syntax", + branch: "main" +) let package = Package( name: "Csv2Img", @@ -33,7 +37,8 @@ let package = Package( // Dependencies declare other packages that this package depends on. // .package(url: /* package url */, from: "1.0.0"), argumentParser, - docc + docc, + swiftSyntax, ], targets: [ // Targets are the basic building blocks of a package. A target can define a module or a test suite. @@ -48,7 +53,9 @@ let package = Package( .target( name: "CsvBuilder", dependencies: [ - "Csv2Img" + "Csv2Img", + .product(name: "SwiftSyntax", package: "swift-syntax"), + .product(name: "SwiftParser", package: "swift-syntax"), ] ), .testTarget( diff --git a/Sources/Csv2Img/CsvColumn.swift b/Sources/Csv2Img/CsvColumn.swift index a8b5ff0..41f941b 100644 --- a/Sources/Csv2Img/CsvColumn.swift +++ b/Sources/Csv2Img/CsvColumn.swift @@ -69,6 +69,10 @@ extension Csv.Column { } return styles } + + public static func random() -> Style { + random(count: 1).first! + } } } diff --git a/Sources/CsvBuilder/CsvCompositionParser.swift b/Sources/CsvBuilder/CsvCompositionParser.swift new file mode 100644 index 0000000..e1cc35d --- /dev/null +++ b/Sources/CsvBuilder/CsvCompositionParser.swift @@ -0,0 +1,124 @@ +import Foundation +import SwiftSyntax +import SwiftParser +import Csv2Img + +public struct CsvCompositionParser { + + public enum Error: LocalizedError { + case fileNotFound(type: String) + case failedToDecodeWithUtf8 + } + + public static func parse( + type: Composition.Type, + rows: [Csv.Row] = [] + ) throws -> Csv { + let compositionType = String(describing: type) + guard let file = Bundle.main.url(forResource: compositionType, withExtension: "txt") else { + throw Error.fileNotFound(type: compositionType) + } + let content = try Data(contentsOf: file) + guard let source = String(data: content, encoding: .utf8) else { + throw Error.failedToDecodeWithUtf8 + } + let syntax: SourceFileSyntax = Parser.parse(source: source) + return parseIntoCsv(type: type, source: syntax, rows: rows) + } + + static func parseIntoCsv( + type: C.Type, + source: SourceFileSyntax, + rows: [Csv.Row] = [] + ) -> Csv { + var allColumns: [Csv.Column] = [] + for statement in source.statements { + // Find struct which conforms to CsvComposition + switch statement.item { + case .decl(let decl): + guard let decl = decl.as(StructDeclSyntax.self) else { + break + } + if !validateInheritedType(decl: decl) { + break + } + let members = decl.members.members + let columns = decl.findColumns(type: C.self, members: members) + .map { Csv.Column.init(name: $0, style: .random()) } + allColumns.append(contentsOf: columns) + default: + break + } + } + return Csv( + separator: ",", + columns: allColumns, + rows: rows, + exportType: .pdf + ) + } + + static func validateInheritedType(decl: StructDeclSyntax) -> Bool { + decl.inheritanceClause? + .inheritedTypeCollection + .map(\.typeName) + .compactMap { syntax in + syntax.as(SimpleTypeIdentifierSyntax.self) + } + .first(where: { syntax in + syntax.name.description.contains("CsvComposition") + }) != nil + } + + static func extractColumns(_ type: C.Type, variableDecl decl: VariableDeclSyntax) -> [String] { + guard let attributes = decl.attributes else { + return [] + } + return attributes.compactMap { attribute in + var columns: [String] = [] + for attr in attributes { + guard let attr = attr.as(AttributeSyntax.self) else { + continue + } + let hasCsvRowsAttr = attr.attributeName.as(SimpleTypeIdentifierSyntax.self)?.name.text == "CsvRows" + if !hasCsvRowsAttr { + continue + } + guard let tokens = attr.argument?.tokens(viewMode: .all) else { + continue + } + let column = tokens + .compactMap { + if case let TokenKind.stringSegment(column) = $0.tokenKind { + return column + } + return nil + } + .first + guard let column else { + continue + } + columns.append(column) + } + return columns + } + .flatMap { $0 } + } +} + +extension StructDeclSyntax { + func findColumns(type: (some CsvComposition).Type, members: MemberDeclListSyntax) -> [String] { + var columns: [String] = [] + for member in members { + guard let decl = member.decl.as(VariableDeclSyntax.self) else { + continue + } + let c = CsvCompositionParser.extractColumns(type, variableDecl: decl) + if c.isEmpty { + continue + } + columns.append(contentsOf: c) + } + return columns + } +} diff --git a/Sources/CsvBuilder/Example.swift b/Sources/CsvBuilder/Example.swift index 2c485b7..158b446 100644 --- a/Sources/CsvBuilder/Example.swift +++ b/Sources/CsvBuilder/Example.swift @@ -1,19 +1,10 @@ -// -// Example.swift -// -// -// Created by Fumiya Tanaka on 2022/08/24. -// - -import Foundation - - -public struct ExampleComposition: CsvComposition { +/// Just an example +struct ExampleComposition: CsvComposition { @CsvRows(column: "age") - public var ages: [String] + var ages: [String] @CsvRows(column: "name") - public var names: [String] + var names: [String] - public init() { } + init() { } } diff --git a/Sources/CsvBuilder/NewCsvComposition.swift b/Sources/CsvBuilder/NewCsvComposition.swift new file mode 100644 index 0000000..3e74ad5 --- /dev/null +++ b/Sources/CsvBuilder/NewCsvComposition.swift @@ -0,0 +1,24 @@ +// +// File.swift +// +// +// Created by Fumiya Tanaka on 2023/02/01. +// + +import Foundation + +/// Under development +public struct NewCsvComposition { + var columns: [Column] + var rows: Rows + + @dynamicMemberLookup + public struct Rows { + public typealias Value = [Column: [Row]] + var value: Value = [:] + + subscript(dynamicMember keyPath: KeyPath) -> Value { + fatalError() + } + } +}