From e28bee804f091399c94b0e15320e325924e3f995 Mon Sep 17 00:00:00 2001 From: Sven Weidauer Date: Sat, 8 Oct 2022 11:03:28 +0200 Subject: [PATCH 01/15] Make importer protocol. --- .../LibMakeColors/Importers/Importer.swift | 5 +++ .../Importers/List/ListImporter.swift | 36 +++++++++++++++++++ .../List}/Scanner+ColorParser.swift | 4 +-- Sources/LibMakeColors/MakeColors.swift | 26 ++------------ 4 files changed, 45 insertions(+), 26 deletions(-) create mode 100644 Sources/LibMakeColors/Importers/Importer.swift create mode 100644 Sources/LibMakeColors/Importers/List/ListImporter.swift rename Sources/LibMakeColors/{Model => Importers/List}/Scanner+ColorParser.swift (98%) diff --git a/Sources/LibMakeColors/Importers/Importer.swift b/Sources/LibMakeColors/Importers/Importer.swift new file mode 100644 index 0000000..7528edb --- /dev/null +++ b/Sources/LibMakeColors/Importers/Importer.swift @@ -0,0 +1,5 @@ +protocol Importer { + init(source: String) throws + + func read() throws -> [String: ColorDef] +} diff --git a/Sources/LibMakeColors/Importers/List/ListImporter.swift b/Sources/LibMakeColors/Importers/List/ListImporter.swift new file mode 100644 index 0000000..d0e3d50 --- /dev/null +++ b/Sources/LibMakeColors/Importers/List/ListImporter.swift @@ -0,0 +1,36 @@ +import Foundation + +struct ListImporter: Importer { + let input: String + + init(source: String) { + input = source + } + + func read() throws -> [String: ColorDef] { + let scanner = Scanner(string: try readInput()) + scanner.charactersToBeSkipped = .whitespaces + + return try scanner.colorList() + } + + func readInput() throws -> String { + if input == "-" { + return try readStdin() + } + + let url = URL(fileURLWithPath: input) + return try String(contentsOf: url) + } + + func readStdin() throws -> String { + guard + let data = try FileHandle.standardInput.readToEnd(), + let input = String(data: data, encoding: .utf8) + else { + throw Errors.cannotReadStdin + } + + return input + } +} diff --git a/Sources/LibMakeColors/Model/Scanner+ColorParser.swift b/Sources/LibMakeColors/Importers/List/Scanner+ColorParser.swift similarity index 98% rename from Sources/LibMakeColors/Model/Scanner+ColorParser.swift rename to Sources/LibMakeColors/Importers/List/Scanner+ColorParser.swift index 9870824..b3d43d7 100644 --- a/Sources/LibMakeColors/Model/Scanner+ColorParser.swift +++ b/Sources/LibMakeColors/Importers/List/Scanner+ColorParser.swift @@ -110,7 +110,7 @@ extension Scanner { func colorLine() -> (String, ColorDef)? { guard - let name = self.name(), + let name = name(), let def = colorDef(), endOfLine() else { @@ -168,7 +168,7 @@ extension Scanner { func commaSeparated() -> [UInt8]? { var result: [UInt8] = [] repeat { - guard let component = self.component() else { + guard let component = component() else { return nil } result.append(component) diff --git a/Sources/LibMakeColors/MakeColors.swift b/Sources/LibMakeColors/MakeColors.swift index a2db649..810a7c8 100644 --- a/Sources/LibMakeColors/MakeColors.swift +++ b/Sources/LibMakeColors/MakeColors.swift @@ -66,10 +66,8 @@ public final class MakeColors: ParsableCommand, Context { public init() {} public func run() throws { - let scanner = Scanner(string: try readInput()) - scanner.charactersToBeSkipped = .whitespaces - - let data = try scanner.colorList() + let importer = ListImporter(source: input) + let data = try importer.read() if dump { try dump(data: data) @@ -81,26 +79,6 @@ public final class MakeColors: ParsableCommand, Context { try writeOutput(fileWrapper) } - func readInput() throws -> String { - if input == "-" { - return try readStdin() - } - - let url = URL(fileURLWithPath: input) - return try String(contentsOf: url) - } - - func readStdin() throws -> String { - guard - let data = try FileHandle.standardInput.readToEnd(), - let input = String(data: data, encoding: .utf8) - else { - throw Errors.cannotReadStdin - } - - return input - } - func dump(data: [String: ColorDef]) throws { for (key, color) in data.sorted() { let resolved = try data.resolve(key) From 02d2350b29a1bf69e25d9d19d169f110bf3a200e Mon Sep 17 00:00:00 2001 From: Sven Weidauer Date: Sat, 8 Oct 2022 11:04:24 +0200 Subject: [PATCH 02/15] Fix warnings --- Sources/LibMakeColors/Generators/Generator.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/LibMakeColors/Generators/Generator.swift b/Sources/LibMakeColors/Generators/Generator.swift index 8561ac3..085021b 100644 --- a/Sources/LibMakeColors/Generators/Generator.swift +++ b/Sources/LibMakeColors/Generators/Generator.swift @@ -1,6 +1,6 @@ import Foundation -protocol Generator: class { +protocol Generator: AnyObject { static var defaultExtension: String { get } static var option: String { get } @@ -9,7 +9,7 @@ protocol Generator: class { func generate(data: [String: ColorDef]) throws -> FileWrapper } -protocol Context: class { +protocol Context: AnyObject { var prefix: String? { get } } From dc00d87a32cd7ca9992b626faf4bb94edac50641 Mon Sep 17 00:00:00 2001 From: Sven Weidauer Date: Sat, 8 Oct 2022 11:20:00 +0200 Subject: [PATCH 03/15] Add option to select importer. --- .../LibMakeColors/Importers/Importer.swift | 8 +++++ Sources/LibMakeColors/MakeColors.swift | 34 ++++++++++++++++++- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/Sources/LibMakeColors/Importers/Importer.swift b/Sources/LibMakeColors/Importers/Importer.swift index 7528edb..3fb9da2 100644 --- a/Sources/LibMakeColors/Importers/Importer.swift +++ b/Sources/LibMakeColors/Importers/Importer.swift @@ -2,4 +2,12 @@ protocol Importer { init(source: String) throws func read() throws -> [String: ColorDef] + + static var option: String { get } +} + +extension Importer { + static var option: String { + String(String(describing: self).droppingSuffix("Importer")) + } } diff --git a/Sources/LibMakeColors/MakeColors.swift b/Sources/LibMakeColors/MakeColors.swift index 810a7c8..cfe0fea 100644 --- a/Sources/LibMakeColors/MakeColors.swift +++ b/Sources/LibMakeColors/MakeColors.swift @@ -19,6 +19,35 @@ private struct GeneratorOption: EnumerableFlag, CustomStringConvertible { } } +private struct ImporterOption: CaseIterable, ExpressibleByArgument, CustomStringConvertible { + static let allCases: [ImporterOption] = [ + .list, + ] + + static let list = ImporterOption(type: ListImporter.self) + + let type: Importer.Type + + init(type: Importer.Type) { + self.type = type + } + + init?(argument: String) { + guard let found = Self.allCases.first(where: { $0.description.caseInsensitiveCompare(argument) == .orderedSame }) else { + return nil + } + self = found + } + + var description: String { + type.option + } + + static func == (lhs: ImporterOption, rhs: ImporterOption) -> Bool { + lhs.type == rhs.type + } +} + enum Errors: Error { case syntaxError case duplicateColor(String) @@ -54,6 +83,9 @@ public final class MakeColors: ParsableCommand, Context { @Flag(help: "The formatter to use.") private var formatter = GeneratorOption.allCases[0] + @Option(help: "The importer to use.") + private var importer = ImporterOption.list + @Option(help: "Prefix for color names.") var prefix: String? @@ -66,7 +98,7 @@ public final class MakeColors: ParsableCommand, Context { public init() {} public func run() throws { - let importer = ListImporter(source: input) + let importer = try importer.type.init(source: input) let data = try importer.read() if dump { From d7d34812cf00830b71d285a427a5abcb2d4c8dc7 Mon Sep 17 00:00:00 2001 From: Sven Weidauer Date: Sat, 8 Oct 2022 11:42:02 +0200 Subject: [PATCH 04/15] Format --- Sources/LibMakeColors/MakeColors.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Sources/LibMakeColors/MakeColors.swift b/Sources/LibMakeColors/MakeColors.swift index cfe0fea..949bc52 100644 --- a/Sources/LibMakeColors/MakeColors.swift +++ b/Sources/LibMakeColors/MakeColors.swift @@ -33,7 +33,10 @@ private struct ImporterOption: CaseIterable, ExpressibleByArgument, CustomString } init?(argument: String) { - guard let found = Self.allCases.first(where: { $0.description.caseInsensitiveCompare(argument) == .orderedSame }) else { + guard + let found = Self.allCases + .first(where: { $0.description.caseInsensitiveCompare(argument) == .orderedSame }) + else { return nil } self = found From 012203a4f367ef36437557b43fca14ba7ff74c35 Mon Sep 17 00:00:00 2001 From: Sven Weidauer Date: Sat, 8 Oct 2022 11:42:42 +0200 Subject: [PATCH 05/15] Lowercase importer names --- Sources/LibMakeColors/Importers/Importer.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/LibMakeColors/Importers/Importer.swift b/Sources/LibMakeColors/Importers/Importer.swift index 3fb9da2..426fec8 100644 --- a/Sources/LibMakeColors/Importers/Importer.swift +++ b/Sources/LibMakeColors/Importers/Importer.swift @@ -8,6 +8,6 @@ protocol Importer { extension Importer { static var option: String { - String(String(describing: self).droppingSuffix("Importer")) + String(describing: self).droppingSuffix("Importer").lowercased() } } From 0bb53ea949764cb8ada68284746dd7ddf763833f Mon Sep 17 00:00:00 2001 From: Sven Weidauer Date: Sat, 8 Oct 2022 11:46:52 +0200 Subject: [PATCH 06/15] Remove library target --- Package.swift | 14 ++++---------- .../Extensions/FileWrapper+Extensions.swift | 0 .../Extensions/StringProtocol+Extensions.swift | 0 .../Generators/AndroidGenerator.swift | 0 .../Generators/AssetCatalogGenerator.swift | 0 .../Generators/Generator.swift | 0 .../Generators/HTMLGenerator.swift | 0 .../Importers/Importer.swift | 0 .../Importers/List/ListImporter.swift | 0 .../Importers/List/Scanner+ColorParser.swift | 0 .../{LibMakeColors => MakeColors}/MakeColors.swift | 1 + .../Model/Color+HSV.swift | 0 .../Model/Color.swift | 0 Sources/MakeColors/main.swift | 3 --- .../AssetCatalogFormattingTest.swift | 2 +- Tests/MakeColorsTests/ColorHSVTest.swift | 2 +- Tests/MakeColorsTests/ColorParserTest.swift | 2 +- 17 files changed, 8 insertions(+), 16 deletions(-) rename Sources/{LibMakeColors => MakeColors}/Extensions/FileWrapper+Extensions.swift (100%) rename Sources/{LibMakeColors => MakeColors}/Extensions/StringProtocol+Extensions.swift (100%) rename Sources/{LibMakeColors => MakeColors}/Generators/AndroidGenerator.swift (100%) rename Sources/{LibMakeColors => MakeColors}/Generators/AssetCatalogGenerator.swift (100%) rename Sources/{LibMakeColors => MakeColors}/Generators/Generator.swift (100%) rename Sources/{LibMakeColors => MakeColors}/Generators/HTMLGenerator.swift (100%) rename Sources/{LibMakeColors => MakeColors}/Importers/Importer.swift (100%) rename Sources/{LibMakeColors => MakeColors}/Importers/List/ListImporter.swift (100%) rename Sources/{LibMakeColors => MakeColors}/Importers/List/Scanner+ColorParser.swift (100%) rename Sources/{LibMakeColors => MakeColors}/MakeColors.swift (99%) rename Sources/{LibMakeColors => MakeColors}/Model/Color+HSV.swift (100%) rename Sources/{LibMakeColors => MakeColors}/Model/Color.swift (100%) delete mode 100644 Sources/MakeColors/main.swift diff --git a/Package.swift b/Package.swift index d0b3184..f6195f4 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.3 +// swift-tools-version:5.7 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription @@ -10,17 +10,11 @@ let package = Package( ], dependencies: [ .package(url: "https://github.com/apple/swift-argument-parser", .upToNextMinor(from: "0.3.1")), - .package(url: "https://github.com/robb/RBBJSON", .branch("main")), + .package(url: "https://github.com/robb/RBBJSON", branch: "main"), ], targets: [ - .target( + .executableTarget( name: "MakeColors", - dependencies: [ - "LibMakeColors", - ] - ), - .target( - name: "LibMakeColors", dependencies: [ .product(name: "ArgumentParser", package: "swift-argument-parser"), ] @@ -28,7 +22,7 @@ let package = Package( .testTarget( name: "MakeColorsTests", dependencies: [ - "LibMakeColors", + "MakeColors", .product(name: "RBBJSON", package: "RBBJSON"), ] ), diff --git a/Sources/LibMakeColors/Extensions/FileWrapper+Extensions.swift b/Sources/MakeColors/Extensions/FileWrapper+Extensions.swift similarity index 100% rename from Sources/LibMakeColors/Extensions/FileWrapper+Extensions.swift rename to Sources/MakeColors/Extensions/FileWrapper+Extensions.swift diff --git a/Sources/LibMakeColors/Extensions/StringProtocol+Extensions.swift b/Sources/MakeColors/Extensions/StringProtocol+Extensions.swift similarity index 100% rename from Sources/LibMakeColors/Extensions/StringProtocol+Extensions.swift rename to Sources/MakeColors/Extensions/StringProtocol+Extensions.swift diff --git a/Sources/LibMakeColors/Generators/AndroidGenerator.swift b/Sources/MakeColors/Generators/AndroidGenerator.swift similarity index 100% rename from Sources/LibMakeColors/Generators/AndroidGenerator.swift rename to Sources/MakeColors/Generators/AndroidGenerator.swift diff --git a/Sources/LibMakeColors/Generators/AssetCatalogGenerator.swift b/Sources/MakeColors/Generators/AssetCatalogGenerator.swift similarity index 100% rename from Sources/LibMakeColors/Generators/AssetCatalogGenerator.swift rename to Sources/MakeColors/Generators/AssetCatalogGenerator.swift diff --git a/Sources/LibMakeColors/Generators/Generator.swift b/Sources/MakeColors/Generators/Generator.swift similarity index 100% rename from Sources/LibMakeColors/Generators/Generator.swift rename to Sources/MakeColors/Generators/Generator.swift diff --git a/Sources/LibMakeColors/Generators/HTMLGenerator.swift b/Sources/MakeColors/Generators/HTMLGenerator.swift similarity index 100% rename from Sources/LibMakeColors/Generators/HTMLGenerator.swift rename to Sources/MakeColors/Generators/HTMLGenerator.swift diff --git a/Sources/LibMakeColors/Importers/Importer.swift b/Sources/MakeColors/Importers/Importer.swift similarity index 100% rename from Sources/LibMakeColors/Importers/Importer.swift rename to Sources/MakeColors/Importers/Importer.swift diff --git a/Sources/LibMakeColors/Importers/List/ListImporter.swift b/Sources/MakeColors/Importers/List/ListImporter.swift similarity index 100% rename from Sources/LibMakeColors/Importers/List/ListImporter.swift rename to Sources/MakeColors/Importers/List/ListImporter.swift diff --git a/Sources/LibMakeColors/Importers/List/Scanner+ColorParser.swift b/Sources/MakeColors/Importers/List/Scanner+ColorParser.swift similarity index 100% rename from Sources/LibMakeColors/Importers/List/Scanner+ColorParser.swift rename to Sources/MakeColors/Importers/List/Scanner+ColorParser.swift diff --git a/Sources/LibMakeColors/MakeColors.swift b/Sources/MakeColors/MakeColors.swift similarity index 99% rename from Sources/LibMakeColors/MakeColors.swift rename to Sources/MakeColors/MakeColors.swift index 949bc52..3254d4e 100644 --- a/Sources/LibMakeColors/MakeColors.swift +++ b/Sources/MakeColors/MakeColors.swift @@ -79,6 +79,7 @@ enum HelpTexts { ) } +@main public final class MakeColors: ParsableCommand, Context { @Argument(help: HelpTexts.input) var input: String diff --git a/Sources/LibMakeColors/Model/Color+HSV.swift b/Sources/MakeColors/Model/Color+HSV.swift similarity index 100% rename from Sources/LibMakeColors/Model/Color+HSV.swift rename to Sources/MakeColors/Model/Color+HSV.swift diff --git a/Sources/LibMakeColors/Model/Color.swift b/Sources/MakeColors/Model/Color.swift similarity index 100% rename from Sources/LibMakeColors/Model/Color.swift rename to Sources/MakeColors/Model/Color.swift diff --git a/Sources/MakeColors/main.swift b/Sources/MakeColors/main.swift deleted file mode 100644 index db25b90..0000000 --- a/Sources/MakeColors/main.swift +++ /dev/null @@ -1,3 +0,0 @@ -import LibMakeColors - -MakeColors.main() diff --git a/Tests/MakeColorsTests/AssetCatalogFormattingTest.swift b/Tests/MakeColorsTests/AssetCatalogFormattingTest.swift index 4c0e8e5..d4f9f10 100644 --- a/Tests/MakeColorsTests/AssetCatalogFormattingTest.swift +++ b/Tests/MakeColorsTests/AssetCatalogFormattingTest.swift @@ -1,4 +1,4 @@ -@testable import LibMakeColors +@testable import MakeColors import RBBJSON import XCTest diff --git a/Tests/MakeColorsTests/ColorHSVTest.swift b/Tests/MakeColorsTests/ColorHSVTest.swift index d7bd9dc..9eba183 100644 --- a/Tests/MakeColorsTests/ColorHSVTest.swift +++ b/Tests/MakeColorsTests/ColorHSVTest.swift @@ -1,4 +1,4 @@ -@testable import LibMakeColors +@testable import MakeColors import XCTest final class ColorHSVTest: XCTestCase { diff --git a/Tests/MakeColorsTests/ColorParserTest.swift b/Tests/MakeColorsTests/ColorParserTest.swift index 075c72d..8c3756e 100644 --- a/Tests/MakeColorsTests/ColorParserTest.swift +++ b/Tests/MakeColorsTests/ColorParserTest.swift @@ -1,4 +1,4 @@ -@testable import LibMakeColors +@testable import MakeColors import XCTest final class ColorParserTest: XCTestCase { From 3adefbf70e1291aaa645a37bb3200b9ebc0164c6 Mon Sep 17 00:00:00 2001 From: Sven Weidauer Date: Sat, 8 Oct 2022 11:47:32 +0200 Subject: [PATCH 07/15] Update argument parser --- Package.resolved | 42 ++++++++++++++++++++---------------------- Package.swift | 2 +- 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/Package.resolved b/Package.resolved index a0484d0..717a74e 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,25 +1,23 @@ { - "object": { - "pins": [ - { - "package": "RBBJSON", - "repositoryURL": "https://github.com/robb/RBBJSON", - "state": { - "branch": "main", - "revision": "102c970283e105d7c5be2e29630db29c808c20eb", - "version": null - } - }, - { - "package": "swift-argument-parser", - "repositoryURL": "https://github.com/apple/swift-argument-parser", - "state": { - "branch": null, - "revision": "92646c0cdbaca076c8d3d0207891785b3379cbff", - "version": "0.3.1" - } + "pins" : [ + { + "identity" : "rbbjson", + "kind" : "remoteSourceControl", + "location" : "https://github.com/robb/RBBJSON", + "state" : { + "branch" : "main", + "revision" : "102c970283e105d7c5be2e29630db29c808c20eb" } - ] - }, - "version": 1 + }, + { + "identity" : "swift-argument-parser", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-argument-parser", + "state" : { + "revision" : "9f39744e025c7d377987f30b03770805dcb0bcd1", + "version" : "1.1.4" + } + } + ], + "version" : 2 } diff --git a/Package.swift b/Package.swift index f6195f4..0c21422 100644 --- a/Package.swift +++ b/Package.swift @@ -9,7 +9,7 @@ let package = Package( .macOS("10.15.4"), ], dependencies: [ - .package(url: "https://github.com/apple/swift-argument-parser", .upToNextMinor(from: "0.3.1")), + .package(url: "https://github.com/apple/swift-argument-parser", .upToNextMinor(from: "1.1.4")), .package(url: "https://github.com/robb/RBBJSON", branch: "main"), ], targets: [ From 956c1f5d2766cd0968858af9ec2fef2c196021eb Mon Sep 17 00:00:00 2001 From: Sven Weidauer Date: Sat, 8 Oct 2022 11:48:33 +0200 Subject: [PATCH 08/15] Make importer async --- Sources/MakeColors/Importers/Importer.swift | 2 +- Sources/MakeColors/MakeColors.swift | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/MakeColors/Importers/Importer.swift b/Sources/MakeColors/Importers/Importer.swift index 426fec8..5ba54c2 100644 --- a/Sources/MakeColors/Importers/Importer.swift +++ b/Sources/MakeColors/Importers/Importer.swift @@ -1,7 +1,7 @@ protocol Importer { init(source: String) throws - func read() throws -> [String: ColorDef] + func read() async throws -> [String: ColorDef] static var option: String { get } } diff --git a/Sources/MakeColors/MakeColors.swift b/Sources/MakeColors/MakeColors.swift index 3254d4e..c19563f 100644 --- a/Sources/MakeColors/MakeColors.swift +++ b/Sources/MakeColors/MakeColors.swift @@ -80,7 +80,7 @@ enum HelpTexts { } @main -public final class MakeColors: ParsableCommand, Context { +public final class MakeColors: AsyncParsableCommand, Context { @Argument(help: HelpTexts.input) var input: String @@ -101,9 +101,9 @@ public final class MakeColors: ParsableCommand, Context { public init() {} - public func run() throws { + public func run() async throws { let importer = try importer.type.init(source: input) - let data = try importer.read() + let data = try await importer.read() if dump { try dump(data: data) From 2b1f21dbab08c36266aed891aeeee76fc5220f95 Mon Sep 17 00:00:00 2001 From: Sven Weidauer Date: Sat, 8 Oct 2022 12:41:02 +0200 Subject: [PATCH 09/15] Quick and dirty figma importer --- Package.swift | 2 +- .../Importers/Figma/FigmaImporter.swift | 149 ++++++++++++++++++ Sources/MakeColors/MakeColors.swift | 1 + 3 files changed, 151 insertions(+), 1 deletion(-) create mode 100644 Sources/MakeColors/Importers/Figma/FigmaImporter.swift diff --git a/Package.swift b/Package.swift index 0c21422..05a09d5 100644 --- a/Package.swift +++ b/Package.swift @@ -6,7 +6,7 @@ import PackageDescription let package = Package( name: "MakeColors", platforms: [ - .macOS("10.15.4"), + .macOS("12.0"), ], dependencies: [ .package(url: "https://github.com/apple/swift-argument-parser", .upToNextMinor(from: "1.1.4")), diff --git a/Sources/MakeColors/Importers/Figma/FigmaImporter.swift b/Sources/MakeColors/Importers/Figma/FigmaImporter.swift new file mode 100644 index 0000000..488f4d8 --- /dev/null +++ b/Sources/MakeColors/Importers/Figma/FigmaImporter.swift @@ -0,0 +1,149 @@ +import Foundation + +enum FigmaErrors: Error { + case invalidUrl + case missingToken + case invalidResponse + case missingColor(String) +} + +class FigmaImporter: Importer { + let key: String + let token: String + + required init(source: String) throws { + // https://www.figma.com/file/:key/:title + guard + let url = URL(string: source), + url.host == "www.figma.com", + url.pathComponents.count >= 4, + url.pathComponents[1] == "file" + else { + throw FigmaErrors.invalidUrl + } + + key = url.pathComponents[2] + + guard let token = ProcessInfo.processInfo.environment["FIGMA_TOKEN"] else { + throw FigmaErrors.missingToken + } + + self.token = token + } + + func read() async throws -> [String: ColorDef] { + let styles = try await request(StylesResponse.self, path: "/v1/files/\(key)/styles").meta.styles + .filter { $0.styleType == "FILL" } + + let ids = styles.map(\.nodeId).joined(separator: ",") + + let nodes = try await request( + NodesResponse.self, + path: "/v1/files/\(key)/nodes", + query: [URLQueryItem(name: "ids", value: ids)] + ) + .nodes + + var result: [String: ColorDef] = [:] + result.reserveCapacity(styles.count) + + for style in styles { + guard + let node = nodes[style.nodeId], + let fill = node.document.fills.first(where: { $0.type == "SOLID" }) + else { + throw FigmaErrors.missingColor(style.name) + } + + if node.document.fills.count > 1 { + print("Warning: Multiple fills defined for \(style.name)") + } + + if fill.blendMode != "NORMAL" { + print("Warning: Blend mode \(fill.blendMode) used for \(style.name)") + } + + guard !result.keys.contains(style.name) else { + throw Errors.duplicateColor(style.name) + } + + result[style.name] = .color(Color(fill.color)) + } + + return result + } + + func request(_: T.Type = T.self, path: String, query: [URLQueryItem]? = nil) async throws -> T { + var components = URLComponents() + components.scheme = "https" + components.host = "api.figma.com" + components.path = path + components.queryItems = query + + guard let url = components.url else { + fatalError("Cannot create url. Components: \(components)") + } + + var request = URLRequest(url: url) + request.setValue(token, forHTTPHeaderField: "X-Figma-Token") + + let (data, response) = try await URLSession.shared.data(for: request) + + guard let response = response as? HTTPURLResponse else { + fatalError("Non-HTTP-Response received: \(response)") + } + + guard response.statusCode == 200 else { + throw FigmaErrors.invalidResponse + } + + let decoder = JSONDecoder() + decoder.keyDecodingStrategy = .convertFromSnakeCase + return try decoder.decode(T.self, from: data) + } +} + +struct StylesResponse: Decodable { + var meta: Meta + struct Meta: Decodable { + var styles: [Style] + } + + struct Style: Decodable { + var nodeId: String + var styleType: String + var name: String + var description: String + } +} + +struct NodesResponse: Decodable { + var nodes: [String: Node] + + struct Node: Decodable { + var document: Document + } + + struct Document: Decodable { + var fills: [Fill] + } + + struct Fill: Decodable { + var blendMode: String + var type: String + var color: Color + } + + struct Color: Decodable { + var r, g, b, a: Float + } +} + +extension Color { + init(_ color: NodesResponse.Color) { + red = UInt8(truncatingIfNeeded: Int(color.r * 0xFF)) + green = UInt8(truncatingIfNeeded: Int(color.g * 0xFF)) + blue = UInt8(truncatingIfNeeded: Int(color.b * 0xFF)) + alpha = UInt8(truncatingIfNeeded: Int(color.a * 0xFF)) + } +} diff --git a/Sources/MakeColors/MakeColors.swift b/Sources/MakeColors/MakeColors.swift index c19563f..12a5ff3 100644 --- a/Sources/MakeColors/MakeColors.swift +++ b/Sources/MakeColors/MakeColors.swift @@ -22,6 +22,7 @@ private struct GeneratorOption: EnumerableFlag, CustomStringConvertible { private struct ImporterOption: CaseIterable, ExpressibleByArgument, CustomStringConvertible { static let allCases: [ImporterOption] = [ .list, + .init(type: FigmaImporter.self), ] static let list = ImporterOption(type: ListImporter.self) From ac708f4ae2497c0357333cb5f33b84658ee0a5ef Mon Sep 17 00:00:00 2001 From: Sven Weidauer Date: Sat, 8 Oct 2022 12:50:55 +0200 Subject: [PATCH 10/15] Let importer determine output file name. --- .../Importers/Figma/FigmaImporter.swift | 2 ++ Sources/MakeColors/Importers/Importer.swift | 2 ++ .../MakeColors/Importers/List/ListImporter.swift | 2 ++ Sources/MakeColors/MakeColors.swift | 15 +++------------ 4 files changed, 9 insertions(+), 12 deletions(-) diff --git a/Sources/MakeColors/Importers/Figma/FigmaImporter.swift b/Sources/MakeColors/Importers/Figma/FigmaImporter.swift index 488f4d8..c84339f 100644 --- a/Sources/MakeColors/Importers/Figma/FigmaImporter.swift +++ b/Sources/MakeColors/Importers/Figma/FigmaImporter.swift @@ -10,6 +10,7 @@ enum FigmaErrors: Error { class FigmaImporter: Importer { let key: String let token: String + let outputName: String required init(source: String) throws { // https://www.figma.com/file/:key/:title @@ -23,6 +24,7 @@ class FigmaImporter: Importer { } key = url.pathComponents[2] + outputName = url.pathComponents[3] guard let token = ProcessInfo.processInfo.environment["FIGMA_TOKEN"] else { throw FigmaErrors.missingToken diff --git a/Sources/MakeColors/Importers/Importer.swift b/Sources/MakeColors/Importers/Importer.swift index 5ba54c2..6c4d5d8 100644 --- a/Sources/MakeColors/Importers/Importer.swift +++ b/Sources/MakeColors/Importers/Importer.swift @@ -3,6 +3,8 @@ protocol Importer { func read() async throws -> [String: ColorDef] + var outputName: String { get } + static var option: String { get } } diff --git a/Sources/MakeColors/Importers/List/ListImporter.swift b/Sources/MakeColors/Importers/List/ListImporter.swift index d0e3d50..ef291b1 100644 --- a/Sources/MakeColors/Importers/List/ListImporter.swift +++ b/Sources/MakeColors/Importers/List/ListImporter.swift @@ -2,9 +2,11 @@ import Foundation struct ListImporter: Importer { let input: String + var outputName: String init(source: String) { input = source + outputName = URL(fileURLWithPath: source).deletingPathExtension().lastPathComponent } func read() throws -> [String: ColorDef] { diff --git a/Sources/MakeColors/MakeColors.swift b/Sources/MakeColors/MakeColors.swift index 12a5ff3..8b88849 100644 --- a/Sources/MakeColors/MakeColors.swift +++ b/Sources/MakeColors/MakeColors.swift @@ -113,7 +113,7 @@ public final class MakeColors: AsyncParsableCommand, Context { let generator = formatter.type.init(context: self) let fileWrapper = try generator.generate(data: data) - try writeOutput(fileWrapper) + try writeOutput(fileWrapper, name: output ?? "\(importer.outputName).\(formatter.type.defaultExtension)") } func dump(data: [String: ColorDef]) throws { @@ -133,7 +133,7 @@ public final class MakeColors: AsyncParsableCommand, Context { } } - func writeOutput(_ wrapper: FileWrapper) throws { + func writeOutput(_ wrapper: FileWrapper, name: String) throws { if shouldWriteToStdout { guard wrapper.isRegularFile, let contents = wrapper.regularFileContents else { throw Errors.cannotWriteWrapperToStdout @@ -141,19 +141,10 @@ public final class MakeColors: AsyncParsableCommand, Context { FileHandle.standardOutput.write(contents) } else { - let writeURL = outputURL(extension: formatter.type.defaultExtension) + let writeURL = URL(fileURLWithPath: name) try wrapper.write(to: writeURL, options: .atomic, originalContentsURL: nil) } } var shouldWriteToStdout: Bool { output == "-" || (input == "-" && output == nil) } - - func outputURL(extension: String) -> URL { - if let output = output { - return URL(fileURLWithPath: output) - } else { - let basename = URL(fileURLWithPath: input).deletingPathExtension().lastPathComponent - return URL(fileURLWithPath: basename).appendingPathExtension(`extension`) - } - } } From 4a489ccd20396c5e760213e9baba53463a3e617f Mon Sep 17 00:00:00 2001 From: Sven Weidauer Date: Sat, 8 Oct 2022 12:59:18 +0200 Subject: [PATCH 11/15] Update .swift-version --- .swift-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.swift-version b/.swift-version index d346e2a..760606e 100644 --- a/.swift-version +++ b/.swift-version @@ -1 +1 @@ -5.3 +5.7 From fe95a6fd35e7894b755dd2bac03323304c030f07 Mon Sep 17 00:00:00 2001 From: Sven Weidauer Date: Sat, 8 Oct 2022 13:00:03 +0200 Subject: [PATCH 12/15] Need Xcode 14 to build --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 36deebd..961f0ff 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -24,7 +24,7 @@ jobs: head "https://github.com/${{ github.repository }}.git" license "MIT" - depends_on :xcode => ["12.0", :build] + depends_on :xcode => ["14.0", :build] def install system "make", "install", "prefix=#{prefix}" From 2271198f471971adf162d2c02f7f48110a7444c4 Mon Sep 17 00:00:00 2001 From: Sven Weidauer Date: Sat, 8 Oct 2022 13:04:59 +0200 Subject: [PATCH 13/15] Select Xcode 14 --- .github/workflows/test.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index be00eaa..7331788 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,6 +8,10 @@ jobs: runs-on: macos-latest steps: - uses: actions/checkout@v1 + + - uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: '14.0' - name: Cache Swift packages uses: actions/cache@v2 From 028095f00e973b8df40aebe361f7009834a9c319 Mon Sep 17 00:00:00 2001 From: Sven Weidauer Date: Sat, 8 Oct 2022 13:08:03 +0200 Subject: [PATCH 14/15] Use macos-12 image --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7331788..28f1422 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -5,7 +5,7 @@ on: jobs: BuildAndTest: - runs-on: macos-latest + runs-on: macos-12 steps: - uses: actions/checkout@v1 From fa9da2263367cb1d7b57c59f8b4fc86b8bdb76cb Mon Sep 17 00:00:00 2001 From: Sven Weidauer Date: Sat, 8 Oct 2022 13:09:40 +0200 Subject: [PATCH 15/15] Remove setup-xcode, should not be needed any more --- .github/workflows/test.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 28f1422..3b150ab 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,10 +8,6 @@ jobs: runs-on: macos-12 steps: - uses: actions/checkout@v1 - - - uses: maxim-lobanov/setup-xcode@v1 - with: - xcode-version: '14.0' - name: Cache Swift packages uses: actions/cache@v2