Publish v1.0
Some checks failed
gitea-openium/resgen.swift/pipeline/head There was a failure building this commit

Reviewed-on: #1
This commit is contained in:
2022-10-17 11:24:27 +02:00
parent a99466f258
commit 6203700b0c
87 changed files with 3112 additions and 1223 deletions

View File

@ -0,0 +1,110 @@
//
// main.swift
//
//
// Created by Thibaut Schmitt on 20/12/2021.
//
import ToolCore
import Foundation
import ArgumentParser
struct Colors: ParsableCommand {
// MARK: - CommandConfiguration
static var configuration = CommandConfiguration(
abstract: "A utility for generate colors assets and their getters.",
version: ResgenSwiftVersion
)
// MARK: - Static
static let toolName = "Color"
static let defaultExtensionName = "UIColor"
static let assetsColorsFolderName = "Colors"
// MARK: - Command options
@OptionGroup var options: ColorsToolOptions
// MARK: - Run
public func run() throws {
print("[\(Self.toolName)] Starting colors generation")
// Check requirements
guard checkRequirements() else { return }
print("[\(Self.toolName)] Will generate colors")
// Delete current colors
deleteCurrentColors()
// Get colors to generate
let parsedColors = ColorFileParser.parse(options.inputFile,
colorStyle: options.colorStyle)
// -> Time: 0.0020350217819213867 seconds
// Generate all colors in xcassets
ColorXcassetHelper.generateXcassetColors(colors: parsedColors,
to: options.xcassetsPath)
// -> Time: 3.4505380392074585 seconds
// Generate extension
ColorExtensionGenerator.writeExtensionFile(colors: parsedColors,
staticVar: options.staticMembers,
extensionName: options.extensionName,
extensionFilePath: options.extensionFilePath)
// -> Time: 0.0010340213775634766 seconds
print("[\(Self.toolName)] Colors generated")
}
// MARK: - Requirements
private func checkRequirements() -> Bool {
let fileManager = FileManager()
// Check if input file exists
guard fileManager.fileExists(atPath: options.inputFile) else {
let error = ColorsToolError.fileNotExists(options.inputFile)
print(error.localizedDescription)
Colors.exit(withError: error)
}
// Check if xcassets file exists
guard fileManager.fileExists(atPath: options.xcassetsPath) else {
let error = ColorsToolError.fileNotExists(options.xcassetsPath)
print(error.localizedDescription)
Colors.exit(withError: error)
}
// Check if needed to regenerate
guard GeneratorChecker.shouldGenerate(force: options.forceGeneration,
inputFilePath: options.inputFile,
extensionFilePath: options.extensionFilePath) else {
print("[\(Self.toolName)] Colors are already up to date :) ")
return false
}
return true
}
// MARK: - Helpers
private func deleteCurrentColors() {
let fileManager = FileManager()
let assetsColorPath = "\(options.xcassetsPath)/Colors"
if fileManager.fileExists(atPath: assetsColorPath) {
do {
try fileManager.removeItem(atPath: assetsColorPath)
} catch {
let error = ColorsToolError.deleteExistingColors("\(options.xcassetsPath)/Colors")
print(error.localizedDescription)
Colors.exit(withError: error)
}
}
}
}

View File

@ -0,0 +1,43 @@
//
// ColorsToolError.swift
//
//
// Created by Thibaut Schmitt on 20/12/2021.
//
import Foundation
enum ColorsToolError: Error {
case badFormat(String)
case writeAsset(String)
case createAssetFolder(String)
case writeExtension(String, String)
case fileNotExists(String)
case badColorDefinition(String, String)
case deleteExistingColors(String)
var description: String {
switch self {
case .badFormat(let info):
return "error:[\(Colors.toolName)] Bad line format: \(info). Accepted format are: colorName=\"#RGB/#ARGB\"; colorName \"#RGB/#ARGB\"; colorName \"#RGB/#ARGB\" \"#RGB/#ARGB\""
case .writeAsset(let info):
return "error:[\(Colors.toolName)] An error occured while writing color in Xcasset: \(info)"
case .createAssetFolder(let assetsFolder):
return "error:[\(Colors.toolName)] An error occured while creating colors folder `\(assetsFolder)`"
case .writeExtension(let filename, let info):
return "error:[\(Colors.toolName)] An error occured while writing extension in \(filename): \(info)"
case .fileNotExists(let filename):
return "error:[\(Colors.toolName)] File \(filename) does not exists"
case .badColorDefinition(let lightColor, let darkColor):
return "error:[\(Colors.toolName)] One of these two colors has invalid synthax: -\(lightColor)- or -\(darkColor)-"
case .deleteExistingColors(let assetsFolder):
return "error:[\(Colors.toolName)] An error occured while deleting colors folder `\(assetsFolder)`"
}
}
}

