Refactor in one unique command with subcommand + add a new command to generate ressources from a configuration file
Some checks failed
gitea-openium/resgen.swift/pipeline/head There was a failure building this commit

This commit is contained in:
2022-08-30 17:02:11 +02:00
parent a99466f258
commit 264c221604
60 changed files with 825 additions and 235 deletions

View File

@ -160,6 +160,20 @@
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "ColorTool"
BuildableName = "ColorTool"
BlueprintName = "ColorTool"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction

View File

@ -34,6 +34,34 @@
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "ToolCore"
BuildableName = "ToolCore"
BlueprintName = "ToolCore"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "ResgenSwift_ResgenSwift"
BuildableName = "ResgenSwift_ResgenSwift"
BlueprintName = "ResgenSwift_ResgenSwift"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction

View File

@ -9,6 +9,15 @@
"revision": "e1465042f195f374b94f915ba8ca49de24300a0d",
"version": "1.0.2"
}
},
{
"package": "Yams",
"repositoryURL": "https://github.com/jpsim/Yams.git",
"state": {
"branch": null,
"revision": "01835dc202670b5bb90d07f3eae41867e9ed29f6",
"version": "5.0.1"
}
}
]
},

View File

@ -1,56 +1,67 @@
// swift-tools-version:5.3
// swift-tools-version:5.6
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "ResgenSwift",
platforms: [.macOS(.v10_12)],
platforms: [.macOS(.v12)],
dependencies: [
// Dependencies declare other packages that this package depends on.
.package(url: "https://github.com/apple/swift-argument-parser", from: "1.0.0")
.package(url: "https://github.com/apple/swift-argument-parser", from: "1.0.0"),
.package(url: "https://github.com/jpsim/Yams.git", from: "5.0.1")
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
.executableTarget(
name: "ResgenSwift",
dependencies: ["FontTool", "ColorTool", "Strings", "Imagium"]
),
.target(
name: "FontTool",
dependencies: [
"ToolCore",
.product(name: "ArgumentParser", package: "swift-argument-parser")
]
),
.target(
name: "ColorTool",
dependencies: [
"ToolCore",
.product(name: "ArgumentParser", package: "swift-argument-parser")
]
),
.target(
name: "Strings",
dependencies: [
"ToolCore",
.product(name: "ArgumentParser", package: "swift-argument-parser")
],
sources: ["."] // Force include all subdirectories
),
.target(
name: "Imagium",
dependencies: [
"ToolCore",
.product(name: "ArgumentParser", package: "swift-argument-parser")
.product(name: "ArgumentParser", package: "swift-argument-parser"),
"Yams"
]
),
// .executableTarget(
// name: "FontTool",
// dependencies: [
// "ToolCore",
// .product(name: "ArgumentParser", package: "swift-argument-parser")
// ]
// ),
//
// .executableTarget(
// name: "ColorTool",
// dependencies: [
// "ToolCore",
// .product(name: "ArgumentParser", package: "swift-argument-parser")
// ]
// ),
//
// .executableTarget(
// name: "Strings",
// dependencies: [
// "ToolCore",
// .product(name: "ArgumentParser", package: "swift-argument-parser")
// ],
// sources: ["."] // Force include all subdirectories
// ),
//
// .executableTarget(
// name: "Imagium",
// dependencies: [
// "ToolCore",
// .product(name: "ArgumentParser", package: "swift-argument-parser")
// ]
// ),
// Helper targets
.target(name: "ToolCore"),
// Test targets
.testTarget(
name: "ResgenSwiftTests",
dependencies: ["ResgenSwift"]),
// .testTarget(
// name: "ResgenSwiftTests",
// dependencies: ["ResgenSwift"]),
]
)

View File

@ -121,7 +121,7 @@ swift run -c release Strings tags $FORCE_FLAG "./Tags/tags.txt" \
5. `--extension-name` *(optional)* : name of class to add the extension
6. `--extension-suffix` *(optional)* : additional text which is added to filename (ex: `AppTags+GreatApp.swift`)
> ⚠️ If extension name is not set or is `Tags`, it will generate static property on `Tags`. This class may not exists in your project, just create an empty class named `Tags` is necessary.
> ⚠️ If extension name is not set or is `Tags`, it will generate static property on `Tags`. `Tags` is a typealias of `String`.
# Images

View File

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

View File

@ -1,43 +0,0 @@
//
// ImagiumError.swift
//
//
// Created by Thibaut Schmitt on 24/01/2022.
//
import Foundation
enum ImagiumError: Error {
case inputFolderNotFound(String)
case fileNotExists(String)
case unknownImageExtension(String)
case getFileAttributed(String, String)
case rsvgConvertNotFound
case writeFile(String, String)
case unknown(String)
var localizedDescription: String {
switch self {
case .inputFolderNotFound(let inputFolder):
return " error:[\(Imagium.toolName)] Input folder not found: \(inputFolder)"
case .fileNotExists(let filename):
return " error:[\(Imagium.toolName)] File \(filename) does not exists"
case .unknownImageExtension(let filename):
return " error:[\(Imagium.toolName)] File \(filename) have an unhandled file extension. Cannot generate image."
case .getFileAttributed(let filename, let errorDescription):
return " error:[\(Imagium.toolName)] Getting file attributes of \(filename) failed with error: \(errorDescription)"
case .rsvgConvertNotFound:
return " error:[\(Imagium.toolName)] Can't find rsvg-convert (can be installed with 'brew remove imagemagick && brew install imagemagick --with-librsvg')"
case .writeFile(let subErrorDescription, let filename):
return " error:[\(Imagium.toolName)] An error occured while writing content to \(filename): \(subErrorDescription)"
case .unknown(let errorDescription):
return " error:[\(Imagium.toolName)] Unknown error: \(errorDescription)"
}
}
}

