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

@ -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,49 +0,0 @@
//
// File.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 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.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)
print(error.localizedDescription)
ColorTool.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]))
}
}
}
}

View File

@ -1,87 +0,0 @@
//
// ImageExtensionGenerator.swift
//
//
// Created by Thibaut Schmitt on 14/02/2022.
//
import ToolCore
import Foundation
class ImageExtensionGenerator {
// MARK: - Extension files
static func writeStringsFiles(images: [ParsedImage], staticVar: Bool, inputFilename: String, extensionName: String, extensionFilePath: String) {
// Get header/footer
let extensionHeader = Self.getHeader(inputFilename: inputFilename, extensionClassname: extensionName)
let extensionFooter = Self.getFooter()
// Create content
let extensionContent: String = {
var content = ""
images.forEach { img in
if staticVar {
content += "\n\(img.getStaticImageProperty())"
} else {
content += "\n\(img.getImageProperty())"
}
content += "\n "
}
return content
}()
// Create file if not exists
let fileManager = FileManager()
if fileManager.fileExists(atPath: extensionFilePath) == false {
Shell.shell("touch", "\(extensionFilePath)")
}
// Generate extension
Self.generateExtensionFile(extensionFilePath: extensionFilePath, extensionHeader, extensionContent, extensionFooter)
}
// MARK: - pragm
private static func generateExtensionFile(extensionFilePath: String, _ 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 = ImagiumError.writeFile(extensionFilePath, error.localizedDescription)
print(error.localizedDescription)
Imagium.exit(withError: error)
}
}
private static func getHeader(inputFilename: String, extensionClassname: String) -> String {
"""
// Generated by ResgenSwift.Imagium \(ResgenSwiftVersion)
// Images from \(inputFilename)
import UIKit
extension \(extensionClassname) {
"""
}
private static func getFooter() -> String {
"""
}
"""
}
}
//@objc var onboarding_foreground3: UIImage {
// return UIImage(named: "onboarding_foreground3")!
// }

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,33 +20,19 @@ struct ColorTool: ParsableCommand {
// MARK: - Static
static let toolName = "ColorTool"
static let toolName = "Color"
static let defaultExtensionName = "UIColor"
static let assetsColorsFolderName = "Colors"
// MARK: - Properties
var extensionFileName: String {
if options.extensionSuffix.isEmpty == false {
return "\(options.extensionName)+\(options.extensionSuffix).swift"
}
return "\(options.extensionName).swift"
}
var extensionFilePath: String { "\(options.extensionOutputPath)/\(extensionFileName)" }
var generateStaticVariable: Bool {
options.extensionName == Self.defaultExtensionName
}
// MARK: - Command options
@OptionGroup var options: ColorToolOptions
@OptionGroup var options: ColorsToolOptions
// MARK: - Run
public func run() throws {
print("[\(Self.toolName)] Starting colors generation")
print("[\(Self.toolName)] Will use inputFile \(options.inputFile) to generate \(options.colorStyle) colors in xcassets \(options.xcassetsPath)")
// Check requirements
guard checkRequirements() else { return }
@ -54,21 +40,24 @@ struct ColorTool: ParsableCommand {
// 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: generateStaticVariable,
staticVar: options.staticMembers,
extensionName: options.extensionName,
extensionFilePath: extensionFilePath)
extensionFilePath: options.extensionFilePath)
// -> Time: 0.0010340213775634766 seconds
print("[\(Self.toolName)] Colors generated")
}
@ -79,20 +68,22 @@ 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
guard GeneratorChecker.shouldGenerate(force: options.forceGeneration, inputFilePath: options.inputFile, extensionFilePath: extensionFilePath) else {
guard GeneratorChecker.shouldGenerate(force: options.forceGeneration,
inputFilePath: options.inputFile,
extensionFilePath: options.extensionFilePath) else {
print("[\(Self.toolName)] Colors are already up to date :) ")
return false
}
@ -103,21 +94,17 @@ struct ColorTool: ParsableCommand {
// MARK: - Helpers
private func deleteCurrentColors() {
Shell.shell("rm", "-rf", "\(options.xcassetsPath)/Colors/*")
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)
}
}
}
}
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"
*/

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

