Remove Shell invocation as many as possible (high cost in term of speed of execution)
Some checks failed
gitea-openium/resgen.swift/pipeline/head There was a failure building this commit

This commit is contained in:
2022-09-02 15:57:20 +02:00
parent 8723019732
commit e3258453bb
15 changed files with 95 additions and 119 deletions

View File

@ -44,16 +44,19 @@ struct Colors: ParsableCommand {
// Get colors to generate // Get colors to generate
let parsedColors = ColorFileParser.parse(options.inputFile, let parsedColors = ColorFileParser.parse(options.inputFile,
colorStyle: options.colorStyle) colorStyle: options.colorStyle)
// -> Time: 0.0020350217819213867 seconds
// Generate all colors in xcassets // Generate all colors in xcassets
ColorXcassetHelper.generateXcassetColors(colors: parsedColors, ColorXcassetHelper.generateXcassetColors(colors: parsedColors,
to: options.xcassetsPath) to: options.xcassetsPath)
// -> Time: 3.4505380392074585 seconds
// Generate extension // Generate extension
ColorExtensionGenerator.writeExtensionFile(colors: parsedColors, ColorExtensionGenerator.writeExtensionFile(colors: parsedColors,
staticVar: options.staticMembers, staticVar: options.staticMembers,
extensionName: options.extensionName, extensionName: options.extensionName,
extensionFilePath: options.extensionFilePath) extensionFilePath: options.extensionFilePath)
// -> Time: 0.0010340213775634766 seconds
print("[\(Self.toolName)] Colors generated") print("[\(Self.toolName)] Colors generated")
} }
@ -91,7 +94,18 @@ struct Colors: ParsableCommand {
// MARK: - Helpers // MARK: - Helpers
private func deleteCurrentColors() { 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)
}
}
} }
} }

View File