View File

@ -9,7 +9,7 @@ import ToolCore
import Foundation
import ArgumentParser
struct ColorTool: ParsableCommand {
struct Colors: ParsableCommand {
// MARK: - CommandConfiguration
@ -20,7 +20,7 @@ struct ColorTool: ParsableCommand {
// MARK: - Static
static let toolName = "ColorTool"
static let toolName = "Color"
static let defaultExtensionName = "UIColor"
static let assetsColorsFolderName = "Colors"
@ -39,7 +39,7 @@ struct ColorTool: ParsableCommand {
// MARK: - Command options
@OptionGroup var options: ColorToolOptions
@OptionGroup var options: ColorsToolOptions
// MARK: - Run
@ -79,16 +79,16 @@ struct ColorTool: ParsableCommand {
// Check if input file exists
guard fileManager.fileExists(atPath: options.inputFile) else {
let error = ColorToolError.fileNotExists(options.inputFile)
let error = ColorsToolError.fileNotExists(options.inputFile)
print(error.localizedDescription)
ColorTool.exit(withError: error)
Colors.exit(withError: error)
}
// Check if xcassets file exists
guard fileManager.fileExists(atPath: options.xcassetsPath) else {
let error = ColorToolError.fileNotExists(options.xcassetsPath)
let error = ColorsToolError.fileNotExists(options.xcassetsPath)
print(error.localizedDescription)
ColorTool.exit(withError: error)
Colors.exit(withError: error)
}
// Check if needed to regenerate
@ -107,8 +107,6 @@ struct ColorTool: ParsableCommand {
}
}
ColorTool.main()
/*
Command samples:

View File

@ -0,0 +1,35 @@
//
// ColorsToolError.swift
//
//
// Created by Thibaut Schmitt on 20/12/2021.
//
import Foundation
enum ColorsToolError: Error {
case badFormat(String)
case writeAsset(String)
case writeExtension(String, String)
case fileNotExists(String)
case badColorDefinition(String, 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 .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)-"
}
}
}

View File

@ -1,5 +1,5 @@
//
// ColorToolOptions.swift
// ColorsToolOptions.swift
//
//
// Created by Thibaut Schmitt on 17/01/2022.
@ -8,7 +8,7 @@
import Foundation
import ArgumentParser
struct ColorToolOptions: ParsableArguments {
struct ColorsToolOptions: ParsableArguments {
@Flag(name: [.customShort("f"), .customShort("F")], help: "Should force generation")
var forceGeneration = false
@ -25,13 +25,13 @@ struct ColorToolOptions: ParsableArguments {
var extensionOutputPath: String
@Option(help: "Extension name. If not specified, it will generate an UIColor extension. Using default extension name will generate static property.")
var extensionName: String = ColorTool.defaultExtensionName
var extensionName: String = Colors.defaultExtensionName
@Option(help: "Extension suffix. Ex: MyApp, it will generate {extensionName}+ColorsMyApp.swift")
var extensionSuffix: String = ""
}
extension ColorToolOptions {
extension ColorsToolOptions {
var colorStyle: ColorStyle {
ColorStyle(rawValue: style) ?? .all
}

View File

@ -33,15 +33,15 @@ struct ColorExtensionGenerator {
do {
try extensionContent.write(to: extensionFilePathURL, atomically: true, encoding: .utf8)
} catch (let error) {
let error = ColorToolError.writeExtension(extensionFilePath, error.localizedDescription)
let error = ColorsToolError.writeExtension(extensionFilePath, error.localizedDescription)
print(error.localizedDescription)
ColorTool.exit(withError: error)
Colors.exit(withError: error)
}
}
private static func getHeader(extensionClassname: String) -> String {
"""
// Generated by ResgenSwift.\(ColorTool.toolName) \(ResgenSwiftVersion)
// Generated by ResgenSwift.\(Colors.toolName) \(ResgenSwiftVersion)
import UIKit

View File

@ -31,9 +31,9 @@ struct ColorXcassetHelper {
do {
try color.contentsJSON().write(to: contentsJsonPathURL, atomically: true, encoding: .utf8)
} catch (let error) {
let error = ColorToolError.writeAsset(error.localizedDescription)
let error = ColorsToolError.writeAsset(error.localizedDescription)
print(error.localizedDescription)
ColorTool.exit(withError: error)
Colors.exit(withError: error)
}
}
}

View File

@ -1,5 +1,5 @@
//
// File.swift
// ColorStyle.swift
//
//
// Created by Thibaut Schmitt on 29/08/2022.

View File

@ -25,9 +25,9 @@ struct ParsedColor {
}
guard allComponents.contains(true) == false else {
let error = ColorToolError.badColorDefinition(light, dark)
let error = ColorsToolError.badColorDefinition(light, dark)
print(error.localizedDescription)
ColorTool.exit(withError: error)
Colors.exit(withError: error)
}
return """

View File

@ -1,5 +1,5 @@
//
// File.swift
// ColorFileParser.swift
//
//
// Created by Thibaut Schmitt on 29/08/2022.
@ -22,16 +22,16 @@ class ColorFileParser {
.replacingOccurrences(of: "=", with: "") // Keep compat with current file format
guard colorLineCleanedUp.hasPrefix("#") == false, colorLineCleanedUp.isEmpty == false else {
print("[\(ColorTool.toolName)] ⚠️ BadFormat or empty line (line number: \(lineNumber + 1)). Skip this line")
print("[\(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 = ColorToolError.badFormat(colorLine)
let error = ColorsToolError.badFormat(colorLine)
print(error.localizedDescription)
ColorTool.exit(withError: error)
Colors.exit(withError: error)
}
switch colorStyle {

View File

@ -1,5 +1,5 @@
//
// FontOptions.swift
// FontsOptions.swift
//
//
// Created by Thibaut Schmitt on 17/01/2022.
@ -8,7 +8,7 @@
import Foundation
import ArgumentParser
struct FontOptions: ParsableArguments {
struct FontsOptions: ParsableArguments {
@Flag(name: [.customShort("f"), .customShort("F")], help: "Should force generation")
var forceGeneration = false
@ -19,8 +19,25 @@ struct FontOptions: ParsableArguments {
var extensionOutputPath: String
@Option(help: "Extension name. If not specified, it will generate an UIFont extension. Using default extension name will generate static property.")
var extensionName: String = FontTool.defaultExtensionName
var extensionName: String = Fonts.defaultExtensionName
@Option(help: "Extension suffix. Ex: MyApp, it will generate {extensionName}+FontsMyApp.swift")
var extensionSuffix: String = ""
}
extension FontsOptions {
var extensionFileName: String {
if extensionSuffix.isEmpty == false {
return "\(extensionName)+\(extensionSuffix).swift"
}
return "\(extensionName).swift"
}
var extensionFilePath: String {
"\(extensionOutputPath)/\(extensionFileName)"
}
var generateStaticVariable: Bool {
extensionName == Fonts.defaultExtensionName
}
}

View File

@ -1,5 +1,5 @@
//
// FontTool.swift
// Fonts.swift
//
//
// Created by Thibaut Schmitt on 13/12/2021.
@ -9,7 +9,7 @@ import ToolCore
import Foundation
import ArgumentParser
struct FontTool: ParsableCommand {
struct Fonts: ParsableCommand {
// MARK: - CommandConfiguration
@ -20,7 +20,7 @@ struct FontTool: ParsableCommand {
// MARK: - Static
static let toolName = "FontTool"
static let toolName = "Fonts"
static let defaultExtensionName = "UIFont"
// MARK: - Properties
@ -38,7 +38,7 @@ struct FontTool: ParsableCommand {
// MARK: - Command Options
@OptionGroup var options: FontOptions
@OptionGroup var options: FontsOptions
// MARK: - Run
@ -55,7 +55,7 @@ struct FontTool: ParsableCommand {
// Get real font names
let inputFolder = URL(fileURLWithPath: options.inputFile).deletingLastPathComponent().relativePath
let fontsNames = FontToolHelper.getFontPostScriptName(for: fontsToGenerate,
let fontsNames = FontsToolHelper.getFontPostScriptName(for: fontsToGenerate,
inputFolder: inputFolder)
// Generate extension
@ -77,9 +77,9 @@ struct FontTool: ParsableCommand {
// Check input file exists
guard fileManager.fileExists(atPath: options.inputFile) else {
let error = FontToolError.fileNotExists(options.inputFile)
let error = FontsToolError.fileNotExists(options.inputFile)
print(error.localizedDescription)
FontTool.exit(withError: error)
Fonts.exit(withError: error)
}
// Check if needed to regenerate
@ -91,5 +91,3 @@ struct FontTool: ParsableCommand {
return true
}
}
FontTool.main()

View File

@ -1,5 +1,5 @@
//
// FontToolError.swift
// FontsToolError.swift
//
//
// Created by Thibaut Schmitt on 13/12/2021.
@ -7,7 +7,7 @@
import Foundation
enum FontToolError: Error {
enum FontsToolError: Error {
case fcScan(String, Int32, String?)
case inputFolderNotFound(String)
case fileNotExists(String)
@ -16,16 +16,16 @@ enum FontToolError: Error {
var localizedDescription: String {
switch self {
case .fcScan(let path, let code, let output):
return "error:[\(FontTool.toolName)] Error while getting fontName (fc-scan --format %{postscriptname} \(path). fc-scan exit with \(code) and output is: \(output ?? "no output")"
return "error:[\(Fonts.toolName)] Error while getting fontName (fc-scan --format %{postscriptname} \(path). fc-scan exit with \(code) and output is: \(output ?? "no output")"
case .inputFolderNotFound(let inputFolder):
return " error:[\(FontTool.toolName)] Input folder not found: \(inputFolder)"
return " error:[\(Fonts.toolName)] Input folder not found: \(inputFolder)"
case .fileNotExists(let filename):
return " error:[\(FontTool.toolName)] File \(filename) does not exists"
return " error:[\(Fonts.toolName)] File \(filename) does not exists"
case .writeExtension(let filename, let info):
return "error:[\(FontTool.toolName)] An error occured while writing extension in \(filename): \(info)"
return "error:[\(Fonts.toolName)] An error occured while writing extension in \(filename): \(info)"
}
}
}

View File

@ -1,5 +1,5 @@
//
// FontToolHelper.swift
// FontsToolHelper.swift
//
//
// Created by Thibaut Schmitt on 13/12/2021.
@ -8,7 +8,7 @@
import Foundation
import ToolCore
class FontToolHelper {
class FontsToolHelper {
static func getFontPostScriptName(for fonts: [String], inputFolder: String) -> [FontName] {
let fontsFilenames = Self.getFontsFilenames(fromInputFolder: inputFolder)
@ -38,9 +38,9 @@ class FontToolHelper {
// Get a enumerator for all files
let fileManager = FileManager()
guard fileManager.fileExists(atPath: inputFolder) else {
let error = FontToolError.inputFolderNotFound(inputFolder)
let error = FontsToolError.inputFolderNotFound(inputFolder)
print(error.localizedDescription)
FontTool.exit(withError: error)
Fonts.exit(withError: error)
}
let enumerator: FileManager.DirectoryEnumerator = fileManager.enumerator(atPath: inputFolder)!
@ -63,9 +63,9 @@ class FontToolHelper {
let task = Shell.shell("fc-scan", "--format", "%{postscriptname}", path)
guard let fontName = task.output, task.terminationStatus == 0 else {
let error = FontToolError.fcScan(path, task.terminationStatus, task.output)
let error = FontsToolError.fcScan(path, task.terminationStatus, task.output)
print(error.localizedDescription)
FontTool.exit(withError: error)
Fonts.exit(withError: error)
}
return fontName

View File

@ -1,5 +1,5 @@
//
// File.swift
// FontPlistGenerator.swift
//
//
// Created by Thibaut Schmitt on 29/08/2022.

View File

@ -31,15 +31,15 @@ class FontExtensionGenerator {
do {
try extensionContent.write(to: extensionFilePathURL, atomically: true, encoding: .utf8)
} catch (let error) {
let error = FontToolError.writeExtension(extensionFilePath, error.localizedDescription)
let error = FontsToolError.writeExtension(extensionFilePath, error.localizedDescription)
print(error.localizedDescription)
FontTool.exit(withError: error)
Fonts.exit(withError: error)
}
}
private static func getHeader(extensionClassname: String) -> String {
"""
// Generated by ResgenSwift.\(FontTool.toolName) \(ResgenSwiftVersion)
// Generated by ResgenSwift.\(Fonts.toolName) \(ResgenSwiftVersion)
import UIKit
@ -48,18 +48,18 @@ class FontExtensionGenerator {
}
private static func getFontNameEnum(fontsNames: [String]) -> String {
var enumDefinition = "\tenum FontName: String {\n"
var enumDefinition = " enum FontName: String {\n"
fontsNames.forEach {
enumDefinition += "\t\tcase \($0.removeCharacters(from: "[]+-_")) = \"\($0)\"\n"
enumDefinition += " case \($0.removeCharacters(from: "[]+-_")) = \"\($0)\"\n"
}
enumDefinition += "\t}\n"
enumDefinition += " }\n"
return enumDefinition
}
private static func getFontMethods(fontsNames: [FontName], staticVar: Bool) -> String {
let pragma = "\t// MARK: - Getter"
let pragma = " // MARK: - Getter"
var propertiesOrMethods: [String] = fontsNames
.unique()

View File

@ -1,5 +1,5 @@
//
// File.swift
// FontName.swift
//
//
// Created by Thibaut Schmitt on 29/08/2022.

View File

@ -1,5 +1,5 @@
//
// File.swift
// FontFileParser.swift
//
//
// Created by Thibaut Schmitt on 29/08/2022.

View File

@ -0,0 +1,51 @@
//
// Generate.swift
//
//
// Created by Thibaut Schmitt on 30/08/2022.
//
import ToolCore
import Foundation
import ArgumentParser
struct Generate: ParsableCommand {
// MARK: - CommandConfiguration
static var configuration = CommandConfiguration(
abstract: "A utility to generate ressources based on a configuration file",
version: ResgenSwiftVersion
)
// MARK: - Static
static let toolName = "Generate"
// MARK: - Command Options
@OptionGroup var options: GenerateOptions
// MARK: - Run
public func run() throws {
print("[\(Self.toolName)] Starting Resgen with configuration: \(options.configurationFile)")
// Parse
let configuration = ConfigurationFileParser.parse(options.configurationFile)
print("Found configurations :")
print(" - \(configuration.colors.count) colors configuration")
print(" - \(configuration.fonts.count) fonts configuration")
print(" - \(configuration.images.count) images configuration")
print(" - \(configuration.strings.count) strings configuration")
print(" - \(configuration.tags.count) tags configuration")
print()
// Execute commands
configuration.runnableConfigurations
.forEach {
$0.run(force: options.forceGeneration)
}
print("[\(Self.toolName)] Resgen ended")
}
}

View File

@ -0,0 +1,30 @@
//
// ResgenSwiftError.swift
//
//
// Created by Thibaut Schmitt on 30/08/2022.
//
import Foundation
enum GenerateError: Error {
case fileNotExists(String)
case invalidConfigurationFile(String)
case commandError([String], String)
var localizedDescription: String {
switch self {
case .fileNotExists(let filename):
return " error:[\(Generate.toolName)] File \(filename) does not exists"
case .invalidConfigurationFile(let filename):
return " error:[\(Generate.toolName)] File \(filename) is not a valid configuration file"
case .commandError(let command, let terminationStatus):
let readableCommand = command
.map { $0 }
.joined(separator: " ")
return "error:[\(Generate.toolName)] An error occured while running command '\(readableCommand)'. Command terminate with status code: \(terminationStatus)"
}
}
}

View File

@ -0,0 +1,19 @@
//
// GenerateOptions.swift
//
//
// Created by Thibaut Schmitt on 30/08/2022.
//
import Foundation
import Foundation
import ArgumentParser
struct GenerateOptions: ParsableArguments {
@Flag(name: [.customShort("f"), .customShort("F")], help: "Should force generation")
var forceGeneration = false
@Argument(help: "Configuration file.", transform: { $0.replaceTiltWithHomeDirectoryPath() })
var configurationFile: String
}

View File

@ -0,0 +1,140 @@
//
// ConfigurationFile.swift
//
//
// Created by Thibaut Schmitt on 30/08/2022.
//
import Foundation
struct ConfigurationFile: Codable, CustomDebugStringConvertible {
var colors: [ColorsConfiguration]
var fonts: [FontsConfiguration]
var images: [ImagesConfiguration]
var strings: [StringsConfiguration]
var tags: [TagsConfiguration]
var runnableConfigurations: [Runnable] {
let runnables: [[Runnable]] = [colors, fonts, images, strings, tags]
return Array(runnables.joined())
}
var debugDescription: String {
"""
\(colors)
-----------
-----------
\(fonts)
-----------
-----------
\(images)
-----------
-----------
\(strings)
-----------
-----------
\(tags)
"""
}
}
struct ColorsConfiguration: Codable, CustomDebugStringConvertible {
let inputFile: String
let style: String
let xcassetsPath: String
let extensionOutputPath: String
let extensionName: String?
let extensionSuffix: String?
var debugDescription: String {
"""
Colors configuration:
- Input file: \(inputFile)
- Style: \(style)
- Xcassets path: \(xcassetsPath)
- Extension output path: \(extensionOutputPath)
- Extension name: \(extensionName ?? "-")
- Extension suffix: \(extensionSuffix ?? "-")
"""
}
}
struct FontsConfiguration: Codable, CustomDebugStringConvertible {
let inputFile: String
let extensionOutputPath: String
let extensionName: String?
let extensionSuffix: String?
var debugDescription: String {
"""
Fonts configuration:
- Input file: \(inputFile)
- Extension output path: \(extensionOutputPath)
- Extension name: \(extensionName ?? "-")
- Extension suffix: \(extensionSuffix ?? "-")
"""
}
}
struct ImagesConfiguration: Codable, CustomDebugStringConvertible {
let inputFile: String
let xcassetsPath: String
let extensionOutputPath: String
let extensionName: String?
let extensionSuffix: String?
var debugDescription: String {
"""
Images configuration:
- Input file: \(inputFile)
- Xcassets path: \(xcassetsPath)
- Extension output path: \(extensionOutputPath)
- Extension name: \(extensionName ?? "-")
- Extension suffix: \(extensionSuffix ?? "-")
"""
}
}
struct StringsConfiguration: Codable, CustomDebugStringConvertible {
let inputFile: String
let outputPath: String
let langs: String
let defaultLang: String
let extensionOutputPath: String
let extensionName: String?
let extensionSuffix: String?
var debugDescription: String {
"""
Strings configuration:
- Input file: \(inputFile)
- Output path: \(outputPath)
- Langs: \(langs)
- Default lang: \(defaultLang)
- Extension output path: \(extensionOutputPath)
- Extension name: \(extensionName ?? "-")
- Extension suffix: \(extensionSuffix ?? "-")
"""
}
}
struct TagsConfiguration: Codable, CustomDebugStringConvertible {
let inputFile: String
let lang: String
let extensionOutputPath: String
let extensionName: String?
let extensionSuffix: String?
var debugDescription: String {
"""
Tags configuration:
- Input file: \(inputFile)
- Lang: \(lang)
- Extension output path: \(extensionOutputPath)
- Extension name: \(extensionName ?? "-")
- Extension suffix: \(extensionSuffix ?? "-")
"""
}
}

View File

@ -0,0 +1,27 @@
//
// ConfigurationFileParser.swift
//
//
// Created by Thibaut Schmitt on 30/08/2022.
//
import Foundation
import Yams
class ConfigurationFileParser {
static func parse(_ configurationFile: String) -> ConfigurationFile {
guard let data = FileManager().contents(atPath: configurationFile) else {
let error = GenerateError.fileNotExists(configurationFile)
print(error.localizedDescription)
Generate.exit(withError: error)
}
guard let configuration = try? YAMLDecoder().decode(ConfigurationFile.self, from: data) else {
let error = GenerateError.invalidConfigurationFile(configurationFile)
print(error.localizedDescription)
Generate.exit(withError: error)
}
return configuration
}
}

View File

@ -0,0 +1,43 @@
//
// ColorsConfiguration+ShellCommandable.swift
//
//
// Created by Thibaut Schmitt on 30/08/2022.
//
import Foundation
extension ColorsConfiguration: Runnable {
func run(force: Bool) {
var args = [String]()
if force {
args += ["-f"]
}
args += [
inputFile,
"--style",
style,
"--xcassets-path",
xcassetsPath,
"--extension-output-path",
extensionOutputPath
]
if let extensionName = extensionName {
args += [
"--extension-name",
extensionName
]
}
if let extensionSuffix = extensionSuffix {
args += [
"--extension-suffix",
extensionSuffix
]
}
Colors.main(args)
}
}

View File

@ -0,0 +1,40 @@
//
// FontsConfiguration+ShellCommandable.swift
//
//
// Created by Thibaut Schmitt on 30/08/2022.
//
import Foundation
extension FontsConfiguration: Runnable {
func run(force: Bool) {
var args = [String]()
if force {
args += ["-f"]
}
args += [
inputFile,
"--extension-output-path",
extensionOutputPath
]
if let extensionName = extensionName {
args += [
"--extension-name",
extensionName
]
}
if let extensionSuffix = extensionSuffix {
args += [
"--extension-suffix",
extensionSuffix
]
}
Fonts.main(args)
}
}

View File

@ -0,0 +1,41 @@
//
// ImagesConfiguration+ShellCommandable.swift
//
//
// Created by Thibaut Schmitt on 30/08/2022.
//
import Foundation
extension ImagesConfiguration: Runnable {
func run(force: Bool) {
var args = [String]()
if force {
args += ["-f"]
}
args += [
inputFile,
"--xcassets-path",
xcassetsPath,
"--extension-output-path",
extensionOutputPath
]
if let extensionName = extensionName {
args += [
"--extension-name",
extensionName
]
}
if let extensionSuffix = extensionSuffix {
args += [
"--extension-suffix",
extensionSuffix
]
}
Images.main(args)
}
}

View File

@ -0,0 +1,13 @@
//
// ShellCommandable.swift
//
//
// Created by Thibaut Schmitt on 30/08/2022.
//
import Foundation
protocol Runnable {
func run(force: Bool)
}

View File

@ -0,0 +1,46 @@
//
// StringsConfiguration+ShellCommandable.swift
//
//
// Created by Thibaut Schmitt on 30/08/2022.
//
import Foundation
extension StringsConfiguration: Runnable {
func run(force: Bool) {
var args = [String]()
if force {
args += ["-f"]
}
args += [
inputFile,
"--output-path",
outputPath,
"--langs",
langs,
"--default-lang",
defaultLang,
"--extension-output-path",
extensionOutputPath
]
if let extensionName = extensionName {
args += [
"--extension-name",
extensionName
]
}
if let extensionSuffix = extensionSuffix {
args += [
"--extension-suffix",
extensionSuffix
]
}
Stringium.main(args)
}
}

View File

@ -0,0 +1,41 @@
//
// TagsConfiguration+ShellCommandable.swift
//
//
// Created by Thibaut Schmitt on 30/08/2022.
//
import Foundation
extension TagsConfiguration: Runnable {
func run(force: Bool) {
var args = [String]()
if force {
args += ["-f"]
}
args += [
inputFile,
"--lang",
lang,
"--extension-output-path",
extensionOutputPath
]
if let extensionName = extensionName {
args += [
"--extension-name",
extensionName
]
}
if let extensionSuffix = extensionSuffix {
args += [
"--extension-suffix",
extensionSuffix
]
}
Tags.main(args)
}
}

View File

@ -11,9 +11,9 @@ extension FileManager {
func getAllRegularFileIn(directory: String) -> [String] {
var files = [String]()
guard let enumerator = self.enumerator(at: URL(string: directory)!, includingPropertiesForKeys: [.isRegularFileKey], options: [.skipsHiddenFiles, .skipsPackageDescendants]) else {
let error = ImagiumError.unknown("Cannot enumerate file in \(directory)")
let error = ImagesError.unknown("Cannot enumerate file in \(directory)")
print(error.localizedDescription)
Imagium.exit(withError: error)
Images.exit(withError: error)
}
for case let fileURL as URL in enumerator {
@ -23,9 +23,9 @@ extension FileManager {
files.append(fileURL.relativePath)
}
} catch {
let error = ImagiumError.getFileAttributed(fileURL.relativePath, error.localizedDescription)
let error = ImagesError.getFileAttributed(fileURL.relativePath, error.localizedDescription)
print(error.localizedDescription)
Imagium.exit(withError: error)
Images.exit(withError: error)
}
}
return files
@ -34,9 +34,9 @@ extension FileManager {
func getAllImageSetFolderIn(directory: String) -> [String] {
var files = [String]()
guard let enumerator = self.enumerator(at: URL(string: directory)!, includingPropertiesForKeys: [.isDirectoryKey], options: [.skipsHiddenFiles, .skipsPackageDescendants]) else {
let error = ImagiumError.unknown("Cannot enumerate imageset directory in \(directory)")
let error = ImagesError.unknown("Cannot enumerate imageset directory in \(directory)")
print(error.localizedDescription)
Imagium.exit(withError: error)
Images.exit(withError: error)
}
for case let fileURL as URL in enumerator {
@ -46,9 +46,9 @@ extension FileManager {
files.append(fileURL.lastPathComponent)
}
} catch {
let error = ImagiumError.getFileAttributed(fileURL.relativePath, error.localizedDescription)
let error = ImagesError.getFileAttributed(fileURL.relativePath, error.localizedDescription)
print(error.localizedDescription)
Imagium.exit(withError: error)
Images.exit(withError: error)
}
}
return files

View File

@ -58,9 +58,9 @@ class ImageExtensionGenerator {
do {
try extensionContent.write(to: extensionFilePathURL, atomically: true, encoding: .utf8)
} catch (let error) {
let error = ImagiumError.writeFile(extensionFilePath, error.localizedDescription)
let error = ImagesError.writeFile(extensionFilePath, error.localizedDescription)
print(error.localizedDescription)
Imagium.exit(withError: error)
Images.exit(withError: error)
}
}

View File

@ -1,5 +1,5 @@
//
// File.swift
// XcassetsGenerator.swift
//
//
// Created by Thibaut Schmitt on 24/01/2022.
@ -24,7 +24,7 @@ class XcassetsGenerator {
func generateXcassets(inputPath: String, imagesToGenerate: [ParsedImage], xcassetsPath: String) {
let fileManager = FileManager()
let svgConverter = Imagium.getSvgConverterPath()
let svgConverter = Images.getSvgConverterPath()
let allSubFiles = fileManager.getAllRegularFileIn(directory: inputPath)
var generatedAssetsPaths = [String]()
@ -47,9 +47,9 @@ class XcassetsGenerator {
return (subfile, "jepg")
}
}
let error = ImagiumError.unknownImageExtension(parsedImage.name)
let error = ImagesError.unknownImageExtension(parsedImage.name)
print(error.localizedDescription)
Imagium.exit(withError: error)
Images.exit(withError: error)
}()
// Create imageset folder

View File

@ -1,5 +1,5 @@
//
// Imagium.swift
// Images.swift
//
//
// Created by Thibaut Schmitt on 24/01/2022.
@ -9,7 +9,7 @@ import ToolCore
import Foundation
import ArgumentParser
struct Imagium: ParsableCommand {
struct Images: ParsableCommand {
// MARK: - CommandConfiguration
@ -20,7 +20,7 @@ struct Imagium: ParsableCommand {
// MARK: - Static
static let toolName = "Imagium"
static let toolName = "Images"
static let defaultExtensionName = "UIImage"
// MARK: - Properties
@ -35,7 +35,7 @@ struct Imagium: ParsableCommand {
// MARK: - Command Options
@OptionGroup var options: ImagiumOptions
@OptionGroup var options: ImagesOptions
// MARK: - Run
@ -81,13 +81,13 @@ struct Imagium: ParsableCommand {
// Input file
guard fileManager.fileExists(atPath: options.inputFile) else {
let error = ImagiumError.fileNotExists(options.inputFile)
let error = ImagesError.fileNotExists(options.inputFile)
print(error.localizedDescription)
Imagium.exit(withError: error)
Images.exit(withError: error)
}
// RSVG-Converter
_ = Imagium.getSvgConverterPath()
_ = Images.getSvgConverterPath()
// Check if needed to regenerate
guard GeneratorChecker.shouldGenerate(force: options.forceExecution, inputFilePath: options.inputFile, extensionFilePath: extensionFilePath) else {
@ -107,10 +107,8 @@ struct Imagium: ParsableCommand {
return taskSvgConverter.output!.removeCharacters(from: CharacterSet.whitespacesAndNewlines)
}
let error = ImagiumError.rsvgConvertNotFound
let error = ImagesError.rsvgConvertNotFound
print(error.localizedDescription)
Imagium.exit(withError: error)
Images.exit(withError: error)
}
}
Imagium.main()

View File

@ -0,0 +1,43 @@
//
// ImagesError.swift
//
//
// Created by Thibaut Schmitt on 24/01/2022.
//
import Foundation
enum ImagesError: Error {
case inputFolderNotFound(String)
case fileNotExists(String)
case unknownImageExtension(String)
case getFileAttributed(String, String)
case rsvgConvertNotFound
case writeFile(String, String)
case unknown(String)
var localizedDescription: String {
switch self {
case .inputFolderNotFound(let inputFolder):
return " error:[\(Images.toolName)] Input folder not found: \(inputFolder)"
case .fileNotExists(let filename):
return " error:[\(Images.toolName)] File \(filename) does not exists"
case .unknownImageExtension(let filename):
return " error:[\(Images.toolName)] File \(filename) have an unhandled file extension. Cannot generate image."
case .getFileAttributed(let filename, let errorDescription):
return " error:[\(Images.toolName)] Getting file attributes of \(filename) failed with error: \(errorDescription)"
case .rsvgConvertNotFound:
return " error:[\(Images.toolName)] Can't find rsvg-convert (can be installed with 'brew remove imagemagick && brew install imagemagick --with-librsvg')"
case .writeFile(let subErrorDescription, let filename):
return " error:[\(Images.toolName)] An error occured while writing content to \(filename): \(subErrorDescription)"
case .unknown(let errorDescription):
return " error:[\(Images.toolName)] Unknown error: \(errorDescription)"
}
}
}

View File

@ -8,7 +8,7 @@
import Foundation
import ArgumentParser
struct ImagiumOptions: ParsableArguments {
struct ImagesOptions: ParsableArguments {
@Flag(name: .customShort("f"), help: "Should force script execution")
var forceExecution = false
@ -25,7 +25,7 @@ struct ImagiumOptions: ParsableArguments {
var extensionOutputPath: String
@Option(help: "Extension name. If not specified, it will generate an UIImage extension. Using default extension name will generate static property.")
var extensionName: String = Imagium.defaultExtensionName
var extensionName: String = Images.defaultExtensionName
@Option(help: "Extension suffix. Ex: MyApp, it will generate {extensionName}+Image{extensionSuffix}.swift")
var extensionSuffix: String = ""

View File

@ -11,7 +11,7 @@ import CoreVideo
class TagsGenerator {
static func writeExtensionFiles(sections: [Section], lang: String, tags: [String], staticVar: Bool, extensionName: String, extensionFilePath: String) {
let extensionHeader = Self.getHeader(extensionClassname: extensionName)
let extensionHeader = Self.getHeader(extensionClassname: extensionName, staticVar: staticVar)
let extensionFooter = Self.getFooter()
let extensionContent: String = {
@ -22,7 +22,7 @@ class TagsGenerator {
return // Go to next section
}
content += "\n\t// MARK: - \(section.name)"
content += "\n // MARK: - \(section.name)"
section.definitions.forEach { definition in
if staticVar {
content += "\n\n\(definition.getStaticProperty(forLang: lang))"
@ -55,13 +55,11 @@ class TagsGenerator {
}
}
private static func getHeader(extensionClassname: String) -> String {
private static func getHeader(extensionClassname: String, staticVar: Bool) -> String {
"""
// Generated by ResgenSwift.Strings.Tags \(ResgenSwiftVersion)
// typelias Tags = String
import UIKit
\(staticVar ? "typelias Tags = String\n\n" : "")import UIKit
extension \(extensionClassname) {
"""

View File

@ -27,8 +27,8 @@ struct Stringium: ParsableCommand {
// MARK: - Properties
var extensionFileName: String {
if options.extensionSuffix.isEmpty == false {
return "\(options.extensionName)+\(options.extensionSuffix).swift"
if let extensionSuffix = options.extensionSuffix {
return "\(options.extensionName)+\(extensionSuffix).swift"
}
return "\(options.extensionName).swift"
}
@ -47,8 +47,6 @@ struct Stringium: ParsableCommand {
// MARK: - Command options
// The `@OptionGroup` attribute includes the flags, options, and
// arguments defined by another `ParsableArguments` type.
@OptionGroup var options: StringiumOptions
// MARK: - Run

View File

@ -34,7 +34,7 @@ struct StringiumOptions: ParsableArguments {
var extensionName: String = Stringium.defaultExtensionName
@Option(help: "Extension suffix. Ex: MyApp, it will generate {extensionName}+{extensionSuffix}.swift")
var extensionSuffix: String = ""
var extensionSuffix: String?
}
extension StringiumOptions {

View File

@ -26,5 +26,5 @@ struct Strings: ParsableCommand {
)
}
Strings.main()
//Strings.main()

View File

@ -28,8 +28,8 @@ struct Tags: ParsableCommand {
// MARK: - Properties
var extensionFileName: String {
if options.extensionSuffix.isEmpty == false {
return "\(options.extensionName)+\(options.extensionSuffix).swift"
if let extensionSuffix = options.extensionSuffix {
return "\(options.extensionName)+\(extensionSuffix).swift"
}
return "\(options.extensionName).swift"
}
@ -40,8 +40,6 @@ struct Tags: ParsableCommand {
// MARK: - Command Options
// The `@OptionGroup` attribute includes the flags, options, and
// arguments defined by another `ParsableArguments` type.
@OptionGroup var options: TagsOptions
// MARK: - Run

View File

@ -25,5 +25,5 @@ struct TagsOptions: ParsableArguments {
var extensionName: String = Tags.defaultExtensionName
@Option(help: "Extension suffix. Ex: MyApp, it will generate {extensionName}+Tag{extensionSuffix}.swift")
var extensionSuffix: String = ""
var extensionSuffix: String?
}

View File

@ -33,8 +33,6 @@ struct Twine: ParsableCommand {
// MARK: - Command Options
// The `@OptionGroup` attribute includes the flags, options, and
// arguments defined by another `ParsableArguments` type.
@OptionGroup var options: TwineOptions
// MARK: - Run

View File

@ -1,5 +1,5 @@
//
// File.swift
// TwineOptions.swift
//
//
// Created by Thibaut Schmitt on 10/01/2022.

View File

@ -1 +1,35 @@
print("Welcome ResgenSwift")
//
// ResgenSwift.swift
//
//
// Created by Thibaut Schmitt on 13/12/2021.
//
import ToolCore
import Foundation
import ArgumentParser
struct ResgenSwift: ParsableCommand {
static var configuration = CommandConfiguration(
abstract: "A utility for generate ressources.",
version: ResgenSwiftVersion,
// Pass an array to `subcommands` to set up a nested tree of subcommands.
// With language support for type-level introspection, this could be
// provided by automatically finding nested `ParsableCommand` types.
subcommands: [
Colors.self,
Fonts.self,
Images.self,
Strings.self,
Generate.self
]
// A default subcommand, when provided, is automatically selected if a
// subcommand is not given on the command line.
//defaultSubcommand: Twine.self
)
}
ResgenSwift.main()

View File

@ -10,14 +10,14 @@ import Foundation
public class Shell {
@discardableResult
public static func shell(_ args: String...) -> (terminationStatus: Int32, output: String?) {
public static func shell(launchPath: String = "/usr/bin/env", _ args: String...) -> (terminationStatus: Int32, output: String?) {
let task = Process()
task.launchPath = "/usr/bin/env"
task.launchPath = launchPath
task.arguments = args
let pipe = Pipe()
task.standardOutput = pipe
task.launch()
try? task.run()
task.waitUntilExit()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
@ -30,9 +30,9 @@ public class Shell {
}
@discardableResult
public static func shell(_ args: [String]) -> (terminationStatus: Int32, output: String?) {
public static func shell(launchPath: String = "/usr/bin/env", _ args: [String]) -> (terminationStatus: Int32, output: String?) {
let task = Process()
task.launchPath = "/usr/bin/env"
task.launchPath = launchPath
task.arguments = args
let pipe = Pipe()