From 99d5c5ca746d6b1cc4cccbe42d6ca691c3885446 Mon Sep 17 00:00:00 2001 From: Sven Weidauer Date: Thu, 31 Dec 2020 18:41:25 +0100 Subject: [PATCH 01/41] Fix asset catalog extension --- Sources/LibMakeColors/Generators/AssetCatalogGenerator.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/LibMakeColors/Generators/AssetCatalogGenerator.swift b/Sources/LibMakeColors/Generators/AssetCatalogGenerator.swift index 7851575..62235eb 100644 --- a/Sources/LibMakeColors/Generators/AssetCatalogGenerator.swift +++ b/Sources/LibMakeColors/Generators/AssetCatalogGenerator.swift @@ -1,7 +1,7 @@ import Foundation final class AssetCatalogGenerator: Generator { - static let defaultExtension = "xcasset" + static let defaultExtension = "xcassets" static let option = "ios" let context: Context From fb0934ae01082ffc95f23fd12a28f82ae22ff453 Mon Sep 17 00:00:00 2001 From: Sven Weidauer Date: Thu, 31 Dec 2020 18:47:37 +0100 Subject: [PATCH 02/41] Output alpha as hex value as well to asset catalogs --- Sources/LibMakeColors/Generators/AssetCatalogGenerator.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/LibMakeColors/Generators/AssetCatalogGenerator.swift b/Sources/LibMakeColors/Generators/AssetCatalogGenerator.swift index 62235eb..964c6f5 100644 --- a/Sources/LibMakeColors/Generators/AssetCatalogGenerator.swift +++ b/Sources/LibMakeColors/Generators/AssetCatalogGenerator.swift @@ -67,7 +67,7 @@ private extension Color { "color" : { "color-space" : "srgb", "components" : { - "alpha" : "\(Float(alpha) / 256)", + "alpha" : "0x\(String(alpha, radix: 16))", "blue" : "0x\(String(blue, radix: 16))", "green" : "0x\(String(green, radix: 16))", "red" : "0x\(String(red, radix: 16))" From eb964e2e956f0d20e491aab55d5286fc1f7689cf Mon Sep 17 00:00:00 2001 From: Sven Weidauer Date: Thu, 31 Dec 2020 18:55:04 +0100 Subject: [PATCH 03/41] Add info about installing via makefile. --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 1dda36b..2d86b06 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,8 @@ brew tap 5sw/makecolors brew install make-colors ``` +If you don’t use Homebrew you can also install directly from source. Clone the repository or download the release and run `make install` inside the working copy. + ## Input format Each line in your input contains one color definition. That is a name followed by the actual color. We support RGB colors in a few formats similar to CSS: From 465c1c078f8980bcdf70b10519d3163085c2718c Mon Sep 17 00:00:00 2001 From: Sven Weidauer Date: Thu, 31 Dec 2020 19:01:35 +0100 Subject: [PATCH 04/41] Improve example in README.md --- Docs/assetcatalog.png | Bin 0 -> 14907 bytes Docs/html.png | Bin 0 -> 40445 bytes README.md | 38 +++++++++++++++++++++++++++++++++----- 3 files changed, 33 insertions(+), 5 deletions(-) create mode 100644 Docs/assetcatalog.png create mode 100644 Docs/html.png diff --git a/Docs/assetcatalog.png b/Docs/assetcatalog.png new file mode 100644 index 0000000000000000000000000000000000000000..05cc788ff9df22e3bfc11a70e99133362b9e222b GIT binary patch literal 14907 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 z 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 Sbz}+*d^p>CkgDyd3I79GfU~gx literal 0 HcmV?d00001 diff --git a/README.md b/README.md index 2d86b06..1e897c0 100644 --- a/README.md +++ b/README.md @@ -18,16 +18,19 @@ If you don’t use Homebrew you can also install directly from source. Clone the Each line in your input contains one color definition. That is a name followed by the actual color. We support RGB colors in a few formats similar to CSS: ``` -Color/Color1 #fff -Color/Color2 #abcdeff0 -Color/Color3 rgb(12, 13, 53) -Color/Color4 rgba(250, 250, 250, 128) +Base/Green #8fd151 +Base/PaleGreen #d0f9a9 +Base/Red rgb(249, 39, 7) +TransparentRed rgba(255, 0, 0, 128) +Base/Yellow #ff0 ``` Colors can also reference other colors by prefixing them with an `@` sign: ``` -ColorAlias @Color/Color1 +Error @Base/Red +Warning @Base/Yellow +Good @Base/Green ``` ## Output format @@ -36,14 +39,39 @@ ColorAlias @Color/Color1 The optional prefix followed by a `/` is added in front of the color name. Then for each part separate by / a new folder that provides namespace is inserted in the asset catalogs. Spaces are inserted between CamelCase words. Color references are inserted as copies of the original color. +For the given example input the generated asset catalog looks like this: + +![](Docs/assetcatalog.png) + ### Android resource XML (`--android`) The optional prefix, followed by a underscore is added in front of the name. Names are converted from CamelCase to snake_case and / is replaced by underscores as well. Color references the generated color resource also references the original color. +The generated XML for the example input looks like this: + +``` + + + #8FD151 + #D0F9A9 + #F92707 + #FFFF00 + #FF000080 + @color/bla_base_red + @color/bla_base_green + @color/bla_base_yellow + + +``` + ### HTML preview (`--html`) Generates a simple HTML table with the color names, values and a sample. +The generated HTML looks like this: + +![](Docs/html.png) + ## Future work - Support other color formats (HSV, ...) From b657561e5cde5c0800b3647065bb66b4a99e222b Mon Sep 17 00:00:00 2001 From: Sven Weidauer Date: Thu, 31 Dec 2020 19:03:05 +0100 Subject: [PATCH 05/41] Crop asset catalog screenshot --- Docs/assetcatalog.png | Bin 14907 -> 14349 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/Docs/assetcatalog.png b/Docs/assetcatalog.png index 05cc788ff9df22e3bfc11a70e99133362b9e222b..a5107eabdabd9d7df29645dee2ff079340d15ed7 100644 GIT binary patch delta 11167 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 JR!W-%{||4rT-*Qv delta 11759 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 From 01b564adb5b6c1508bea8042c346a15f3a671817 Mon Sep 17 00:00:00 2001 From: Sven Weidauer Date: Thu, 31 Dec 2020 19:05:36 +0100 Subject: [PATCH 06/41] Fix android output in readme file --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 1e897c0..04ab603 100644 --- a/README.md +++ b/README.md @@ -52,14 +52,14 @@ The generated XML for the example input looks like this: ``` - #8FD151 - #D0F9A9 - #F92707 - #FFFF00 - #FF000080 - @color/bla_base_red - @color/bla_base_green - @color/bla_base_yellow + #8FD151 + #D0F9A9 + #F92707 + #FFFF00 + #FF000080 + @color/base_red + @color/base_green + @color/base_yellow ``` From abc0ef910d49139f0b460ade4d83ec74c474c12a Mon Sep 17 00:00:00 2001 From: Sven Weidauer Date: Thu, 31 Dec 2020 19:31:42 +0100 Subject: [PATCH 07/41] Allow reading input from stdin --- Package.swift | 2 +- Sources/LibMakeColors/MakeColors.swift | 45 ++++++++++++++++++++++---- 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/Package.swift b/Package.swift index ea9aaaa..47180f3 100644 --- a/Package.swift +++ b/Package.swift @@ -6,7 +6,7 @@ import PackageDescription let package = Package( name: "MakeColors", platforms: [ - .macOS(.v10_15), + .macOS("10.15.4"), ], dependencies: [ .package(url: "https://github.com/apple/swift-argument-parser", .upToNextMinor(from: "0.3.1")), diff --git a/Sources/LibMakeColors/MakeColors.swift b/Sources/LibMakeColors/MakeColors.swift index 8dfa743..3b4e7f5 100644 --- a/Sources/LibMakeColors/MakeColors.swift +++ b/Sources/LibMakeColors/MakeColors.swift @@ -24,6 +24,8 @@ enum Errors: Error { case duplicateColor(String) case missingReference(String) case cyclicReference(String) + case cannotWriteWrapperToStdout + case cannotReadStdin } public final class MakeColors: ParsableCommand, Context { @@ -42,10 +44,7 @@ public final class MakeColors: ParsableCommand, Context { public init() {} public func run() throws { - let url = URL(fileURLWithPath: input) - let string = try String(contentsOf: url) - - let scanner = Scanner(string: string) + let scanner = Scanner(string: try readInput()) scanner.charactersToBeSkipped = .whitespaces let data = try scanner.colorList() @@ -68,10 +67,44 @@ public final class MakeColors: ParsableCommand, Context { let generator = formatter.type.init(context: self) let fileWrapper = try generator.generate(data: data) - let writeURL = outputURL(extension: formatter.type.defaultExtension) - try fileWrapper.write(to: writeURL, options: .atomic, originalContentsURL: nil) + 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 writeOutput(_ wrapper: FileWrapper) throws { + if shouldWriteToStdout { + guard wrapper.isRegularFile, let contents = wrapper.regularFileContents else { + throw Errors.cannotWriteWrapperToStdout + } + + FileHandle.standardOutput.write(contents) + } else { + let writeURL = outputURL(extension: formatter.type.defaultExtension) + try wrapper.write(to: writeURL, options: .atomic, originalContentsURL: nil) + } + } + + var shouldWriteToStdout: Bool { input == "-" && output == nil } + func outputURL(extension: String) -> URL { if let output = output { return URL(fileURLWithPath: output) From 13f985c0a43f7d2edca069dbda68920e1351a27c Mon Sep 17 00:00:00 2001 From: Sven Weidauer Date: Thu, 31 Dec 2020 19:44:26 +0100 Subject: [PATCH 08/41] Improve usage message and add to README --- README.md | 20 ++++++++++++++++++ Sources/LibMakeColors/MakeColors.swift | 29 +++++++++++++++++++++----- 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 04ab603..26464a0 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,26 @@ brew install make-colors If you don’t use Homebrew you can also install directly from source. Clone the repository or download the release and run `make install` inside the working copy. +## Usage + +``` +USAGE: make-colors [--ios] [--android] [--html] [--prefix ] [--output ] + +ARGUMENTS: + The color list to process. + Use - to process the standard input. + +OPTIONS: + --ios/--android/--html The formatter to use (default: ios) + --prefix Prefix for color names + --output Output file to write. + Use - for standard output. + Default is the input file name with the appropriate file extension. If + the input is read from the standard input the default is standard + output. + -h, --help Show help information. +``` + ## Input format Each line in your input contains one color definition. That is a name followed by the actual color. We support RGB colors in a few formats similar to CSS: diff --git a/Sources/LibMakeColors/MakeColors.swift b/Sources/LibMakeColors/MakeColors.swift index 3b4e7f5..3140601 100644 --- a/Sources/LibMakeColors/MakeColors.swift +++ b/Sources/LibMakeColors/MakeColors.swift @@ -28,17 +28,36 @@ enum Errors: Error { case cannotReadStdin } +enum HelpTexts { + static let input = ArgumentHelp( + "The color list to process.", + discussion: """ + Use - to process the standard input. + """ + ) + + static let output = ArgumentHelp( + "Output file to write.", + discussion: """ + Use - for standard output. + Default is the input file name with the appropriate file extension. \ + If the input is - the default is standard output. + Note that asset catalogs cannot be written to standard output. + """ + ) +} + public final class MakeColors: ParsableCommand, Context { - @Argument(help: "The color list to proces") + @Argument(help: HelpTexts.input) var input: String - @Flag(help: "The formatter to use") + @Flag(help: "The formatter to use.") private var formatter = GeneratorOption.allCases[0] - @Option(help: "Prefix for color names") + @Option(help: "Prefix for color names.") var prefix: String? - @Option(help: "Output file") + @Option(help: HelpTexts.output) var output: String? public init() {} @@ -103,7 +122,7 @@ public final class MakeColors: ParsableCommand, Context { } } - var shouldWriteToStdout: Bool { input == "-" && output == nil } + var shouldWriteToStdout: Bool { output == "-" || (input == "-" && output == nil) } func outputURL(extension: String) -> URL { if let output = output { From b1adcb158e2cfa0764f4c881bc191f7764fae7aa Mon Sep 17 00:00:00 2001 From: Sven Weidauer Date: Thu, 31 Dec 2020 19:47:55 +0100 Subject: [PATCH 09/41] Accept upper-case hex digits for colors --- Sources/LibMakeColors/Model/Scanner+ColorParser.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/LibMakeColors/Model/Scanner+ColorParser.swift b/Sources/LibMakeColors/Model/Scanner+ColorParser.swift index 3d56246..62904b1 100644 --- a/Sources/LibMakeColors/Model/Scanner+ColorParser.swift +++ b/Sources/LibMakeColors/Model/Scanner+ColorParser.swift @@ -111,7 +111,7 @@ private extension Scanner { } private extension CharacterSet { - static let hex = CharacterSet(charactersIn: "0123456789abcdef") + static let hex = CharacterSet(charactersIn: "0123456789abcdefABCDEF") static let name = alphanumerics.union(CharacterSet(charactersIn: "_/")) } From 0991ca01edd64c30cd59aee838fe65e1b9acff23 Mon Sep 17 00:00:00 2001 From: Sven Weidauer Date: Thu, 31 Dec 2020 19:49:04 +0100 Subject: [PATCH 10/41] Test scanning upper case hex digits --- Tests/MakeColorsTests/ColorParserTest.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Tests/MakeColorsTests/ColorParserTest.swift b/Tests/MakeColorsTests/ColorParserTest.swift index 86e8e4d..4a8a5e5 100644 --- a/Tests/MakeColorsTests/ColorParserTest.swift +++ b/Tests/MakeColorsTests/ColorParserTest.swift @@ -8,6 +8,12 @@ final class ColorParserTest: XCTestCase { XCTAssertEqual(Color(red: 0xAA, green: 0xBB, blue: 0xCC), color) } + func testScanningThreeDigitColorUppercase() throws { + let scanner = Scanner(string: "#ABc") + let color = scanner.color() + XCTAssertEqual(Color(red: 0xAA, green: 0xBB, blue: 0xCC), color) + } + func testScanningFourDigitColor() throws { let scanner = Scanner(string: "#abcd") let color = scanner.color() From d660c40793613a4f2228d7312f97c0d607054ecd Mon Sep 17 00:00:00 2001 From: Sven Weidauer Date: Thu, 31 Dec 2020 20:11:53 +0100 Subject: [PATCH 11/41] Implement parsing grayscale colors as white(value) or white(value, alpha) --- Sources/LibMakeColors/Model/Color.swift | 8 +++++++ .../Model/Scanner+ColorParser.swift | 24 +++++++++++++++++++ Tests/MakeColorsTests/ColorParserTest.swift | 24 +++++++++++++++++++ 3 files changed, 56 insertions(+) diff --git a/Sources/LibMakeColors/Model/Color.swift b/Sources/LibMakeColors/Model/Color.swift index ef2030e..6e9740d 100644 --- a/Sources/LibMakeColors/Model/Color.swift +++ b/Sources/LibMakeColors/Model/Color.swift @@ -19,6 +19,14 @@ struct Color: CustomStringConvertible, Equatable { alpha = array.count == 4 ? array[3] : 0xFF } + init(white array: [UInt8]) { + precondition(array.count == 1 || array.count == 2) + red = array[0] + green = array[0] + blue = array[0] + alpha = array.count == 2 ? array[1] : 0xFF + } + var description: String { let alphaSuffix = alpha != 0xFF ? String(format: "%02X", alpha) : "" return String(format: "#%02X%02X%02X%@", red, green, blue, alphaSuffix) diff --git a/Sources/LibMakeColors/Model/Scanner+ColorParser.swift b/Sources/LibMakeColors/Model/Scanner+ColorParser.swift index 62904b1..fb0bd1c 100644 --- a/Sources/LibMakeColors/Model/Scanner+ColorParser.swift +++ b/Sources/LibMakeColors/Model/Scanner+ColorParser.swift @@ -27,6 +27,10 @@ extension Scanner { return Color(components) } + if string("white"), let arguments = argumentList(min: 1, max: 2) { + return Color(white: arguments) + } + return nil } @@ -91,6 +95,26 @@ extension Scanner { return result } + // swiftlint:disable:next discouraged_optional_collection + func argumentList(_ count: Int) -> [UInt8]? { + argumentList(min: count, max: count) + } + + // swiftlint:disable:next discouraged_optional_collection + func argumentList(min: Int, max: Int? = nil) -> [UInt8]? { + let max = max ?? Int.max + guard + string("("), + let arguments = commaSeparated(), + string(")"), + (min...max) ~= arguments.count + else { + return nil + } + + return arguments + } + // swiftlint:disable:next discouraged_optional_collection func commaSeparated() -> [UInt8]? { var result: [UInt8] = [] diff --git a/Tests/MakeColorsTests/ColorParserTest.swift b/Tests/MakeColorsTests/ColorParserTest.swift index 4a8a5e5..3db8714 100644 --- a/Tests/MakeColorsTests/ColorParserTest.swift +++ b/Tests/MakeColorsTests/ColorParserTest.swift @@ -43,4 +43,28 @@ final class ColorParserTest: XCTestCase { let color = scanner.color() XCTAssertEqual(Color(red: 1, green: 2, blue: 3, alpha: 4), color) } + + func testScanningWhite() throws { + let scanner = Scanner(string: "white(255)") + let color = scanner.color() + XCTAssertEqual(Color(red: 255, green: 255, blue: 255, alpha: 255), color) + } + + func testScanningWhiteWithAlpha() throws { + let scanner = Scanner(string: "white(255, 128)") + let color = scanner.color() + XCTAssertEqual(Color(red: 255, green: 255, blue: 255, alpha: 128), color) + } + + func testWhiteFailsWithoutArguments() throws { + let scanner = Scanner(string: "white()") + let color = scanner.color() + XCTAssertNil(color) + } + + func testWhiteFailsWith3Arguments() throws { + let scanner = Scanner(string: "white(1,2,3)") + let color = scanner.color() + XCTAssertNil(color) + } } From 4406619c84764dce53c72b1da038cc402fb6174d Mon Sep 17 00:00:00 2001 From: Sven Weidauer Date: Thu, 31 Dec 2020 20:16:41 +0100 Subject: [PATCH 12/41] Simplify color parser tests --- Tests/MakeColorsTests/ColorParserTest.swift | 38 +++++++++------------ 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/Tests/MakeColorsTests/ColorParserTest.swift b/Tests/MakeColorsTests/ColorParserTest.swift index 3db8714..708c77d 100644 --- a/Tests/MakeColorsTests/ColorParserTest.swift +++ b/Tests/MakeColorsTests/ColorParserTest.swift @@ -3,68 +3,62 @@ import XCTest final class ColorParserTest: XCTestCase { func testScanningThreeDigitColor() throws { - let scanner = Scanner(string: "#abc") - let color = scanner.color() + let color = scanColor("#abc") XCTAssertEqual(Color(red: 0xAA, green: 0xBB, blue: 0xCC), color) } func testScanningThreeDigitColorUppercase() throws { - let scanner = Scanner(string: "#ABc") - let color = scanner.color() + let color = scanColor("#ABc") XCTAssertEqual(Color(red: 0xAA, green: 0xBB, blue: 0xCC), color) } func testScanningFourDigitColor() throws { - let scanner = Scanner(string: "#abcd") - let color = scanner.color() + let color = scanColor("#abcd") XCTAssertEqual(Color(red: 0xAA, green: 0xBB, blue: 0xCC, alpha: 0xDD), color) } func testScanningSixDigitColor() throws { - let scanner = Scanner(string: "#abcdef") - let color = scanner.color() + let color = scanColor("#abcdef") XCTAssertEqual(Color(red: 0xAB, green: 0xCD, blue: 0xEF), color) } func testScanningEightDigitColor() throws { - let scanner = Scanner(string: "#abcdef17") - let color = scanner.color() + let color = scanColor("#abcdef17") XCTAssertEqual(Color(red: 0xAB, green: 0xCD, blue: 0xEF, alpha: 0x17), color) } func testScanningRGBColor() throws { - let scanner = Scanner(string: "rgb(1,2,3)") - let color = scanner.color() + let color = scanColor("rgb(1,2,3)") XCTAssertEqual(Color(red: 1, green: 2, blue: 3), color) } func testScanningRGBAColor() throws { - let scanner = Scanner(string: "rgba(1,2,3,4)") - let color = scanner.color() + let color = scanColor("rgba(1,2,3,4)") XCTAssertEqual(Color(red: 1, green: 2, blue: 3, alpha: 4), color) } func testScanningWhite() throws { - let scanner = Scanner(string: "white(255)") - let color = scanner.color() + let color = scanColor("white(255)") XCTAssertEqual(Color(red: 255, green: 255, blue: 255, alpha: 255), color) } func testScanningWhiteWithAlpha() throws { - let scanner = Scanner(string: "white(255, 128)") - let color = scanner.color() + let color = scanColor("white(255, 128)") XCTAssertEqual(Color(red: 255, green: 255, blue: 255, alpha: 128), color) } func testWhiteFailsWithoutArguments() throws { - let scanner = Scanner(string: "white()") - let color = scanner.color() + let color = scanColor("white()") XCTAssertNil(color) } func testWhiteFailsWith3Arguments() throws { - let scanner = Scanner(string: "white(1,2,3)") - let color = scanner.color() + let color = scanColor("white(1,2,3)") XCTAssertNil(color) } + + private func scanColor(_ input: String) -> Color? { + let scanner = Scanner(string: input) + return scanner.color() + } } From 90a3ed745fe3ace900b3888884470dfd9aa3a172 Mon Sep 17 00:00:00 2001 From: Sven Weidauer Date: Thu, 31 Dec 2020 20:19:51 +0100 Subject: [PATCH 13/41] Document grayscale colors in README --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 26464a0..3385e5d 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,13 @@ Base/Red rgb(249, 39, 7) TransparentRed rgba(255, 0, 0, 128) Base/Yellow #ff0 ``` +Grayscale colors can be produced with the `white(value)` and `white(value, alpha)` syntax. A value of zero means black while a value of 255 is pure white. + +``` +Black white(0) +MediumGray white(128) +TransparentGray white(128, 128) +``` Colors can also reference other colors by prefixing them with an `@` sign: From 911db304ae2b4c5f42d22b954220f47e1ac51253 Mon Sep 17 00:00:00 2001 From: Sven Weidauer Date: Thu, 31 Dec 2020 20:26:32 +0100 Subject: [PATCH 14/41] Add flag to dump read colors to console. Not very helpful if you want to use this in a pipeline --- README.md | 11 ++++---- Sources/LibMakeColors/MakeColors.swift | 35 ++++++++++++++++---------- 2 files changed, 28 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 3385e5d..afa53a7 100644 --- a/README.md +++ b/README.md @@ -16,20 +16,21 @@ If you don’t use Homebrew you can also install directly from source. Clone the ## Usage ``` -USAGE: make-colors [--ios] [--android] [--html] [--prefix ] [--output ] +USAGE: make-colors [--ios] [--android] [--html] [--prefix ] [--output ] [--dump] ARGUMENTS: The color list to process. Use - to process the standard input. OPTIONS: - --ios/--android/--html The formatter to use (default: ios) - --prefix Prefix for color names + --ios/--android/--html The formatter to use. (default: ios) + --prefix Prefix for color names. --output Output file to write. Use - for standard output. Default is the input file name with the appropriate file extension. If - the input is read from the standard input the default is standard - output. + the input is - the default is standard output. + Note that asset catalogs cannot be written to standard output. + --dump List read colors on console. -h, --help Show help information. ``` diff --git a/Sources/LibMakeColors/MakeColors.swift b/Sources/LibMakeColors/MakeColors.swift index 3140601..a2db649 100644 --- a/Sources/LibMakeColors/MakeColors.swift +++ b/Sources/LibMakeColors/MakeColors.swift @@ -60,6 +60,9 @@ public final class MakeColors: ParsableCommand, Context { @Option(help: HelpTexts.output) var output: String? + @Flag(help: "List read colors on console.") + var dump = false + public init() {} public func run() throws { @@ -68,19 +71,8 @@ public final class MakeColors: ParsableCommand, Context { let data = try scanner.colorList() - for (key, color) in data.sorted() { - let resolved = try data.resolve(key) - switch color { - case .color: - print(key.insertCamelCaseSeparators(), resolved, separator: ": ") - - case let .reference(referenced): - print( - "\(key.insertCamelCaseSeparators()) (@\(referenced.insertCamelCaseSeparators()))", - resolved, - separator: ": " - ) - } + if dump { + try dump(data: data) } let generator = formatter.type.init(context: self) @@ -109,6 +101,23 @@ public final class MakeColors: ParsableCommand, Context { return input } + func dump(data: [String: ColorDef]) throws { + for (key, color) in data.sorted() { + let resolved = try data.resolve(key) + switch color { + case .color: + print(key.insertCamelCaseSeparators(), resolved, separator: ": ") + + case let .reference(referenced): + print( + "\(key.insertCamelCaseSeparators()) (@\(referenced.insertCamelCaseSeparators()))", + resolved, + separator: ": " + ) + } + } + } + func writeOutput(_ wrapper: FileWrapper) throws { if shouldWriteToStdout { guard wrapper.isRegularFile, let contents = wrapper.regularFileContents else { From e65f68465412c11aa89f1f7da15298bb29c97212 Mon Sep 17 00:00:00 2001 From: Sven Weidauer Date: Fri, 1 Jan 2021 12:52:20 +0100 Subject: [PATCH 15/41] Support giving color values as percentage --- .../Model/Scanner+ColorParser.swift | 14 ++++++++- Tests/MakeColorsTests/ColorParserTest.swift | 29 +++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/Sources/LibMakeColors/Model/Scanner+ColorParser.swift b/Sources/LibMakeColors/Model/Scanner+ColorParser.swift index fb0bd1c..84957cd 100644 --- a/Sources/LibMakeColors/Model/Scanner+ColorParser.swift +++ b/Sources/LibMakeColors/Model/Scanner+ColorParser.swift @@ -119,13 +119,25 @@ extension Scanner { func commaSeparated() -> [UInt8]? { var result: [UInt8] = [] repeat { - guard let int = scanInt(), let component = UInt8(exactly: int) else { + guard let component = self.component() else { return nil } result.append(component) } while string(",") return result } + + func component() -> UInt8? { + guard var int = scanInt() else { + return nil + } + + if string("%") { + int = int * 0xFF / 100 + } + + return UInt8(exactly: int) + } } private extension Scanner { diff --git a/Tests/MakeColorsTests/ColorParserTest.swift b/Tests/MakeColorsTests/ColorParserTest.swift index 708c77d..edcb587 100644 --- a/Tests/MakeColorsTests/ColorParserTest.swift +++ b/Tests/MakeColorsTests/ColorParserTest.swift @@ -57,6 +57,35 @@ final class ColorParserTest: XCTestCase { XCTAssertNil(color) } + func testScanningColorWithPercentage() throws { + let color = scanColor("rgba(100%, 0, 50%, 100%)") + XCTAssertEqual(color, Color(red: 255, green: 0, blue: 127, alpha: 255)) + } + + func testReadingComponentAsByte() throws { + let scanner = Scanner(string: "128") + XCTAssertEqual(scanner.component(), 128) + XCTAssertTrue(scanner.isAtEnd) + } + + func testReadingComponentAs100Percent() throws { + let scanner = Scanner(string: "100%") + XCTAssertEqual(scanner.component(), 0xFF) + XCTAssertTrue(scanner.isAtEnd) + } + + func testReadingComponentAs0Percent() throws { + let scanner = Scanner(string: "0%") + XCTAssertEqual(scanner.component(), 0) + XCTAssertTrue(scanner.isAtEnd) + } + + func testReadingComponentAs50PercentRoundsDown() throws { + let scanner = Scanner(string: "50%") + XCTAssertEqual(scanner.component(), 127) + XCTAssertTrue(scanner.isAtEnd) + } + private func scanColor(_ input: String) -> Color? { let scanner = Scanner(string: input) return scanner.color() From 0b94872101087ebc96254f95b7821877799de09b Mon Sep 17 00:00:00 2001 From: Sven Weidauer Date: Fri, 1 Jan 2021 13:33:11 +0100 Subject: [PATCH 16/41] Add HSV conversion --- Sources/LibMakeColors/Model/Color+HSV.swift | 36 +++++++++++++++++++ Tests/MakeColorsTests/ColorHSVTest.swift | 39 +++++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 Sources/LibMakeColors/Model/Color+HSV.swift create mode 100644 Tests/MakeColorsTests/ColorHSVTest.swift diff --git a/Sources/LibMakeColors/Model/Color+HSV.swift b/Sources/LibMakeColors/Model/Color+HSV.swift new file mode 100644 index 0000000..8b9383f --- /dev/null +++ b/Sources/LibMakeColors/Model/Color+HSV.swift @@ -0,0 +1,36 @@ +extension Color { + init(hue: Int, saturation: UInt8, value: UInt8, alpha: UInt8 = 0xFF) { + let degrees = abs(hue % 360) + + let s = Double(saturation) / 0xFF + let v = Double(value) / 0xFF + let C = s * v + let X = C * (1 - abs((Double(degrees) / 60).truncatingRemainder(dividingBy: 2) - 1)) + let m = v - C + + let result: (r: Double, g: Double, b: Double) + switch degrees { + case 0..<60: + result = (C, X, 0) + case 60..<120: + result = (X, C, 0) + case 120..<180: + result = (0, C, X) + case 180..<240: + result = (0, X, C) + case 240..<300: + result = (X, 0, C) + case 300..<360: + result = (C, 0, X) + default: + fatalError("Degrees out of range") + } + + self.init( + red: UInt8(((result.r + m) * 0xFF).rounded()), + green: UInt8(((result.g + m) * 0xFF).rounded()), + blue: UInt8(((result.b + m) * 0xFF).rounded()), + alpha: alpha + ) + } +} diff --git a/Tests/MakeColorsTests/ColorHSVTest.swift b/Tests/MakeColorsTests/ColorHSVTest.swift new file mode 100644 index 0000000..d7bd9dc --- /dev/null +++ b/Tests/MakeColorsTests/ColorHSVTest.swift @@ -0,0 +1,39 @@ +@testable import LibMakeColors +import XCTest + +final class ColorHSVTest: XCTestCase { + func testHSV0Degrees() { + let color = Color(hue: 0, saturation: 0xFF, value: 0xFF) + XCTAssertEqual(color, Color(red: 0xFF, green: 0, blue: 0)) + } + + func testHSV60Degrees() { + let color = Color(hue: 60, saturation: 0xFF, value: 0xFF) + XCTAssertEqual(color, Color(red: 0xFF, green: 0xFF, blue: 0)) + } + + func testHSV120Degrees() { + let color = Color(hue: 120, saturation: 0xFF, value: 0xFF) + XCTAssertEqual(color, Color(red: 0, green: 0xFF, blue: 0)) + } + + func testHSV180Degrees() { + let color = Color(hue: 180, saturation: 0xFF, value: 0xFF) + XCTAssertEqual(color, Color(red: 0, green: 0xFF, blue: 0xFF)) + } + + func testHSV240Degrees() { + let color = Color(hue: 240, saturation: 0xFF, value: 0xFF) + XCTAssertEqual(color, Color(red: 0, green: 0, blue: 0xFF)) + } + + func testHSV300Degrees() { + let color = Color(hue: 300, saturation: 0xFF, value: 0xFF) + XCTAssertEqual(color, Color(red: 0xFF, green: 0, blue: 0xFF)) + } + + func testHSV360Degrees() { + let color = Color(hue: 360, saturation: 0xFF, value: 0xFF) + XCTAssertEqual(color, Color(red: 0xFF, green: 0, blue: 0)) + } +} From 75a3bc265dd07d9f7e0b721b4c3969dd4c4bfe7e Mon Sep 17 00:00:00 2001 From: Sven Weidauer Date: Fri, 1 Jan 2021 13:49:44 +0100 Subject: [PATCH 17/41] Parse hsv colors --- .../Model/Scanner+ColorParser.swift | 49 +++++++++++++++++++ Tests/MakeColorsTests/ColorParserTest.swift | 32 +++++++++++- 2 files changed, 80 insertions(+), 1 deletion(-) diff --git a/Sources/LibMakeColors/Model/Scanner+ColorParser.swift b/Sources/LibMakeColors/Model/Scanner+ColorParser.swift index 84957cd..c0d54d2 100644 --- a/Sources/LibMakeColors/Model/Scanner+ColorParser.swift +++ b/Sources/LibMakeColors/Model/Scanner+ColorParser.swift @@ -31,9 +31,58 @@ extension Scanner { return Color(white: arguments) } + if string("hsva") { + return readHSV(alpha: true) + } + + if string("hsv") { + return readHSV(alpha: false) + } + return nil } + private func readHSV(alpha readAlpha: Bool) -> Color? { + guard + string("("), + let hue = degrees(), + string(","), + let saturation = component(), + string(","), + let value = component() + else { return nil } + + let alpha: UInt8 + + if readAlpha { + guard + string(","), + let value = component() + else { return nil } + alpha = value + } else { + alpha = 0xFF + } + + guard string(")") else { return nil } + + return Color(hue: hue, saturation: saturation, value: value, alpha: alpha) + } + + func degrees() -> Int? { + guard let int = scanInt() else { return nil } + + if string("%") { + guard 0...100 ~= int else { return nil } + return (360 * int) / 100 + } else if string("°") || string("deg") { + return int + } else { + guard 0...0xFF ~= int else { return nil } + return (360 * int) / 0xFF + } + } + func colorReference() -> String? { guard string("@") else { return nil } return name() diff --git a/Tests/MakeColorsTests/ColorParserTest.swift b/Tests/MakeColorsTests/ColorParserTest.swift index edcb587..075c72d 100644 --- a/Tests/MakeColorsTests/ColorParserTest.swift +++ b/Tests/MakeColorsTests/ColorParserTest.swift @@ -86,8 +86,38 @@ final class ColorParserTest: XCTestCase { XCTAssertTrue(scanner.isAtEnd) } + func testScanningDegreesAsByte() throws { + let scanner = Scanner(string: "128") + XCTAssertEqual(scanner.degrees(), 180) + } + + func testScanningDegreesAsPercentage() throws { + let scanner = Scanner(string: "50%") + XCTAssertEqual(scanner.degrees(), 180) + } + + func testScanningDegrees() throws { + let scanner = Scanner(string: "120°") + XCTAssertEqual(scanner.degrees(), 120) + } + + func testScanningDegreesWithDegSuffix() throws { + let scanner = Scanner(string: "120 deg") + XCTAssertEqual(scanner.degrees(), 120) + } + + func testScanningHSVColor() throws { + XCTAssertEqual(scanColor("hsv(60°, 255, 100%)"), Color(red: 0xFF, green: 0xFF, blue: 0)) + } + + func testScanningHSVAColor() throws { + XCTAssertEqual(scanColor("hsva(60°, 50%, 255, 99)"), Color(red: 0xFF, green: 0xFF, blue: 128, alpha: 99)) + } + private func scanColor(_ input: String) -> Color? { let scanner = Scanner(string: input) - return scanner.color() + let result = scanner.color() + XCTAssertTrue(result == nil || scanner.isAtEnd) + return result } } From 04943d8f63b2f486a0f732d42c474c3bf3ac9884 Mon Sep 17 00:00:00 2001 From: Sven Weidauer Date: Fri, 1 Jan 2021 15:17:50 +0100 Subject: [PATCH 18/41] Use simplified argument list parser --- Sources/LibMakeColors/Model/Scanner+ColorParser.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/LibMakeColors/Model/Scanner+ColorParser.swift b/Sources/LibMakeColors/Model/Scanner+ColorParser.swift index c0d54d2..d114d4a 100644 --- a/Sources/LibMakeColors/Model/Scanner+ColorParser.swift +++ b/Sources/LibMakeColors/Model/Scanner+ColorParser.swift @@ -19,11 +19,11 @@ extension Scanner { } } - if string("rgba"), string("("), let components = commaSeparated(), components.count == 4, string(")") { + if string("rgba"), let components = argumentList(4) { return Color(components) } - if string("rgb"), string("("), let components = commaSeparated(), components.count == 3, string(")") { + if string("rgb"), let components = argumentList(3) { return Color(components) } From 8262760bc733275d9a95399e143f7f85250d4bee Mon Sep 17 00:00:00 2001 From: Sven Weidauer Date: Fri, 1 Jan 2021 15:18:16 +0100 Subject: [PATCH 19/41] Limit percent value from 0 to 100. --- Sources/LibMakeColors/Model/Scanner+ColorParser.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/LibMakeColors/Model/Scanner+ColorParser.swift b/Sources/LibMakeColors/Model/Scanner+ColorParser.swift index d114d4a..9870824 100644 --- a/Sources/LibMakeColors/Model/Scanner+ColorParser.swift +++ b/Sources/LibMakeColors/Model/Scanner+ColorParser.swift @@ -182,6 +182,7 @@ extension Scanner { } if string("%") { + guard 0...100 ~= int else { return nil } int = int * 0xFF / 100 } From be2a655c405fea4ffc4786b5bdb17f19f17f89e2 Mon Sep 17 00:00:00 2001 From: Sven Weidauer Date: Sat, 2 Jan 2021 21:17:41 +0100 Subject: [PATCH 20/41] Match formatting used by Xcode. Turns out Xcode requires two digits for hex colors values. Single digits get repeated. --- .../Generators/AssetCatalogGenerator.swift | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/Sources/LibMakeColors/Generators/AssetCatalogGenerator.swift b/Sources/LibMakeColors/Generators/AssetCatalogGenerator.swift index 964c6f5..9cc8698 100644 --- a/Sources/LibMakeColors/Generators/AssetCatalogGenerator.swift +++ b/Sources/LibMakeColors/Generators/AssetCatalogGenerator.swift @@ -67,10 +67,10 @@ private extension Color { "color" : { "color-space" : "srgb", "components" : { - "alpha" : "0x\(String(alpha, radix: 16))", - "blue" : "0x\(String(blue, radix: 16))", - "green" : "0x\(String(green, radix: 16))", - "red" : "0x\(String(red, radix: 16))" + "alpha" : "\(Double(alpha) / 0xFF)", + "blue" : "0x\(blue, radix: 16, width: 2)", + "green" : "0x\(green, radix: 16, width: 2)", + "red" : "0x\(red, radix: 16, width: 2)" } }, "idiom" : "universal" @@ -88,6 +88,22 @@ private extension Color { } } +extension String.StringInterpolation { + mutating func appendInterpolation( + _ value: I, + radix: Int, + width: Int = 0, + uppercase: Bool = true + ) { + var string = String(value, radix: radix, uppercase: uppercase) + if width > string.count { + string.insert(contentsOf: String(repeating: "0", count: width - string.count), at: string.startIndex) + } + + appendLiteral(string) + } +} + private let group = """ { "properties" : { From 1c31c11f49b5dbcde7b39eeaf098baa6d348c29a Mon Sep 17 00:00:00 2001 From: Sven Weidauer Date: Sat, 2 Jan 2021 22:03:41 +0100 Subject: [PATCH 21/41] Create unit test for color JSON --- Package.resolved | 9 +++++++ Package.swift | 6 ++++- .../Generators/AssetCatalogGenerator.swift | 2 +- .../AssetCatalogFormattingTest.swift | 26 +++++++++++++++++++ 4 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 Tests/MakeColorsTests/AssetCatalogFormattingTest.swift diff --git a/Package.resolved b/Package.resolved index 228d9c6..a0484d0 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,6 +1,15 @@ { "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", diff --git a/Package.swift b/Package.swift index 47180f3..d0b3184 100644 --- a/Package.swift +++ b/Package.swift @@ -10,6 +10,7 @@ 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")), ], targets: [ .target( @@ -26,7 +27,10 @@ let package = Package( ), .testTarget( name: "MakeColorsTests", - dependencies: ["LibMakeColors"] + dependencies: [ + "LibMakeColors", + .product(name: "RBBJSON", package: "RBBJSON"), + ] ), ] ) diff --git a/Sources/LibMakeColors/Generators/AssetCatalogGenerator.swift b/Sources/LibMakeColors/Generators/AssetCatalogGenerator.swift index 9cc8698..f21d153 100644 --- a/Sources/LibMakeColors/Generators/AssetCatalogGenerator.swift +++ b/Sources/LibMakeColors/Generators/AssetCatalogGenerator.swift @@ -58,7 +58,7 @@ private let infoTag = """ } """ -private extension Color { +extension Color { func json() -> String { """ { diff --git a/Tests/MakeColorsTests/AssetCatalogFormattingTest.swift b/Tests/MakeColorsTests/AssetCatalogFormattingTest.swift new file mode 100644 index 0000000..4c0e8e5 --- /dev/null +++ b/Tests/MakeColorsTests/AssetCatalogFormattingTest.swift @@ -0,0 +1,26 @@ +@testable import LibMakeColors +import RBBJSON +import XCTest + +class AssetCatalogFormattingTest: XCTestCase { + func testColorProducedValidJSON() throws { + let color = Color(red: 0xFF, green: 0xF0, blue: 0x0F) + let data = Data(color.json().utf8) + + let json = try JSONDecoder().decode(RBBJSON.self, from: data) + + XCTAssertEqual(json.info.author, .string("de.5sw.MakeColors")) + XCTAssertEqual(json.info.version, .number(1)) + + XCTAssertEqual(Array(json.colors[any: .child]).count, 1) + + XCTAssertEqual(json.colors[0].idiom, .string("universal")) + + let jsonColor = json.colors[0].color + XCTAssertEqual(jsonColor["color-space"], .string("srgb")) + XCTAssertEqual(jsonColor.components.alpha, .string("1.0")) + XCTAssertEqual(jsonColor.components.red, .string("0xFF")) + XCTAssertEqual(jsonColor.components.green, .string("0xF0")) + XCTAssertEqual(jsonColor.components.blue, .string("0x0F")) + } +} From 404566c36b6487c7b21bc60cbacab37f2f7d140d Mon Sep 17 00:00:00 2001 From: Sven Weidauer Date: Sat, 2 Jan 2021 22:20:55 +0100 Subject: [PATCH 22/41] Add example file --- Example/.gitignore | 2 ++ Example/Example.txt | 10 ++++++++++ 2 files changed, 12 insertions(+) create mode 100644 Example/.gitignore create mode 100644 Example/Example.txt diff --git a/Example/.gitignore b/Example/.gitignore new file mode 100644 index 0000000..5d1cee6 --- /dev/null +++ b/Example/.gitignore @@ -0,0 +1,2 @@ +Example.* +!Example.txt diff --git a/Example/Example.txt b/Example/Example.txt new file mode 100644 index 0000000..c2be7ac --- /dev/null +++ b/Example/Example.txt @@ -0,0 +1,10 @@ +Base/Green #8fd151 +Base/PaleGreen #d0f9a9 +Base/Red rgb(249, 39, 7) +TransparentRed rgba(255, 0, 0, 128) +Base/Yellow #ff0 + +Error @Base/Red +Warning @Base/Yellow +Good @Base/Green + From 78fcdef196248eca16615bd5becfd663ba764500 Mon Sep 17 00:00:00 2001 From: Sven Weidauer Date: Sat, 2 Jan 2021 22:22:35 +0100 Subject: [PATCH 23/41] Update asset catalog image --- Docs/assetcatalog.png | Bin 14349 -> 14267 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/Docs/assetcatalog.png b/Docs/assetcatalog.png index a5107eabdabd9d7df29645dee2ff079340d15ed7..0340a76f419a3ea641b0d174f875c430548e0b35 100644 GIT binary patch delta 13926 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 Date: Sat, 2 Jan 2021 22:33:52 +0100 Subject: [PATCH 24/41] Add info on HSV colors to README --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index afa53a7..3e4a710 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,7 @@ Base/Red rgb(249, 39, 7) TransparentRed rgba(255, 0, 0, 128) Base/Yellow #ff0 ``` + Grayscale colors can be produced with the `white(value)` and `white(value, alpha)` syntax. A value of zero means black while a value of 255 is pure white. ``` @@ -53,6 +54,12 @@ MediumGray white(128) TransparentGray white(128, 128) ``` +HSV colors can be specified as `hsv(hue, saturation, value)` or `hsva(hue, saturation, value, alpha)` syntax. Hue is specified as degrees with the `°` or `deg` suffix. + +``` +HSV/Yellow hsv(60°, 255, 255) +``` + Colors can also reference other colors by prefixing them with an `@` sign: ``` From ba7c0c04fa3fec4144a0de4393371f4bc758a3b1 Mon Sep 17 00:00:00 2001 From: Sven Date: Sat, 2 Jan 2021 22:35:45 +0100 Subject: [PATCH 25/41] Update README.md --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 3e4a710..b9b8980 100644 --- a/README.md +++ b/README.md @@ -109,7 +109,6 @@ The generated HTML looks like this: ## Future work -- Support other color formats (HSV, ...) - Calculate derived colors (blend, change hue/saturation/brightness/alpha) - Support for dark/light mode - Improved error reporting in the parser From e6f44f5be27498dea59e241eff3cfbd6bba90fff Mon Sep 17 00:00:00 2001 From: Sven Weidauer Date: Sat, 2 Jan 2021 22:43:26 +0100 Subject: [PATCH 26/41] Fix lint warnings --- Sources/LibMakeColors/Model/Color+HSV.swift | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/Sources/LibMakeColors/Model/Color+HSV.swift b/Sources/LibMakeColors/Model/Color+HSV.swift index 8b9383f..9251c03 100644 --- a/Sources/LibMakeColors/Model/Color+HSV.swift +++ b/Sources/LibMakeColors/Model/Color+HSV.swift @@ -2,26 +2,35 @@ extension Color { init(hue: Int, saturation: UInt8, value: UInt8, alpha: UInt8 = 0xFF) { let degrees = abs(hue % 360) - let s = Double(saturation) / 0xFF - let v = Double(value) / 0xFF - let C = s * v + let saturation = Double(saturation) / 0xFF + let value = Double(value) / 0xFF + + // swiftlint:disable identifier_name - Wish I knew what these actually mean. + let C = saturation * value let X = C * (1 - abs((Double(degrees) / 60).truncatingRemainder(dividingBy: 2) - 1)) - let m = v - C + let m = value - C + // swiftlint:enable identifier_name let result: (r: Double, g: Double, b: Double) switch degrees { case 0..<60: result = (C, X, 0) + case 60..<120: result = (X, C, 0) + case 120..<180: result = (0, C, X) + case 180..<240: result = (0, X, C) + case 240..<300: result = (X, 0, C) + case 300..<360: result = (C, 0, X) + default: fatalError("Degrees out of range") } From e28bee804f091399c94b0e15320e325924e3f995 Mon Sep 17 00:00:00 2001 From: Sven Weidauer Date: Sat, 8 Oct 2022 11:03:28 +0200 Subject: [PATCH 27/41] 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 28/41] 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 29/41] 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 30/41] 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 31/41] 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 32/41] 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 33/41] 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 34/41] 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 35/41] 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 36/41] 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 37/41] 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 38/41] 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 39/41] 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 40/41] 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 41/41] 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