157 lines
6.6 KiB
Swift

//
// main.swift
//
//
// Created by Thibaut Schmitt on 20/12/2021.
//
import Foundation
import CLIToolCore
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"
*/