View File

@ -0,0 +1,54 @@
//
// ColorsToolOptions.swift
//
//
// Created by Thibaut Schmitt on 17/01/2022.
//
import Foundation
import ArgumentParser
struct ColorsToolOptions: ParsableArguments {
@Flag(name: [.customShort("f"), .customShort("F")], help: "Should force generation")
var forceGeneration = false
@Argument(help: "Input files where colors ared defined.", transform: { $0.replaceTiltWithHomeDirectoryPath() })
var inputFile: String
@Option(help: "Color style to generate: light for light colors only, or all for dark and light colors")
fileprivate var style: String
@Option(help: "Path of xcassets where to generate colors", transform: { $0.replaceTiltWithHomeDirectoryPath() })
var xcassetsPath: String
@Option(help: "Path where to generate the extension.", transform: { $0.replaceTiltWithHomeDirectoryPath() })
var extensionOutputPath: String
@Option(help: "Tell if it will generate static properties or not")
var staticMembers: Bool = false
@Option(help: "Extension name. If not specified, it will generate an UIColor extension. Using default extension name will generate static property.")
var extensionName: String = Colors.defaultExtensionName
@Option(help: "Extension suffix. Ex: MyApp, it will generate {extensionName}+ColorsMyApp.swift")
var extensionSuffix: String?
}
// MARK: - Computed var
extension ColorsToolOptions {
var colorStyle: ColorStyle {
ColorStyle(rawValue: style) ?? .all
}
var extensionFileName: String {
if let extensionSuffix = extensionSuffix {
return "\(extensionName)+\(extensionSuffix).swift"
}
return "\(extensionName).swift"
}
var extensionFilePath: String {
"\(extensionOutputPath)/\(extensionFileName)"
}
}

View File

@ -0,0 +1,67 @@
//
// ColorExtensionGenerator.swift
//
//
// Created by Thibaut Schmitt on 20/12/2021.
//
import Foundation
import ToolCore
struct ColorExtensionGenerator {
let colors: [ParsedColor]
let extensionClassname: String
static func writeExtensionFile(colors: [ParsedColor], staticVar: Bool, extensionName: String, extensionFilePath: String) {
// Create extension content
let extensionContent = Self.getExtensionContent(colors: colors,
staticVar: staticVar,
extensionName: extensionName)
// Write content
let extensionFilePathURL = URL(fileURLWithPath: extensionFilePath)
do {
try extensionContent.write(to: extensionFilePathURL, atomically: false, encoding: .utf8)
} catch (let error) {
let error = ColorsToolError.writeExtension(extensionFilePath, error.localizedDescription)
print(error.localizedDescription)
Colors.exit(withError: error)
}
}
static func getExtensionContent(colors: [ParsedColor], staticVar: Bool, extensionName: String) -> String {
[
Self.getHeader(extensionClassname: extensionName),
Self.getProperties(for: colors, withStaticVar: staticVar),
Self.getFooter()
]
.joined(separator: "\n")
}
private static func getHeader(extensionClassname: String) -> String {
"""
// Generated by ResgenSwift.\(Colors.toolName) \(ResgenSwiftVersion)
import UIKit
extension \(extensionClassname) {\n
"""
}
private static func getFooter() -> String {
"""
}
"""
}
private static func getProperties(for colors: [ParsedColor], withStaticVar staticVar: Bool) -> String {
colors.map {
if staticVar {
return $0.getColorStaticProperty()
}
return $0.getColorProperty()
}
.joined(separator: "\n\n")
}
}

View File

@ -0,0 +1,47 @@
//
// ColorXcassetHelper.swift
//
//
// Created by Thibaut Schmitt on 20/12/2021.
//
import Foundation
import ToolCore
struct ColorXcassetHelper {
static func generateXcassetColors(colors: [ParsedColor], to xcassetsPath: String) {
colors.forEach {
Self.generateColorSetAssets(from: $0, to: xcassetsPath)
}
}
// Generate ColorSet in XCAssets file
private static func generateColorSetAssets(from color: ParsedColor, to xcassetsPath: String) {
// Create ColorSet
let colorSetPath = "\(xcassetsPath)/Colors/\(color.name).colorset"
let contentsJsonPath = "\(colorSetPath)/Contents.json"
let fileManager = FileManager()
if fileManager.fileExists(atPath: colorSetPath) == false {
do {
try fileManager.createDirectory(atPath: colorSetPath,
withIntermediateDirectories: true)
} catch {
let error = ColorsToolError.createAssetFolder(colorSetPath)
print(error.localizedDescription)
Colors.exit(withError: error)
}
}
// Write content in Contents.json
let contentsJsonPathURL = URL(fileURLWithPath: contentsJsonPath)
do {
try color.contentsJSON().write(to: contentsJsonPathURL, atomically: false, encoding: .utf8)
} catch (let error) {
let error = ColorsToolError.writeAsset(error.localizedDescription)
print(error.localizedDescription)
Colors.exit(withError: error)
}
}
}