@ -10,9 +10,11 @@ import Foundation
enum ColorsToolError: Error { enum ColorsToolError: Error {
case badFormat(String) case badFormat(String)
case writeAsset(String) case writeAsset(String)
case createAssetFolder(String)
case writeExtension(String, String) case writeExtension(String, String)
case fileNotExists(String) case fileNotExists(String)
case badColorDefinition(String, String) case badColorDefinition(String, String)
case deleteExistingColors(String)
var description: String { var description: String {
switch self { switch self {
@ -22,6 +24,9 @@ enum ColorsToolError: Error {
case .writeAsset(let info): case .writeAsset(let info):
return "error:[\(Colors.toolName)] An error occured while writing color in Xcasset: \(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): case .writeExtension(let filename, let info):
return "error:[\(Colors.toolName)] An error occured while writing extension in \(filename): \(info)" return "error:[\(Colors.toolName)] An error occured while writing extension in \(filename): \(info)"
@ -29,7 +34,10 @@ enum ColorsToolError: Error {
return "error:[\(Colors.toolName)] File \(filename) does not exists" return "error:[\(Colors.toolName)] File \(filename) does not exists"
case .badColorDefinition(let lightColor, let darkColor): case .badColorDefinition(let lightColor, let darkColor):
return "error:[\(Colors.toolName)]One of these two colors has invalid synthax: -\(lightColor)- or -\(darkColor)-" return "error:[\(Colors.toolName)] One of these two colors has invalid synthax: -\(lightColor)- or -\(darkColor)-"
case .deleteExistingColors(let assetsFolder):
return "error:[\(Colors.toolName)] An error occured while deleting colors folder `\(assetsFolder)`"
} }
} }
} }

View File

@ -14,12 +14,6 @@ struct ColorExtensionGenerator {
let extensionClassname: String let extensionClassname: String
static func writeExtensionFile(colors: [ParsedColor], staticVar: Bool, extensionName: String, extensionFilePath: 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 // Create extension content
let extensionContent = [ let extensionContent = [
Self.getHeader(extensionClassname: extensionName), Self.getHeader(extensionClassname: extensionName),
@ -31,7 +25,7 @@ struct ColorExtensionGenerator {
// Write content // Write content
let extensionFilePathURL = URL(fileURLWithPath: extensionFilePath) let extensionFilePathURL = URL(fileURLWithPath: extensionFilePath)
do { do {
try extensionContent.write(to: extensionFilePathURL, atomically: true, encoding: .utf8) try extensionContent.write(to: extensionFilePathURL, atomically: false, encoding: .utf8)
} catch (let error) { } catch (let error) {
let error = ColorsToolError.writeExtension(extensionFilePath, error.localizedDescription) let error = ColorsToolError.writeExtension(extensionFilePath, error.localizedDescription)
print(error.localizedDescription) print(error.localizedDescription)

View File

@ -20,17 +20,24 @@ struct ColorXcassetHelper {
private static func generateColorSetAssets(from color: ParsedColor, to xcassetsPath: String) { private static func generateColorSetAssets(from color: ParsedColor, to xcassetsPath: String) {
// Create ColorSet // Create ColorSet
let colorSetPath = "\(xcassetsPath)/Colors/\(color.name).colorset" let colorSetPath = "\(xcassetsPath)/Colors/\(color.name).colorset"
Shell.shell(["mkdir",
"-p", "\(colorSetPath)"])
// Create Contents.json in ColorSet
let contentsJsonPath = "\(colorSetPath)/Contents.json" 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 // Write content in Contents.json
let contentsJsonPathURL = URL(fileURLWithPath: contentsJsonPath) let contentsJsonPathURL = URL(fileURLWithPath: contentsJsonPath)
do { do {
try color.contentsJSON().write(to: contentsJsonPathURL, atomically: true, encoding: .utf8) try color.contentsJSON().write(to: contentsJsonPathURL, atomically: false, encoding: .utf8)
} catch (let error) { } catch (let error) {
let error = ColorsToolError.writeAsset(error.localizedDescription) let error = ColorsToolError.writeAsset(error.localizedDescription)
print(error.localizedDescription) print(error.localizedDescription)

View File

@ -23,7 +23,7 @@ class ColorFileParser {
.replacingOccurrences(of: "=", with: "") // Keep compat with current file format .replacingOccurrences(of: "=", with: "") // Keep compat with current file format
guard colorLineCleanedUp.hasPrefix("#") == false, colorLineCleanedUp.isEmpty == false else { guard colorLineCleanedUp.hasPrefix("#") == false, colorLineCleanedUp.isEmpty == false else {
debugPrint("[\(Colors.toolName)] ⚠️ BadFormat or empty line (line number: \(lineNumber + 1)). Skip this line") // debugPrint("[\(Colors.toolName)] BadFormat or empty line (line number: \(lineNumber + 1)). Skip this line")
return nil return nil
} }

View File

@ -11,12 +11,6 @@ import ToolCore
class FontExtensionGenerator { class FontExtensionGenerator {
static func writeExtensionFile(fontsNames: [String], staticVar: Bool, extensionName: String, extensionFilePath: String) { 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 // Create extension content
let extensionContent = [ let extensionContent = [
Self.getHeader(extensionClassname: extensionName), Self.getHeader(extensionClassname: extensionName),
@ -29,7 +23,7 @@ class FontExtensionGenerator {
// Write content // Write content
let extensionFilePathURL = URL(fileURLWithPath: extensionFilePath) let extensionFilePathURL = URL(fileURLWithPath: extensionFilePath)
do { do {
try extensionContent.write(to: extensionFilePathURL, atomically: true, encoding: .utf8) try extensionContent.write(to: extensionFilePathURL, atomically: false, encoding: .utf8)
} catch (let error) { } catch (let error) {
let error = FontsToolError.writeExtension(extensionFilePath, error.localizedDescription) let error = FontsToolError.writeExtension(extensionFilePath, error.localizedDescription)
print(error.localizedDescription) print(error.localizedDescription)

View File

@ -46,9 +46,10 @@ struct Generate: ParsableCommand {
// Execute commands // Execute commands
configuration.runnableConfigurations configuration.runnableConfigurations
.forEach { .forEach {
let begin = Date()
$0.run(projectDirectory: options.projectDirectory, $0.run(projectDirectory: options.projectDirectory,
force: options.forceGeneration) force: options.forceGeneration)
print("\n") print("Took: \(Date().timeIntervalSince(begin))s\n")
} }
print("[\(Self.toolName)] Resgen ended") print("[\(Self.toolName)] Resgen ended")

View File

@ -12,7 +12,7 @@ extension ImagesConfiguration: Runnable {
var args = [String]() var args = [String]()
if force { if force {
args += ["-F"] // Images has a -f and -F options args += ["-f"] // Images has a -f and -F options
} }
args += [ args += [

View File

@ -31,12 +31,6 @@ class ImageExtensionGenerator {
return content return content
}() }()
// Create file if not exists
let fileManager = FileManager()
if fileManager.fileExists(atPath: extensionFilePath) == false {
Shell.shell(["touch", "\(extensionFilePath)"])
}
// Generate extension // Generate extension
Self.generateExtensionFile(extensionFilePath: extensionFilePath, extensionHeader, extensionContent, extensionFooter) Self.generateExtensionFile(extensionFilePath: extensionFilePath, extensionHeader, extensionContent, extensionFooter)
} }
@ -44,19 +38,13 @@ class ImageExtensionGenerator {
// MARK: - pragm // MARK: - pragm
private static func generateExtensionFile(extensionFilePath: String, _ args: String...) { 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 // Create extension content
let extensionContent = args.joined(separator: "\n") let extensionContent = args.joined(separator: "\n")
// Write content // Write content
let extensionFilePathURL = URL(fileURLWithPath: extensionFilePath) let extensionFilePathURL = URL(fileURLWithPath: extensionFilePath)
do { do {
try extensionContent.write(to: extensionFilePathURL, atomically: true, encoding: .utf8) try extensionContent.write(to: extensionFilePathURL, atomically: false, encoding: .utf8)
} catch (let error) { } catch (let error) {
let error = ImagesError.writeFile(extensionFilePath, error.localizedDescription) let error = ImagesError.writeFile(extensionFilePath, error.localizedDescription)
print(error.localizedDescription) print(error.localizedDescription)

View File

@ -52,10 +52,9 @@ class XcassetsGenerator {
Images.exit(withError: error) Images.exit(withError: error)
}() }()
// Create imageset folder // Create imageset folder name
let imagesetName = "\(parsedImage.name).imageset" let imagesetName = "\(parsedImage.name).imageset"
let imagesetPath = "\(xcassetsPath)/\(imagesetName)" let imagesetPath = "\(xcassetsPath)/\(imagesetName)"
Shell.shell(["mkdir", "-p", imagesetPath])
// Store managed images path // Store managed images path
generatedAssetsPaths.append(imagesetName) generatedAssetsPaths.append(imagesetName)
@ -66,11 +65,23 @@ class XcassetsGenerator {
let output3x = "\(imagesetPath)/\(parsedImage.name)@3x.\(XcassetsGenerator.outputImageExtension)" let output3x = "\(imagesetPath)/\(parsedImage.name)@3x.\(XcassetsGenerator.outputImageExtension)"
// Check if we need to convert image // Check if we need to convert image
if self.shouldBypassGeneration(for: parsedImage, xcassetImagePath: output1x) { guard self.shouldGenerate(inputImagePath: imageData.path, xcassetImagePath: output1x) else {
print("\(parsedImage.name) -> Not regenerating") //print("\(parsedImage.name) -> Not regenerating")
return 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 // Convert image
let convertArguments = parsedImage.convertArguments let convertArguments = parsedImage.convertArguments
if imageData.ext == "svg" { if imageData.ext == "svg" {
@ -110,12 +121,9 @@ class XcassetsGenerator {
// Write Content.json // Write Content.json
let imagesetContentJson = parsedImage.contentJson let imagesetContentJson = parsedImage.contentJson
let contentJsonFilePath = "\(imagesetPath)/Contents.json" let contentJsonFilePath = "\(imagesetPath)/Contents.json"
if fileManager.fileExists(atPath: contentJsonFilePath) == false {
Shell.shell(["touch", "\(contentJsonFilePath)"])
}
let contentJsonFilePathURL = URL(fileURLWithPath: 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") print("\(parsedImage.name) -> Generated")
} }
@ -153,43 +161,11 @@ class XcassetsGenerator {
// MARK: - Helpers: bypass generation // MARK: - Helpers: bypass generation
private func shouldBypassGeneration(for image: ParsedImage, xcassetImagePath: String) -> Bool { private func shouldGenerate(inputImagePath: String, xcassetImagePath: String) -> Bool {
if forceGeneration { if forceGeneration {
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 {
return true return true
} }
return false return GeneratorChecker.isFile(inputImagePath, moreRecenThan: xcassetImagePath)
} }
} }

View File

@ -14,6 +14,7 @@ enum ImagesError: Error {
case getFileAttributed(String, String) case getFileAttributed(String, String)
case rsvgConvertNotFound case rsvgConvertNotFound
case writeFile(String, String) case writeFile(String, String)
case createAssetFolder(String)
case unknown(String) case unknown(String)
var localizedDescription: String { var localizedDescription: String {
@ -36,6 +37,9 @@ enum ImagesError: Error {
case .writeFile(let subErrorDescription, let filename): case .writeFile(let subErrorDescription, let filename):
return " error:[\(Images.toolName)] An error occured while writing content to \(filename): \(subErrorDescription)" 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): case .unknown(let errorDescription):
return " error:[\(Images.toolName)] Unknown error: \(errorDescription)" return " error:[\(Images.toolName)] Unknown error: \(errorDescription)"
} }

View File

@ -28,7 +28,7 @@ class StringsFileGenerator {
let stringsFilePath = "\(outputPath)/\(lang).lproj/\(inputFilenameWithoutExt).strings" let stringsFilePath = "\(outputPath)/\(lang).lproj/\(inputFilenameWithoutExt).strings"
let stringsFilePathURL = URL(fileURLWithPath: stringsFilePath) let stringsFilePathURL = URL(fileURLWithPath: stringsFilePath)
do { do {
try fileContent.write(to: stringsFilePathURL, atomically: true, encoding: .utf8) try fileContent.write(to: stringsFilePathURL, atomically: false, encoding: .utf8)
} catch (let error) { } catch (let error) {
let error = StringiumError.writeFile(error.localizedDescription, stringsFilePath) let error = StringiumError.writeFile(error.localizedDescription, stringsFilePath)
print(error.localizedDescription) print(error.localizedDescription)
@ -115,19 +115,13 @@ class StringsFileGenerator {
return content return content
}() }()
// Create file if not exists
let fileManager = FileManager()
if fileManager.fileExists(atPath: extensionFilePath) == false {
Shell.shell(["touch", "\(extensionFilePath)"])
}
// Create extension content // Create extension content
let extensionFileContent = [extensionHeader, extensionContent, extensionFooter].joined(separator: "\n") let extensionFileContent = [extensionHeader, extensionContent, extensionFooter].joined(separator: "\n")
// Write content // Write content
let extensionFilePathURL = URL(fileURLWithPath: extensionFilePath) let extensionFilePathURL = URL(fileURLWithPath: extensionFilePath)
do { do {
try extensionFileContent.write(to: extensionFilePathURL, atomically: true, encoding: .utf8) try extensionFileContent.write(to: extensionFilePathURL, atomically: false, encoding: .utf8)
} catch (let error) { } catch (let error) {
let error = StringiumError.writeFile(extensionFilePath, error.localizedDescription) let error = StringiumError.writeFile(extensionFilePath, error.localizedDescription)
print(error.localizedDescription) print(error.localizedDescription)

View File

@ -35,19 +35,13 @@ class TagsGenerator {
return content return content
}() }()
// Create file if not exists
let fileManager = FileManager()
if fileManager.fileExists(atPath: extensionFilePath) == false {
Shell.shell(["touch", "\(extensionFilePath)"])
}
// Create extension content // Create extension content
let extensionFileContent = [extensionHeader, extensionContent, extensionFooter].joined(separator: "\n") let extensionFileContent = [extensionHeader, extensionContent, extensionFooter].joined(separator: "\n")
// Write content // Write content
let extensionFilePathURL = URL(fileURLWithPath: extensionFilePath) let extensionFilePathURL = URL(fileURLWithPath: extensionFilePath)
do { do {
try extensionFileContent.write(to: extensionFilePathURL, atomically: true, encoding: .utf8) try extensionFileContent.write(to: extensionFilePathURL, atomically: false, encoding: .utf8)
} catch (let error) { } catch (let error) {
let error = StringiumError.writeFile(extensionFilePath, error.localizedDescription) let error = StringiumError.writeFile(extensionFilePath, error.localizedDescription)
print(error.localizedDescription) print(error.localizedDescription)

View File

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

View File

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