@ -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
@ -24,15 +24,31 @@ struct ColorToolOptions: ParsableArguments {
@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 = ColorTool.defaultExtensionName
var extensionName: String = Colors.defaultExtensionName
@Option(help: "Extension suffix. Ex: MyApp, it will generate {extensionName}+ColorsMyApp.swift")
var extensionSuffix: String = ""
var extensionSuffix: String?
}
extension ColorToolOptions {
// 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

@ -14,34 +14,34 @@ struct ColorExtensionGenerator {
let extensionClassname: String
static func writeExtensionFile(colors: [ParsedColor], staticVar: Bool, extensionName: String, extensionFilePath: String) {
// Create file if not exists
let fileManager = FileManager()
if fileManager.fileExists(atPath: extensionFilePath) == false {
Shell.shell("touch", "\(extensionFilePath)")
}
// Create extension content
let extensionContent = [
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")
// 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 static func getHeader(extensionClassname: String) -> String {
"""
// Generated by ResgenSwift.\(ColorTool.toolName) \(ResgenSwiftVersion)
// Generated by ResgenSwift.\(Colors.toolName) \(ResgenSwiftVersion)
import UIKit

View File

@ -20,20 +20,28 @@ struct ColorXcassetHelper {
private static func generateColorSetAssets(from color: ParsedColor, to xcassetsPath: String) {
// Create ColorSet
let colorSetPath = "\(xcassetsPath)/Colors/\(color.name).colorset"
Shell.shell("mkdir", "-p", "\(colorSetPath)")
// Create Contents.json in ColorSet
let contentsJsonPath = "\(colorSetPath)/Contents.json"
Shell.shell("touch", "\(contentsJsonPath)")
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: true, encoding: .utf8)
try color.contentsJSON().write(to: contentsJsonPathURL, atomically: false, 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

@ -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]))
}
}
}
}

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
@ -18,9 +18,27 @@ struct FontOptions: ParsableArguments {
@Option(help: "Path where to generate the extension.", transform: { $0.replaceTiltWithHomeDirectoryPath() })
var extensionOutputPath: String
@Option(help: "Tell if it will generate static properties or methods")
var staticMembers: Bool = false
@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 = ""
}
// MARK: - Computed var
extension FontsOptions {
var extensionFileName: String {
if extensionSuffix.isEmpty == false {
return "\(extensionName)+\(extensionSuffix).swift"
}
return "\(extensionName).swift"
}
var extensionFilePath: String {
"\(extensionOutputPath)/\(extensionFileName)"
}
}

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,30 +20,18 @@ struct FontTool: ParsableCommand {
// MARK: - Static
static let toolName = "FontTool"
static let toolName = "Fonts"
static let defaultExtensionName = "UIFont"
// MARK: - Properties
var extensionFileName: String {
if options.extensionSuffix.isEmpty == false {
return "\(options.extensionName)+\(options.extensionSuffix).swift"
}
return "\(options.extensionName).swift"
}
var extensionFilePath: String { "\(options.extensionOutputPath)/\(extensionFileName)" }
var generateStaticVariable: Bool {
options.extensionName == Self.defaultExtensionName
}
// MARK: - Command Options
@OptionGroup var options: FontOptions
@OptionGroup var options: FontsOptions
// MARK: - Run
public func run() throws {
print("[\(Self.toolName)] Starting fonts generation")
print("[\(Self.toolName)] Will use inputFile \(options.inputFile) to generate fonts")
// Check requirements
guard checkRequirements() else { return }
@ -55,14 +43,14 @@ 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
FontExtensionGenerator.writeExtensionFile(fontsNames: fontsNames,
staticVar: generateStaticVariable,
staticVar: options.staticMembers,
extensionName: options.extensionName,
extensionFilePath: extensionFilePath)
extensionFilePath: options.extensionFilePath)
print("Info.plist information:")
print("\(FontPlistGenerator.generatePlistUIAppsFontContent(for: fontsNames))")
@ -77,13 +65,15 @@ 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
guard GeneratorChecker.shouldGenerate(force: options.forceGeneration, inputFilePath: options.inputFile, extensionFilePath: extensionFilePath) else {
guard GeneratorChecker.shouldGenerate(force: options.forceGeneration,
inputFilePath: options.inputFile,
extensionFilePath: options.extensionFilePath) else {
print("[\(Self.toolName)] Fonts are already up to date :) ")
return false
}
@ -91,5 +81,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)!
@ -60,12 +60,12 @@ class FontToolHelper {
private static func getFontName(atPath path: String) -> String {
//print("fc-scan --format %{postscriptname} \(path)")
// Get real font name
let task = Shell.shell("fc-scan", "--format", "%{postscriptname}", path)
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.
@ -15,7 +15,7 @@ class FontPlistGenerator {
.forEach {
plistData += "\t\t<string>\($0)</string>\n"
}
plistData += "\t</array>\n*/"
plistData += "\t</array>"
return plistData
}

View File

@ -11,35 +11,35 @@ import ToolCore
class FontExtensionGenerator {
static func writeExtensionFile(fontsNames: [String], staticVar: Bool, extensionName: String, extensionFilePath: String) {
// Check file if not exists
let fileManager = FileManager()
if fileManager.fileExists(atPath: extensionFilePath) == false {
Shell.shell("touch", "\(extensionFilePath)")
}
// Create extension content
let extensionContent = [
let extensionContent = Self.getExtensionContent(fontsNames: fontsNames,
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 = FontsToolError.writeExtension(extensionFilePath, error.localizedDescription)
print(error.localizedDescription)
Fonts.exit(withError: error)
}
}
static func getExtensionContent(fontsNames: [String], staticVar: Bool, extensionName: String) -> String {
[
Self.getHeader(extensionClassname: extensionName),
Self.getFontNameEnum(fontsNames: fontsNames),
Self.getFontMethods(fontsNames: fontsNames, staticVar: staticVar),
Self.getFooter()
]
.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 = FontToolError.writeExtension(extensionFilePath, error.localizedDescription)
print(error.localizedDescription)
FontTool.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.fontNameSanitize) = \"\($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,18 @@
//
// StringExtensions.swift
//
//
// Created by Thibaut Schmitt on 31/08/2022.
//
import Foundation
extension String {
func prependIfRelativePath(_ prependPath: String) -> String {
if self.hasPrefix("/") {
return self
}
return prependPath + self
}
}

View File

@ -0,0 +1,57 @@
//
// 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()
print("Input file: \(configuration.colors.first?.inputFile ?? "no input file")")
// Execute commands
configuration.runnableConfigurations
.forEach {
let begin = Date()
$0.run(projectDirectory: options.projectDirectory,
force: options.forceGeneration)
print("Took: \(Date().timeIntervalSince(begin))s\n")
}
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,28 @@
//
// 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
@Option(help: "Project directory. It will be added to every relative path (path that does not start with `/`",
transform: {
if $0.last == "/" {
return $0
}
return $0 + "/"
})
var projectDirectory: String
}

View File

@ -0,0 +1,180 @@
//
// 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?
private let staticMembers: Bool?
var staticMembersOptions: Bool {
if let staticMembers = staticMembers {
return staticMembers
}
return false
}
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?
private let staticMembers: Bool?
var staticMembersOptions: Bool {
if let staticMembers = staticMembers {
return staticMembers
}
return false
}
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?
private let staticMembers: Bool?
var staticMembersOptions: Bool {
if let staticMembers = staticMembers {
return staticMembers
}
return false
}
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?
private let staticMembers: Bool?
var staticMembersOptions: Bool {
if let staticMembers = staticMembers {
return staticMembers
}
return false
}
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?
private let staticMembers: Bool?
var staticMembersOptions: Bool {
if let staticMembers = staticMembers {
return staticMembers
}
return false
}
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,45 @@
//
// ColorsConfiguration+Runnable.swift
//
//
// Created by Thibaut Schmitt on 30/08/2022.
//
import Foundation
extension ColorsConfiguration: Runnable {
func run(projectDirectory: String, force: Bool) {
var args = [String]()
if force {
args += ["-f"]
}
args += [
inputFile.prependIfRelativePath(projectDirectory),
"--style",
style,
"--xcassets-path",
xcassetsPath.prependIfRelativePath(projectDirectory),
"--extension-output-path",
extensionOutputPath.prependIfRelativePath(projectDirectory),
"--static-members",
"\(staticMembersOptions)"
]
if let extensionName = extensionName {
args += [
"--extension-name",
extensionName
]
}
if let extensionSuffix = extensionSuffix {
args += [
"--extension-suffix",
extensionSuffix
]
}
Colors.main(args)
}
}

View File

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

View File

@ -0,0 +1,43 @@
//
// ImagesConfiguration+Runnable.swift
//
//
// Created by Thibaut Schmitt on 30/08/2022.
//
import Foundation
extension ImagesConfiguration: Runnable {
func run(projectDirectory: String, force: Bool) {
var args = [String]()
if force {
args += ["-f"] // Images has a -f and -F options
}
args += [
inputFile.prependIfRelativePath(projectDirectory),
"--xcassets-path",
xcassetsPath.prependIfRelativePath(projectDirectory),
"--extension-output-path",
extensionOutputPath.prependIfRelativePath(projectDirectory),
"--static-members",
"\(staticMembersOptions)"
]
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(projectDirectory: String, force: Bool)
}

View File

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

View File

@ -0,0 +1,43 @@
//
// TagsConfiguration+Runnable.swift
//
//
// Created by Thibaut Schmitt on 30/08/2022.
//
import Foundation
extension TagsConfiguration: Runnable {
func run(projectDirectory: String, force: Bool) {
var args = [String]()
if force {
args += ["-f"]
}
args += [
inputFile.prependIfRelativePath(projectDirectory),
"--lang",
lang,
"--extension-output-path",
extensionOutputPath.prependIfRelativePath(projectDirectory),
"--static-members",
"\(staticMembersOptions)"
]
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

@ -0,0 +1,77 @@
//
// ImageExtensionGenerator.swift
//
//
// Created by Thibaut Schmitt on 14/02/2022.
//
import ToolCore
import Foundation
class ImageExtensionGenerator {
// MARK: - pragm
static func generateExtensionFile(images: [ParsedImage],
staticVar: Bool,
inputFilename: String,
extensionName: String,
extensionFilePath: String) {
// Create extension conten1t
let extensionContent = Self.getExtensionContent(images: images,
staticVar: staticVar,
extensionName: extensionName,
inputFilename: inputFilename)
// Write content
let extensionFilePathURL = URL(fileURLWithPath: extensionFilePath)
do {
try extensionContent.write(to: extensionFilePathURL, atomically: false, encoding: .utf8)
} catch (let error) {
let error = ImagesError.writeFile(extensionFilePath, error.localizedDescription)
print(error.localizedDescription)
Images.exit(withError: error)
}
}
// MARK: - Extension content
static func getExtensionContent(images: [ParsedImage], staticVar: Bool, extensionName: String, inputFilename: String) -> String {
[
Self.getHeader(inputFilename: inputFilename, extensionClassname: extensionName),
Self.getProperties(images: images, staticVar: staticVar),
Self.getFooter()
]
.joined(separator: "\n")
}
// MARK: - Extension part
private static func getHeader(inputFilename: String, extensionClassname: String) -> String {
"""
// Generated by ResgenSwift.\(Images.toolName) \(ResgenSwiftVersion)
// Images from \(inputFilename)
import UIKit
extension \(extensionClassname) {
"""
}
private static func getProperties(images: [ParsedImage], staticVar: Bool) -> String {
if staticVar {
return images
.map { "\n\($0.getStaticImageProperty())" }
.joined(separator: "\n")
}
return images
.map { "\n\($0.getImageProperty())" }
.joined(separator: "\n")
}
private static func getFooter() -> String {
"""
}
"""
}
}

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,15 +47,14 @@ 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
// Create imageset folder name
let imagesetName = "\(parsedImage.name).imageset"
let imagesetPath = "\(xcassetsPath)/\(imagesetName)"
Shell.shell("mkdir", "-p", imagesetPath)
// Store managed images path
generatedAssetsPaths.append(imagesetName)
@ -66,11 +65,23 @@ class XcassetsGenerator {
let output3x = "\(imagesetPath)/\(parsedImage.name)@3x.\(XcassetsGenerator.outputImageExtension)"
// Check if we need to convert image
if self.shouldBypassGeneration(for: parsedImage, xcassetImagePath: output1x) {
print("\(parsedImage.name) -> Not regenerating")
guard self.shouldGenerate(inputImagePath: imageData.path, xcassetImagePath: output1x) else {
//print("\(parsedImage.name) -> Not regenerating")
return
}
// Create imageset folder
if fileManager.fileExists(atPath: imagesetPath) == false {
do {
try fileManager.createDirectory(atPath: imagesetPath,
withIntermediateDirectories: true)
} catch {
let error = ImagesError.createAssetFolder(imagesetPath)
print(error.localizedDescription)
Images.exit(withError: error)
}
}
// Convert image
let convertArguments = parsedImage.convertArguments
if imageData.ext == "svg" {
@ -96,20 +107,23 @@ class XcassetsGenerator {
// convert path/to/image.png -resize 200x300 path/to/output.png
// convert path/to/image.png -resize 200x path/to/output.png
// convert path/to/image.png -resize x300 path/to/output.png
Shell.shell("convert", "\(imageData.path)", "-resize", "\(convertArguments.x1.width ?? "")x\(convertArguments.x1.height ?? "")", output1x)
Shell.shell("convert", "\(imageData.path)", "-resize", "\(convertArguments.x2.width ?? "")x\(convertArguments.x2.height ?? "")", output2x)
Shell.shell("convert", "\(imageData.path)", "-resize", "\(convertArguments.x3.width ?? "")x\(convertArguments.x3.height ?? "")", output3x)
Shell.shell(["convert", "\(imageData.path)",
"-resize", "\(convertArguments.x1.width ?? "")x\(convertArguments.x1.height ?? "")",
output1x])
Shell.shell(["convert", "\(imageData.path)",
"-resize", "\(convertArguments.x2.width ?? "")x\(convertArguments.x2.height ?? "")",
output2x])
Shell.shell(["convert", "\(imageData.path)",
"-resize", "\(convertArguments.x3.width ?? "")x\(convertArguments.x3.height ?? "")",
output3x])
}
// Write Content.json
let imagesetContentJson = parsedImage.contentJson
let contentJsonFilePath = "\(imagesetPath)/Contents.json"
if fileManager.fileExists(atPath: contentJsonFilePath) == false {
Shell.shell("touch", "\(contentJsonFilePath)")
}
let contentJsonFilePathURL = URL(fileURLWithPath: contentJsonFilePath)
try! imagesetContentJson.write(to: contentJsonFilePathURL, atomically: true, encoding: .utf8)
try! imagesetContentJson.write(to: contentJsonFilePathURL, atomically: false, encoding: .utf8)
print("\(parsedImage.name) -> Generated")
}
@ -147,43 +161,11 @@ class XcassetsGenerator {
// MARK: - Helpers: bypass generation
private func shouldBypassGeneration(for image: ParsedImage, xcassetImagePath: String) -> Bool {
guard forceGeneration == false else {
return false
}
let fileManager = FileManager()
// File not exists -> do not bypass
guard fileManager.fileExists(atPath: xcassetImagePath) else {
return false
}
// Info unavailable -> do not bypass
let taskWidth = Shell.shell("identify", "-format", "%w", xcassetImagePath)
let taskHeight = Shell.shell("identify", "-format", "%h", xcassetImagePath)
guard taskWidth.terminationStatus == 0,
taskHeight.terminationStatus == 0 else {
return false
}
let currentWidth = Int(taskWidth.output ?? "-1") ?? -1
let currentheight = Int(taskHeight.output ?? "-1") ?? -1
// Info unavailable -> do not bypass
guard currentWidth > 0 && currentheight > 0 else {
return false
}
// Check width and height
if image.width != -1 && currentWidth == image.width {
return true
}
if image.height != -1 && currentheight == image.height {
private func shouldGenerate(inputImagePath: String, xcassetImagePath: String) -> Bool {
if forceGeneration {
return true
}
return false
return GeneratorChecker.isFile(inputImagePath, moreRecenThan: xcassetImagePath)
}
}

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,27 +20,18 @@ struct Imagium: ParsableCommand {
// MARK: - Static
static let toolName = "Imagium"
static let toolName = "Images"
static let defaultExtensionName = "UIImage"
// MARK: - Properties
var extensionFileName: String { "\(options.extensionName)+\(options.extensionSuffix).swift" }
var extensionFilePath: String { "\(options.extensionOutputPath)/\(extensionFileName)" }
var inputFilenameWithoutExt: String {
URL(fileURLWithPath: options.inputFile)
.deletingPathExtension()
.lastPathComponent
}
// MARK: - Command Options
@OptionGroup var options: ImagiumOptions
@OptionGroup var options: ImagesOptions
// MARK: - Run
mutating func run() {
print("[\(Self.toolName)] Starting images generation")
print("[\(Self.toolName)] Will use inputFile \(options.inputFile) to generate images in xcassets \(options.xcassetsPath)")
// Check requirements
guard checkRequirements() else { return }
@ -54,17 +45,18 @@ struct Imagium: ParsableCommand {
let inputFolder = URL(fileURLWithPath: options.inputFile)
.deletingLastPathComponent()
.relativePath
let xcassetsGenerator = XcassetsGenerator(forceGeneration: options.forceExecutionAndGeneration)
xcassetsGenerator.generateXcassets(inputPath: inputFolder,
imagesToGenerate: imagesToGenerate,
xcassetsPath: options.xcassetsPath)
// Generate extension
ImageExtensionGenerator.writeStringsFiles(images: imagesToGenerate,
staticVar: options.extensionName == Self.defaultExtensionName,
inputFilename: inputFilenameWithoutExt,
extensionName: options.extensionName,
extensionFilePath: extensionFilePath)
ImageExtensionGenerator.generateExtensionFile(images: imagesToGenerate,
staticVar: options.staticMembers,
inputFilename: options.inputFilenameWithoutExt,
extensionName: options.extensionName,
extensionFilePath: options.extensionFilePath)
print("[\(Self.toolName)] Images generated")
@ -81,16 +73,18 @@ 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 {
guard GeneratorChecker.shouldGenerate(force: options.forceExecution,
inputFilePath: options.inputFile,
extensionFilePath: options.extensionFilePath) else {
print("[\(Self.toolName)] Images are already up to date :) ")
return false
}
@ -102,15 +96,13 @@ struct Imagium: ParsableCommand {
@discardableResult
static func getSvgConverterPath() -> String {
let taskSvgConverter = Shell.shell("which", "rsvg-convert")
let taskSvgConverter = Shell.shell(["which", "rsvg-convert"])
if taskSvgConverter.terminationStatus == 0 {
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,47 @@
//
// 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 createAssetFolder(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 .createAssetFolder(let folder):
return "error:[\(Colors.toolName)] An error occured while creating folder `\(folder)`"
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
@ -24,18 +24,33 @@ struct ImagiumOptions: ParsableArguments {
@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 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 = ""
var extensionSuffix: String?
}
// MARK: - Computed var
/*
swift run -c release Imagium $FORCE_FLAG "./Images/sampleImages.txt" \
--xcassets-path "./Images/imagium.xcassets" \
--extension-output-path "./Images/Generated" \
--extension-name "UIImage" \
--extension-suffix "GenAllScript"
*/
extension ImagesOptions {
var extensionFileName: String {
if let extensionSuffix = extensionSuffix {
return "\(extensionName)+\(extensionSuffix).swift"
}
return "\(extensionName).swift"
}
var extensionFilePath: String {
"\(extensionOutputPath)/\(extensionFileName)"
}
var inputFilenameWithoutExt: String {
URL(fileURLWithPath: inputFile)
.deletingPathExtension()
.lastPathComponent
}
}

View File

@ -13,10 +13,13 @@ class ImageFileParser {
let inputFileContent = try! String(contentsOfFile: inputFile, encoding: .utf8)
let stringsByLines = inputFileContent.components(separatedBy: .newlines)
return Self.parseLines(stringsByLines, platform: platform)
}
static func parseLines(_ lines: [String], platform: PlatormTag) -> [ParsedImage] {
var imagesToGenerate = [ParsedImage]()
// Parse file
stringsByLines.forEach {
lines.forEach {
guard $0.removeLeadingTrailingWhitespace().isEmpty == false, $0.first != "#" else {
return
}

View File

@ -28,7 +28,7 @@ class StringsFileGenerator {
let stringsFilePath = "\(outputPath)/\(lang).lproj/\(inputFilenameWithoutExt).strings"
let stringsFilePathURL = URL(fileURLWithPath: stringsFilePath)
do {
try fileContent.write(to: stringsFilePathURL, atomically: true, encoding: .utf8)
try fileContent.write(to: stringsFilePathURL, atomically: false, encoding: .utf8)
} catch (let error) {
let error = StringiumError.writeFile(error.localizedDescription, stringsFilePath)
print(error.localizedDescription)
@ -37,7 +37,7 @@ class StringsFileGenerator {
}
}
private static func generateStringsFileContent(lang: String, defaultLang: String, tags inputTags: [String], sections: [Section]) -> String {
static func generateStringsFileContent(lang: String, defaultLang: String, tags inputTags: [String], sections: [Section]) -> String {
var stringsFileContent = """
/**
* Apple Strings File
@ -87,47 +87,18 @@ class StringsFileGenerator {
// MARK: - Extension file
static func writeExtensionFiles(sections: [Section], defaultLang lang: String, tags: [String], staticVar: Bool, inputFilename: String, extensionName: String, extensionFilePath: String) {
let extensionHeader = Self.getHeader(stringsFilename: inputFilename, extensionClassname: extensionName)
let extensionFooter = Self.getFooter()
let extensionContent: String = {
var content = ""
sections.forEach { section in
// Check that at least one string will be generated
guard section.hasOneOrMoreMatchingTags(tags: tags) else {
return // Go to next section
}
content += "\n\t// MARK: - \(section.name)"
section.definitions.forEach { definition in
guard definition.hasOneOrMoreMatchingTags(inputTags: tags) == true else {
return // Go to next definition
}
if staticVar {
content += "\n\n\(definition.getNSLocalizedStringStaticProperty(forLang: lang))"
} else {
content += "\n\n\(definition.getNSLocalizedStringProperty(forLang: lang))"
}
}
content += "\n"
}
return content
}()
// Create file if not exists
let fileManager = FileManager()
if fileManager.fileExists(atPath: extensionFilePath) == false {
Shell.shell("touch", "\(extensionFilePath)")
}
// Create extension content
let extensionFileContent = [extensionHeader, extensionContent, extensionFooter].joined(separator: "\n")
// Get extension content
let extensionFileContent = Self.getExtensionContent(sections: sections,
defaultLang: lang,
tags: tags,
staticVar: staticVar,
inputFilename: inputFilename,
extensionName: extensionName)
// Write content
let extensionFilePathURL = URL(fileURLWithPath: extensionFilePath)
do {
try extensionFileContent.write(to: extensionFilePathURL, atomically: true, encoding: .utf8)
try extensionFileContent.write(to: extensionFilePathURL, atomically: false, encoding: .utf8)
} catch (let error) {
let error = StringiumError.writeFile(extensionFilePath, error.localizedDescription)
print(error.localizedDescription)
@ -135,6 +106,19 @@ class StringsFileGenerator {
}
}
// MARK: - Extension content
static func getExtensionContent(sections: [Section], defaultLang lang: String, tags: [String], staticVar: Bool, inputFilename: String, extensionName: String) -> String {
[
Self.getHeader(stringsFilename: inputFilename, extensionClassname: extensionName),
Self.getProperties(sections: sections, defaultLang: lang, tags: tags, staticVar: staticVar),
Self.getFooter()
]
.joined(separator: "\n")
}
// MARK: - Extension part
private static func getHeader(stringsFilename: String, extensionClassname: String) -> String {
"""
// Generated by ResgenSwift.Strings.\(Stringium.toolName) \(ResgenSwiftVersion)
@ -147,6 +131,30 @@ class StringsFileGenerator {
"""
}
private static func getProperties(sections: [Section], defaultLang lang: String, tags: [String], staticVar: Bool) -> String {
sections.compactMap { section in
// Check that at least one string will be generated
guard section.hasOneOrMoreMatchingTags(tags: tags) else {
return nil // Go to next section
}
var res = "\n // MARK: - \(section.name)\n"
res += section.definitions.compactMap { definition in
guard definition.hasOneOrMoreMatchingTags(inputTags: tags) == true else {
return nil // Go to next definition
}
if staticVar {
return "\n\(definition.getNSLocalizedStringStaticProperty(forLang: lang))"
}
return "\n\(definition.getNSLocalizedStringProperty(forLang: lang))"
}
.joined(separator: "\n")
return res
}
.joined(separator: "\n")
}
private static func getFooter() -> String {
"""
}

View File

@ -0,0 +1,85 @@
//
// TagsGenerator.swift
//
//
// Created by Thibaut Schmitt on 10/01/2022.
//
import Foundation
import ToolCore
import CoreVideo
class TagsGenerator {
static func writeExtensionFiles(sections: [Section], lang: String, tags: [String], staticVar: Bool, extensionName: String, extensionFilePath: String) {
// Get extension content
let extensionFileContent = Self.getExtensionContent(sections: sections,
lang: lang,
tags: tags,
staticVar: staticVar,
extensionName: extensionName)
// Write content
let extensionFilePathURL = URL(fileURLWithPath: extensionFilePath)
do {
try extensionFileContent.write(to: extensionFilePathURL, atomically: false, encoding: .utf8)
} catch (let error) {
let error = StringiumError.writeFile(extensionFilePath, error.localizedDescription)
print(error.localizedDescription)
Stringium.exit(withError: error)
}
}
// MARK: - Extension content
static func getExtensionContent(sections: [Section], lang: String, tags: [String], staticVar: Bool, extensionName: String) -> String {
[
Self.getHeader(extensionClassname: extensionName, staticVar: staticVar),
Self.getProperties(sections: sections, lang: lang, tags: tags, staticVar: staticVar),
Self.getFooter()
]
.joined(separator: "\n")
}
// MARK: - Extension part
private static func getHeader(extensionClassname: String, staticVar: Bool) -> String {
"""
// Generated by ResgenSwift.Strings.\(Tags.toolName) \(ResgenSwiftVersion)
\(staticVar ? "typelias Tags = String\n\n" : "")import UIKit
extension \(extensionClassname) {
"""
}
private static func getProperties(sections: [Section], lang: String, tags: [String], staticVar: Bool) -> String {
sections
.compactMap { section in
// Check that at least one string will be generated
guard section.hasOneOrMoreMatchingTags(tags: tags) else {
return nil// Go to next section
}
var res = "\n // MARK: - \(section.name)"
section.definitions.forEach { definition in
guard definition.hasOneOrMoreMatchingTags(inputTags: tags) == true else {
return // Go to next definition
}
if staticVar {
res += "\n\n\(definition.getStaticProperty(forLang: lang))"
} else {
res += "\n\n\(definition.getProperty(forLang: lang))"
}
}
return res
}
.joined(separator: "\n")
}
private static func getFooter() -> String {
"""
}
"""
}
}

View File

@ -53,7 +53,6 @@ class Definition {
guard let match = match else { return }
if let range = Range(match.range, in: input), let last = input[range].last {
debugPrint("Found: \(input[range])")
switch last {
case "d", "u":
methodsParameters.append("Int")

View File

@ -24,37 +24,15 @@ struct Stringium: ParsableCommand {
static let defaultExtensionName = "String"
static let noTranslationTag: String = "notranslation"
// MARK: - Properties
var extensionFileName: String {
if options.extensionSuffix.isEmpty == false {
return "\(options.extensionName)+\(options.extensionSuffix).swift"
}
return "\(options.extensionName).swift"
}
var extensionFilePath: String { "\(options.extensionOutputPath)/\(extensionFileName)" }
var inputFilenameWithoutExt: String {
URL(fileURLWithPath: options.inputFile)
.deletingPathExtension()
.lastPathComponent
}
var generateStaticVariable: Bool {
options.extensionName == Self.defaultExtensionName
}
// MARK: - Command options
// The `@OptionGroup` attribute includes the flags, options, and
// arguments defined by another `ParsableArguments` type.
@OptionGroup var options: StringiumOptions
// MARK: - Run
mutating func run() {
print("[\(Self.toolName)] Starting strings generation")
print("[\(Self.toolName)] Will use inputFile \(options.inputFile) to generate strings for \(options.langs) (default lang: \(options.defaultLang)")
// Check requirements
guard checkRequirements() else { return }
@ -70,16 +48,16 @@ struct Stringium: ParsableCommand {
defaultLang: options.defaultLang,
tags: options.tags,
outputPath: options.stringsFileOutputPath,
inputFilenameWithoutExt: inputFilenameWithoutExt)
inputFilenameWithoutExt: options.inputFilenameWithoutExt)
// Generate extension
StringsFileGenerator.writeExtensionFiles(sections: sections,
defaultLang: options.defaultLang,
tags: options.tags,
staticVar: generateStaticVariable,
inputFilename: inputFilenameWithoutExt,
staticVar: options.staticMembers,
inputFilename: options.inputFilenameWithoutExt,
extensionName: options.extensionName,
extensionFilePath: extensionFilePath)
extensionFilePath: options.extensionFilePath)
print("[\(Self.toolName)] Strings generated")
}
@ -110,7 +88,9 @@ struct Stringium: ParsableCommand {
}
// Check if needed to regenerate
guard GeneratorChecker.shouldGenerate(force: options.forceGeneration, inputFilePath: options.inputFile, extensionFilePath: extensionFilePath) else {
guard GeneratorChecker.shouldGenerate(force: options.forceGeneration,
inputFilePath: options.inputFile,
extensionFilePath: options.extensionFilePath) else {
print("[\(Self.toolName)] Strings are already up to date :) ")
return false
}

View File

@ -30,13 +30,18 @@ struct StringiumOptions: ParsableArguments {
@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 String extension. Using default extension name will generate static property.")
var extensionName: String = Stringium.defaultExtensionName
@Option(help: "Extension suffix. Ex: MyApp, it will generate {extensionName}+{extensionSuffix}.swift")
var extensionSuffix: String = ""
var extensionSuffix: String?
}
// MARK: - Private var getter
extension StringiumOptions {
var stringsFileOutputPath: String {
var outputPath = outputPathRaw
@ -58,3 +63,24 @@ extension StringiumOptions {
.map { String($0) }
}
}
// MARK: - Computed var
extension StringiumOptions {
var extensionFileName: String {
if let extensionSuffix = extensionSuffix {
return "\(extensionName)+\(extensionSuffix).swift"
}
return "\(extensionName).swift"
}
var extensionFilePath: String {
"\(extensionOutputPath)/\(extensionFileName)"
}
var inputFilenameWithoutExt: String {
URL(fileURLWithPath: inputFile)
.deletingPathExtension()
.lastPathComponent
}
}

View File

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

View File

@ -25,29 +25,15 @@ struct Tags: ParsableCommand {
static let defaultExtensionName = "Tags"
static let noTranslationTag: String = "notranslation"
// MARK: - Properties
var extensionFileName: String {
if options.extensionSuffix.isEmpty == false {
return "\(options.extensionName)+\(options.extensionSuffix).swift"
}
return "\(options.extensionName).swift"
}
var extensionFilePath: String { "\(options.extensionOutputPath)/\(extensionFileName)" }
var generateStaticVariable: Bool {
options.extensionName == Self.defaultExtensionName
}
// MARK: - Command Options
// The `@OptionGroup` attribute includes the flags, options, and
// arguments defined by another `ParsableArguments` type.
@OptionGroup var options: TagsOptions
// MARK: - Run
mutating func run() {
print("[\(Self.toolName)] Starting tagss generation")
print("[\(Self.toolName)] Starting tags generation")
print("[\(Self.toolName)] Will use inputFile \(options.inputFile) to generate strings for lang: \(options.lang)")
// Check requirements
guard checkRequirements() else { return }
@ -61,9 +47,9 @@ struct Tags: ParsableCommand {
TagsGenerator.writeExtensionFiles(sections: sections,
lang: options.lang,
tags: ["ios", "iosonly", Self.noTranslationTag],
staticVar: generateStaticVariable,
staticVar: options.staticMembers,
extensionName: options.extensionName,
extensionFilePath: extensionFilePath)
extensionFilePath: options.extensionFilePath)
print("[\(Self.toolName)] Tags generated")
}
@ -81,7 +67,9 @@ struct Tags: ParsableCommand {
}
// Check if needed to regenerate
guard GeneratorChecker.shouldGenerate(force: options.forceGeneration, inputFilePath: options.inputFile, extensionFilePath: extensionFilePath) else {
guard GeneratorChecker.shouldGenerate(force: options.forceGeneration,
inputFilePath: options.inputFile,
extensionFilePath: options.extensionFilePath) else {
print("[\(Self.toolName)] Tags are already up to date :) ")
return false
}

View File

@ -21,9 +21,27 @@ struct TagsOptions: ParsableArguments {
@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 a Tag extension. Using default extension name will generate static property.")
var extensionName: String = Tags.defaultExtensionName
@Option(help: "Extension suffix. Ex: MyApp, it will generate {extensionName}+Tag{extensionSuffix}.swift")
var extensionSuffix: String = ""
var extensionSuffix: String?
}
// MARK: - Computed var
extension TagsOptions {
var extensionFileName: String {
if let extensionSuffix = extensionSuffix {
return "\(extensionName)+\(extensionSuffix).swift"
}
return "\(extensionName).swift"
}
var extensionFilePath: String {
"\(extensionOutputPath)/\(extensionFileName)"
}
}

View File

@ -24,17 +24,8 @@ struct Twine: ParsableCommand {
static let defaultExtensionName = "String"
static let twineExecutable = "\(FileManager.default.homeDirectoryForCurrentUser.relativePath)/scripts/twine/twine"
// MARK: - Properties
var inputFilenameWithoutExt: String { URL(fileURLWithPath: options.inputFile)
.deletingPathExtension()
.lastPathComponent
}
// MARK: - Command Options
// The `@OptionGroup` attribute includes the flags, options, and
// arguments defined by another `ParsableArguments` type.
@OptionGroup var options: TwineOptions
// MARK: - Run
@ -49,21 +40,20 @@ struct Twine: ParsableCommand {
// Generate strings files (lproj files)
for lang in options.langs {
Shell.shell(Self.twineExecutable,
Shell.shell([Self.twineExecutable,
"generate-localization-file", options.inputFile,
"--lang", "\(lang)",
"\(options.outputPath)/\(lang).lproj/\(inputFilenameWithoutExt).strings",
"--tags=ios,iosonly,iosOnly")
"\(options.outputPath)/\(lang).lproj/\(options.inputFilenameWithoutExt).strings",
"--tags=ios,iosonly,iosOnly"])
}
// Generate extension
var extensionFilePath: String { "\(options.extensionOutputPath)/\(inputFilenameWithoutExt).swift" }
Shell.shell(Self.twineExecutable,
Shell.shell([Self.twineExecutable,
"generate-localization-file", options.inputFile,
"--format", "apple-swift",
"--lang", "\(options.defaultLang)",
extensionFilePath,
"--tags=ios,iosonly,iosOnly")
options.extensionFilePath,
"--tags=ios,iosonly,iosOnly"])
print("[\(Self.toolName)] Strings generated")
}
@ -93,11 +83,10 @@ struct Twine: ParsableCommand {
Twine.exit(withError: error)
}
// "R2String+" is hardcoded in Twine formatter
let extensionFilePathGenerated = "\(options.extensionOutputPath)/R2String+\(inputFilenameWithoutExt).swift"
// Check if needed to regenerate
guard GeneratorChecker.shouldGenerate(force: options.forceGeneration, inputFilePath: options.inputFile, extensionFilePath: extensionFilePathGenerated) else {
guard GeneratorChecker.shouldGenerate(force: options.forceGeneration,
inputFilePath: options.inputFile,
extensionFilePath: options.extensionFilePathGenerated) else {
print("[\(Self.toolName)] Strings are already up to date :) ")
return false
}

View File

@ -1,5 +1,5 @@
//
// File.swift
// TwineOptions.swift
//
//
// Created by Thibaut Schmitt on 10/01/2022.
@ -28,6 +28,8 @@ struct TwineOptions: ParsableArguments {
var extensionOutputPath: String
}
// MARK: - Private var getter
extension TwineOptions {
var langs: [String] {
langsRaw
@ -35,3 +37,22 @@ extension TwineOptions {
.map { String($0) }
}
}
// MARK: - Computed var
extension TwineOptions {
var inputFilenameWithoutExt: String {
URL(fileURLWithPath: inputFile)
.deletingPathExtension()
.lastPathComponent
}
var extensionFilePath: String {
"\(extensionOutputPath)/\(inputFilenameWithoutExt).swift"
}
// "R2String+" is hardcoded in Twine formatter
var extensionFilePathGenerated: String {
"\(extensionOutputPath)/R2String+\(inputFilenameWithoutExt).swift"
}
}

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

@ -1,75 +0,0 @@
//
// TagsGenerator.swift
//
//
// Created by Thibaut Schmitt on 10/01/2022.
//
import Foundation
import ToolCore
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 extensionFooter = Self.getFooter()
let extensionContent: String = {
var content = ""
sections.forEach { section in
// Check that at least one string will be generated
guard section.hasOneOrMoreMatchingTags(tags: tags) else {
return // Go to next section
}
content += "\n\t// MARK: - \(section.name)"
section.definitions.forEach { definition in
if staticVar {
content += "\n\n\(definition.getStaticProperty(forLang: lang))"
} else {
content += "\n\n\(definition.getProperty(forLang: lang))"
}
}
content += "\n"
}
return content
}()
// Create file if not exists
let fileManager = FileManager()
if fileManager.fileExists(atPath: extensionFilePath) == false {
Shell.shell("touch", "\(extensionFilePath)")
}
// Create extension content
let extensionFileContent = [extensionHeader, extensionContent, extensionFooter].joined(separator: "\n")
// Write content
let extensionFilePathURL = URL(fileURLWithPath: extensionFilePath)
do {
try extensionFileContent.write(to: extensionFilePathURL, atomically: true, encoding: .utf8)
} catch (let error) {
let error = StringiumError.writeFile(extensionFilePath, error.localizedDescription)
print(error.localizedDescription)
Stringium.exit(withError: error)
}
}
private static func getHeader(extensionClassname: String) -> String {
"""
// Generated by ResgenSwift.Strings.Tags \(ResgenSwiftVersion)
// typelias Tags = String
import UIKit
extension \(extensionClassname) {
"""
}
private static func getFooter() -> String {
"""
}
"""
}
}

View File

@ -15,26 +15,29 @@ public class GeneratorChecker {
return true
}
// If inputFile is newer that generated extension -> Regenerate
let extensionFileURL = URL(fileURLWithPath: extensionFilePath)
let inputFileURL = URL(fileURLWithPath: inputFilePath)
let extensionRessourceValues = try? extensionFileURL.resourceValues(forKeys: [URLResourceKey.contentModificationDateKey])
let inputFileRessourceValues = try? inputFileURL.resourceValues(forKeys: [URLResourceKey.contentModificationDateKey])
if let extensionModificationDate = extensionRessourceValues?.contentModificationDate,
let inputFileModificationDate = inputFileRessourceValues?.contentModificationDate {
if inputFileModificationDate >= extensionModificationDate {
print("Input file is newer that generated extension.")
return true
} else {
return false
}
}
return Self.isFile(inputFilePath, moreRecenThan: extensionFilePath)
}
// ModificationDate not available for both file
print("⚠️ Could not compare file modication date. ⚠️")
return true
public static func isFile(_ fileOne: String, moreRecenThan fileTwo: String) -> Bool {
let fileOneURL = URL(fileURLWithPath: fileOne)
let fileTwoURL = URL(fileURLWithPath: fileTwo)
let fileOneRessourceValues = try? fileOneURL.resourceValues(forKeys: [URLResourceKey.contentModificationDateKey])
let fileTwoRessourceValues = try? fileTwoURL.resourceValues(forKeys: [URLResourceKey.contentModificationDateKey])
guard let fileOneModificationDate = fileOneRessourceValues?.contentModificationDate,
let fileTwoModificationDate = fileTwoRessourceValues?.contentModificationDate else {
print("⚠️ Could not compare file modication date. ⚠️ (assume than file is newer)")
// Date not available -> assume than fileOne is newer than fileTwo
return true
}
if fileOneModificationDate >= fileTwoModificationDate {
debugPrint("File one is more recent than file two.")
return true
}
return false
}
}

View File

@ -9,32 +9,48 @@ import Foundation
public class Shell {
@discardableResult
public static func shell(_ args: String...) -> (terminationStatus: Int32, output: String?) {
let task = Process()
task.launchPath = "/usr/bin/env"
task.arguments = args
let pipe = Pipe()
task.standardOutput = pipe
task.launch()
task.waitUntilExit()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
guard let output: String = String(data: data, encoding: .utf8) else {
return (terminationStatus: task.terminationStatus, output: nil)
}
return (terminationStatus: task.terminationStatus, output: output)
public static var environment: [String: String] {
ProcessInfo.processInfo.environment
}
// @discardableResult
// public static func shell(launchPath: String = "/usr/bin/env", _ args: String...) -> (terminationStatus: Int32, output: String?) {
// let task = Process()
// task.launchPath = launchPath
// task.arguments = args
//
// var currentEnv = ProcessInfo.processInfo.environment
// for (key, value) in environment {
// currentEnv[key] = value
// }
// task.environment = currentEnv
//
// let pipe = Pipe()
// task.standardOutput = pipe
// try? task.run()
// task.waitUntilExit()
//
// let data = pipe.fileHandleForReading.readDataToEndOfFile()
//
// guard let output: String = String(data: data, encoding: .utf8) else {
// return (terminationStatus: task.terminationStatus, output: nil)
// }
//
// return (terminationStatus: task.terminationStatus, output: output)
// }
@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
var currentEnv = ProcessInfo.processInfo.environment
for (key, value) in environment {
currentEnv[key] = value
}
task.environment = currentEnv
let pipe = Pipe()
task.standardOutput = pipe
task.launch()

View File

@ -59,6 +59,7 @@ public extension String {
}
func replaceTiltWithHomeDirectoryPath() -> Self {
// See NSString.expandingTildeInPath
replacingOccurrences(of: "~", with: "\(FileManager.default.homeDirectoryForCurrentUser.relativePath)")
}

View File

@ -7,4 +7,4 @@
import Foundation
public let ResgenSwiftVersion = "0.9"
public let ResgenSwiftVersion = "1.0"