Publish v1.0
Some checks failed
gitea-openium/resgen.swift/pipeline/head There was a failure building this commit
Some checks failed
gitea-openium/resgen.swift/pipeline/head There was a failure building this commit
Reviewed-on: #1
This commit is contained in:
@ -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)-"
|
||||
}
|
||||
}
|
||||
}
|
@ -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]))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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")!
|
||||
// }
|
@ -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)"
|
||||
}
|
||||
}
|
||||
}
|
@ -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"
|
||||
*/
|
43
Sources/ResgenSwift/Colors/ColorsToolError.swift
Normal file
43
Sources/ResgenSwift/Colors/ColorsToolError.swift
Normal 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)`"
|
||||
}
|
||||
}
|
||||
}
|
@ -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)"
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
//
|
||||
// File.swift
|
||||
// ColorStyle.swift
|
||||
//
|
||||
//
|
||||
// Created by Thibaut Schmitt on 29/08/2022.
|
@ -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 """
|
56
Sources/ResgenSwift/Colors/Parser/ColorFileParser.swift
Normal file
56
Sources/ResgenSwift/Colors/Parser/ColorFileParser.swift
Normal 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]))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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)"
|
||||
}
|
||||
}
|
@ -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()
|
@ -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)"
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
@ -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
|
||||
}
|
@ -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()
|
@ -1,5 +1,5 @@
|
||||
//
|
||||
// File.swift
|
||||
// FontName.swift
|
||||
//
|
||||
//
|
||||
// Created by Thibaut Schmitt on 29/08/2022.
|
@ -1,5 +1,5 @@
|
||||
//
|
||||
// File.swift
|
||||
// FontFileParser.swift
|
||||
//
|
||||
//
|
||||
// Created by Thibaut Schmitt on 29/08/2022.
|
@ -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
|
||||
}
|
||||
}
|
57
Sources/ResgenSwift/Generate/Generate.swift
Normal file
57
Sources/ResgenSwift/Generate/Generate.swift
Normal 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")
|
||||
}
|
||||
}
|
30
Sources/ResgenSwift/Generate/GenerateError.swift
Normal file
30
Sources/ResgenSwift/Generate/GenerateError.swift
Normal 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)"
|
||||
}
|
||||
}
|
||||
}
|
28
Sources/ResgenSwift/Generate/GenerateOptions.swift
Normal file
28
Sources/ResgenSwift/Generate/GenerateOptions.swift
Normal 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
|
||||
}
|
180
Sources/ResgenSwift/Generate/Model/ConfigurationFile.swift
Normal file
180
Sources/ResgenSwift/Generate/Model/ConfigurationFile.swift
Normal 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 ?? "-")
|
||||
"""
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
13
Sources/ResgenSwift/Generate/Runnable/Runnable.swift
Normal file
13
Sources/ResgenSwift/Generate/Runnable/Runnable.swift
Normal 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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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
|
@ -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 {
|
||||
"""
|
||||
}
|
||||
"""
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
@ -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()
|
47
Sources/ResgenSwift/Images/ImagesError.swift
Normal file
47
Sources/ResgenSwift/Images/ImagesError.swift
Normal 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)"
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
@ -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 {
|
||||
"""
|
||||
}
|
85
Sources/ResgenSwift/Strings/Generator/TagsGenerator.swift
Normal file
85
Sources/ResgenSwift/Strings/Generator/TagsGenerator.swift
Normal 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 {
|
||||
"""
|
||||
}
|
||||
"""
|
||||
}
|
||||
}
|
@ -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")
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -26,5 +26,5 @@ struct Strings: ParsableCommand {
|
||||
)
|
||||
}
|
||||
|
||||
Strings.main()
|
||||
//Strings.main()
|
||||
|
@ -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
|
||||
}
|
@ -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)"
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
@ -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()
|
||||
|
@ -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 {
|
||||
"""
|
||||
}
|
||||
"""
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -59,6 +59,7 @@ public extension String {
|
||||
}
|
||||
|
||||
func replaceTiltWithHomeDirectoryPath() -> Self {
|
||||
// See NSString.expandingTildeInPath
|
||||
replacingOccurrences(of: "~", with: "\(FileManager.default.homeDirectoryForCurrentUser.relativePath)")
|
||||
}
|
||||
|
||||
|
@ -7,4 +7,4 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
public let ResgenSwiftVersion = "0.9"
|
||||
public let ResgenSwiftVersion = "1.0"
|
||||
|
Reference in New Issue
Block a user