View File

@ -0,0 +1,13 @@
//
// ColorStyle.swift
//
//
// Created by Thibaut Schmitt on 29/08/2022.
//
import Foundation
enum ColorStyle: String, Decodable {
case light
case all
}

View File

@ -0,0 +1,92 @@
//
// ParsedColor.swift
//
//
// Created by Thibaut Schmitt on 20/12/2021.
//
import Foundation
struct ParsedColor {
let name: String
let light: String
let dark: String
// Generate Contents.json content
func contentsJSON() -> String {
let lightARGB = light.colorComponent()
let darkARGB = dark.colorComponent()
let allComponents = [
lightARGB.alpha, lightARGB.red, lightARGB.green, lightARGB.blue,
darkARGB.alpha, darkARGB.red, darkARGB.green, darkARGB.blue
].map {
$0.isEmpty
}
guard allComponents.contains(true) == false else {
let error = ColorsToolError.badColorDefinition(light, dark)
print(error.localizedDescription)
Colors.exit(withError: error)
}
return """
{
"colors": [
{
"color": {
"color-space": "srgb",
"components": {
"alpha": "0x\(lightARGB.alpha)",
"blue": "0x\(lightARGB.blue)",
"green": "0x\(lightARGB.green)",
"red": "0x\(lightARGB.red)",
}
},
"idiom": "universal"
},
{
"appearances": [
{
"appearance": "luminosity",
"value": "dark"
}
],
"color": {
"color-space": "srgb",
"components": {
"alpha": "0x\(darkARGB.alpha)",
"blue": "0x\(darkARGB.blue)",
"green": "0x\(darkARGB.green)",
"red": "0x\(darkARGB.red)",
}
},
"idiom": "universal"
}
],
"info": {
"author": "xcode",
"version": 1
}
}
"""
}
func getColorProperty() -> String {
"""
/// Color \(name) is \(light) (light) or \(dark) (dark)"
@objc var \(name): UIColor {
UIColor(named: "\(name)")!
}
"""
}
func getColorStaticProperty() -> String {
"""
/// Color \(name) is \(light) (light) or \(dark) (dark)"
static var \(name): UIColor {
UIColor(named: "\(name)")!
}
"""
}
}

View File

@ -0,0 +1,56 @@
//
// ColorFileParser.swift
//
//
// Created by Thibaut Schmitt on 29/08/2022.
//
import Foundation
class ColorFileParser {
static func parse(_ inputFile: String, colorStyle: ColorStyle) -> [ParsedColor] {
// Get content of input file
let inputFileContent = try! String(contentsOfFile: inputFile, encoding: .utf8)
let colorsByLines = inputFileContent.components(separatedBy: CharacterSet.newlines)
// Iterate on each line of input file
return parseLines(lines: colorsByLines, colorStyle: colorStyle)
}
static func parseLines(lines: [String], colorStyle: ColorStyle) -> [ParsedColor] {
lines
.enumerated()
.compactMap { lineNumber, colorLine in
// Required format:
// colorName = "#RGB/#ARGB", colorName "#RGB/#ARGB", colorName "#RGB/#ARGB" "#RGB/#ARGB"
let colorLineCleanedUp = colorLine
.removeLeadingWhitespace()
.removeTrailingWhitespace()
.replacingOccurrences(of: "=", with: "") // Keep compat with current file format
guard colorLineCleanedUp.hasPrefix("#") == false, colorLineCleanedUp.isEmpty == false else {
// debugPrint("[\(Colors.toolName)] 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 = ColorsToolError.badFormat(colorLine)
print(error.localizedDescription)
Colors.exit(withError: error)
}
switch colorStyle {
case .light:
return ParsedColor(name: String(colorContent[0]), light: String(colorContent[1]), dark: String(colorContent[1]))
case .all:
if colorContent.count == 3 {
return ParsedColor(name: String(colorContent[0]), light: String(colorContent[1]), dark: String(colorContent[2]))
}
return ParsedColor(name: String(colorContent[0]), light: String(colorContent[1]), dark: String(colorContent[1]))
}
}
}
}