// // main.swift // // // Created by Thibaut Schmitt on 20/12/2021. // import Foundation import ToolCore import ArgumentParser enum ColorStyle: String, Decodable { case light case all } struct ColorTool: ParsableCommand { static let defaultExtensionName = "UIColor" static let assetsColorsFolderName = "Colors" @OptionGroup var options: ColorToolOptions var colorStyle: ColorStyle { ColorStyle(rawValue: options.style) ?? .all } var extensionFileName: String { "\(options.extensionName)+\(options.extensionSuffix).swift" } var extensionFilePath: String { "\(options.extensionOutputPath)/\(extensionFileName)" } public func run() throws { print("[ColorTool] Starting colors generation") print("[ColorTool] Will use inputFile \(options.inputFile) to generate \(colorStyle) colors in xcassets \(options.xcassetsPath)") print("[ColorTool] Extension will be \(extensionFilePath)") // Check requirements let fileManager = FileManager() guard fileManager.fileExists(atPath: options.xcassetsPath) else { let error = ColorToolError.fileNotExists(options.xcassetsPath) print(error.localizedDescription) ColorTool.exit(withError: error) } guard fileManager.fileExists(atPath: options.inputFile) else { let error = ColorToolError.fileNotExists(options.inputFile) print(error.localizedDescription) ColorTool.exit(withError: error) } // Check if needed to regenerate guard GeneratorChecker.shouldGenerate(force: options.forceGeneration, inputFilePath: options.inputFile, extensionFilePath: extensionFilePath) else { print("[ColorTool] Colors are already up to date :) ") return } print("[ColorTool] Will generate colors") // Delete current colors Shell.shell("rm", "-rf", "\(options.xcassetsPath)/Colors/*") // Get colors let colorsToGen = getColorsGen() // Generate all colors in xcassets let colorAssetHelper = ColorXcassetHelper(xcassetsPath: options.xcassetsPath, colors: colorsToGen) colorAssetHelper.generateXcassetColors() // Generate extension let extensionGenerator = ColorExtensionGenerator(colors: colorsToGen, extensionClassname: options.extensionName, isUIColorExtension: isUIColorExtension()) let extensionHeader = extensionGenerator.getHeader() let extensionProperties = extensionGenerator.getProperties() let extensionFooter = extensionGenerator.getFooter() generateExtensionFile(extensionHeader, extensionProperties, extensionFooter) print("[ColorTool] Colors generated") } private func generateExtensionFile(_ args: String...) { // Create file if not exists let fileManager = FileManager() if fileManager.fileExists(atPath: extensionFilePath) == false { Shell.shell("touch", "\(extensionFilePath)") } // Create extension content let extensionContent = args.joined(separator: "\n") // Write content let extensionFilePathURL = URL(fileURLWithPath: extensionFilePath) do { try extensionContent.write(to: extensionFilePathURL, atomically: true, encoding: .utf8) } catch (let error) { let error = ColorToolError.writeExtension(extensionFilePath, error.localizedDescription) print(error.localizedDescription) ColorTool.exit(withError: error) } } private func getColorsGen() -> [GenColor] { // Get content of input file let inputFileContent = try! String(contentsOfFile: options.inputFile, encoding: .utf8) let colorsByLines = inputFileContent.components(separatedBy: CharacterSet.newlines) // Iterate on each line of input file return colorsByLines.enumerated().compactMap { lineNumber, colorLine in // Required format: // colorName="#RGB/#ARGB", colorName "#RGB/#ARGB", colorName "#RGB/#ARGB" "#RGB/#ARGB" let colorLineCleanedUp = colorLine .removeTrailingWhitespace() .replacingOccurrences(of: "=", with: "") // Keep compat with current file format guard colorLineCleanedUp.hasPrefix("#") == false, colorLineCleanedUp.isEmpty == false else { print("[ColorTool] ⚠️ BadFormat or empty line (line number: \(lineNumber + 1)). Skip this line") return nil } let colorContent = colorLineCleanedUp.split(separator: " ") guard colorContent.count >= 2 else { let error = ColorToolError.badFormat(colorLine) print(error.localizedDescription) ColorTool.exit(withError: error) } switch colorStyle { case .light: return GenColor(name: String(colorContent[0]), light: String(colorContent[1]), dark: String(colorContent[1])) case .all: if colorContent.count == 3 { return GenColor(name: String(colorContent[0]), light: String(colorContent[1]), dark: String(colorContent[2])) } return GenColor(name: String(colorContent[0]), light: String(colorContent[1]), dark: String(colorContent[1])) } } } // MARK: - Helpers private func isUIColorExtension() -> Bool { options.extensionName == Self.defaultExtensionName } } ColorTool.main() /* Command samples: 1. UIColor extension without suffix swift run -c release ColorToolCore -f ./SampleFiles/Colors/sampleColors1.txt --style all --xcassets-path "./SampleFiles/Colors/colors.xcassets" --extension-output-path "./SampleFiles/Colors/Generated/" --extension-name "UIColor" 2. UIColor extension with custom suffix swift run -c release ColorToolCore -f ./SampleFiles/Colors/sampleColors1.txt --style all --xcassets-path "./SampleFiles/Colors/colors.xcassets" --extension-output-path "./SampleFiles/Colors/Generated/" --extension-name "UIColor" --extension-suffix "SampleApp" 3. Custom extension with only light theme colors (R2Color) swift run -c release ColorToolCore -f ./SampleFiles/Colors/sampleColors1.txt --style light --xcassets-path "./SampleFiles/Colors/colors.xcassets" --extension-output-path "./SampleFiles/Colors/Generated/" --extension-name "R2Color" */