From e3258453bb04667525f977e184e5c5a8523f8535 Mon Sep 17 00:00:00 2001 From: Thibaut Schmitt Date: Fri, 2 Sep 2022 15:57:20 +0200 Subject: [PATCH] Remove Shell invocation as many as possible (high cost in term of speed of execution) --- Sources/ResgenSwift/Colors/Colors.swift | 24 ++++++-- .../ResgenSwift/Colors/ColorsToolError.swift | 10 +++- .../Generator/ColorExtensionGenerator.swift | 8 +-- .../Colors/Generator/ColorXcassetHelper.swift | 19 ++++-- .../Colors/Parser/ColorFileParser.swift | 2 +- .../Generator/FontToolContentGenerator.swift | 8 +-- Sources/ResgenSwift/Generate/Generate.swift | 3 +- ...ImagesConfiguration+ShellCommandable.swift | 2 +- .../Generator/ImageExtensionGenerator.swift | 14 +---- .../Images/Generator/XcassetsGenerator.swift | 60 ++++++------------- Sources/ResgenSwift/Images/ImagesError.swift | 4 ++ .../Generator/StringsFileGenerator.swift | 10 +--- .../Strings/Generator/TagsGenerator.swift | 8 +-- .../Strings/Model/Definition.swift | 1 - Sources/ToolCore/GeneratorChecker.swift | 41 +++++++------ 15 files changed, 95 insertions(+), 119 deletions(-) diff --git a/Sources/ResgenSwift/Colors/Colors.swift b/Sources/ResgenSwift/Colors/Colors.swift index 1a26778..1f161f2 100644 --- a/Sources/ResgenSwift/Colors/Colors.swift +++ b/Sources/ResgenSwift/Colors/Colors.swift @@ -40,21 +40,24 @@ struct Colors: 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: options.staticMembers, extensionName: options.extensionName, extensionFilePath: options.extensionFilePath) - + // -> Time: 0.0010340213775634766 seconds + print("[\(Self.toolName)] Colors generated") } @@ -91,7 +94,18 @@ struct Colors: 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) + } + } } } diff --git a/Sources/ResgenSwift/Colors/ColorsToolError.swift b/Sources/ResgenSwift/Colors/ColorsToolError.swift index 7bfc860..a4310e5 100644 --- a/Sources/ResgenSwift/Colors/ColorsToolError.swift +++ b/Sources/ResgenSwift/Colors/ColorsToolError.swift @@ -10,9 +10,11 @@ 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 { @@ -21,6 +23,9 @@ enum ColorsToolError: Error { 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)" @@ -29,7 +34,10 @@ enum ColorsToolError: Error { 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)-" + 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)`" } } } diff --git a/Sources/ResgenSwift/Colors/Generator/ColorExtensionGenerator.swift b/Sources/ResgenSwift/Colors/Generator/ColorExtensionGenerator.swift index 28a0426..4dd4642 100644 --- a/Sources/ResgenSwift/Colors/Generator/ColorExtensionGenerator.swift +++ b/Sources/ResgenSwift/Colors/Generator/ColorExtensionGenerator.swift @@ -14,12 +14,6 @@ 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 = [ Self.getHeader(extensionClassname: extensionName), @@ -31,7 +25,7 @@ struct ColorExtensionGenerator { // Write content let extensionFilePathURL = URL(fileURLWithPath: extensionFilePath) do { - try extensionContent.write(to: extensionFilePathURL, atomically: true, encoding: .utf8) + try extensionContent.write(to: extensionFilePathURL, atomically: false, encoding: .utf8) } catch (let error) { let error = ColorsToolError.writeExtension(extensionFilePath, error.localizedDescription) print(error.localizedDescription) diff --git a/Sources/ResgenSwift/Colors/Generator/ColorXcassetHelper.swift b/Sources/ResgenSwift/Colors/Generator/ColorXcassetHelper.swift index 34cd3b1..aa4c0db 100644 --- a/Sources/ResgenSwift/Colors/Generator/ColorXcassetHelper.swift +++ b/Sources/ResgenSwift/Colors/Generator/ColorXcassetHelper.swift @@ -20,17 +20,24 @@ 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 = ColorsToolError.writeAsset(error.localizedDescription) print(error.localizedDescription) diff --git a/Sources/ResgenSwift/Colors/Parser/ColorFileParser.swift b/Sources/ResgenSwift/Colors/Parser/ColorFileParser.swift index 3f02bb1..188fffb 100644 --- a/Sources/ResgenSwift/Colors/Parser/ColorFileParser.swift +++ b/Sources/ResgenSwift/Colors/Parser/ColorFileParser.swift @@ -23,7 +23,7 @@ class ColorFileParser { .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") + // debugPrint("[\(Colors.toolName)] ⚠️ BadFormat or empty line (line number: \(lineNumber + 1)). Skip this line") return nil } diff --git a/Sources/ResgenSwift/Fonts/Generator/FontToolContentGenerator.swift b/Sources/ResgenSwift/Fonts/Generator/FontToolContentGenerator.swift index 490408e..7e8a472 100644 --- a/Sources/ResgenSwift/Fonts/Generator/FontToolContentGenerator.swift +++ b/Sources/ResgenSwift/Fonts/Generator/FontToolContentGenerator.swift @@ -11,12 +11,6 @@ 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 = [ Self.getHeader(extensionClassname: extensionName), @@ -29,7 +23,7 @@ class FontExtensionGenerator { // Write content let extensionFilePathURL = URL(fileURLWithPath: extensionFilePath) do { - try extensionContent.write(to: extensionFilePathURL, atomically: true, encoding: .utf8) + try extensionContent.write(to: extensionFilePathURL, atomically: false, encoding: .utf8) } catch (let error) { let error = FontsToolError.writeExtension(extensionFilePath, error.localizedDescription) print(error.localizedDescription) diff --git a/Sources/ResgenSwift/Generate/Generate.swift b/Sources/ResgenSwift/Generate/Generate.swift index fd9c1b5..54ffc43 100644 --- a/Sources/ResgenSwift/Generate/Generate.swift +++ b/Sources/ResgenSwift/Generate/Generate.swift @@ -46,9 +46,10 @@ struct Generate: ParsableCommand { // Execute commands configuration.runnableConfigurations .forEach { + let begin = Date() $0.run(projectDirectory: options.projectDirectory, force: options.forceGeneration) - print("\n") + print("Took: \(Date().timeIntervalSince(begin))s\n") } print("[\(Self.toolName)] Resgen ended") diff --git a/Sources/ResgenSwift/Generate/Runnable/ImagesConfiguration+ShellCommandable.swift b/Sources/ResgenSwift/Generate/Runnable/ImagesConfiguration+ShellCommandable.swift index a99fd16..db1bb4d 100644 --- a/Sources/ResgenSwift/Generate/Runnable/ImagesConfiguration+ShellCommandable.swift +++ b/Sources/ResgenSwift/Generate/Runnable/ImagesConfiguration+ShellCommandable.swift @@ -12,7 +12,7 @@ extension ImagesConfiguration: Runnable { var args = [String]() if force { - args += ["-F"] // Images has a -f and -F options + args += ["-f"] // Images has a -f and -F options } args += [ diff --git a/Sources/ResgenSwift/Images/Generator/ImageExtensionGenerator.swift b/Sources/ResgenSwift/Images/Generator/ImageExtensionGenerator.swift index 7778d32..73f6e4d 100644 --- a/Sources/ResgenSwift/Images/Generator/ImageExtensionGenerator.swift +++ b/Sources/ResgenSwift/Images/Generator/ImageExtensionGenerator.swift @@ -31,12 +31,6 @@ class ImageExtensionGenerator { 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) } @@ -44,19 +38,13 @@ class ImageExtensionGenerator { // 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) + try extensionContent.write(to: extensionFilePathURL, atomically: false, encoding: .utf8) } catch (let error) { let error = ImagesError.writeFile(extensionFilePath, error.localizedDescription) print(error.localizedDescription) diff --git a/Sources/ResgenSwift/Images/Generator/XcassetsGenerator.swift b/Sources/ResgenSwift/Images/Generator/XcassetsGenerator.swift index d456bef..6573c92 100644 --- a/Sources/ResgenSwift/Images/Generator/XcassetsGenerator.swift +++ b/Sources/ResgenSwift/Images/Generator/XcassetsGenerator.swift @@ -52,10 +52,9 @@ class XcassetsGenerator { 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" { @@ -110,12 +121,9 @@ class XcassetsGenerator { // 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") } @@ -153,43 +161,11 @@ class XcassetsGenerator { // MARK: - Helpers: bypass generation - private func shouldBypassGeneration(for image: ParsedImage, xcassetImagePath: String) -> Bool { + private func shouldGenerate(inputImagePath: String, xcassetImagePath: String) -> Bool { 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 false + return GeneratorChecker.isFile(inputImagePath, moreRecenThan: xcassetImagePath) } - } diff --git a/Sources/ResgenSwift/Images/ImagesError.swift b/Sources/ResgenSwift/Images/ImagesError.swift index 8afb628..a425fc4 100644 --- a/Sources/ResgenSwift/Images/ImagesError.swift +++ b/Sources/ResgenSwift/Images/ImagesError.swift @@ -14,6 +14,7 @@ enum ImagesError: Error { case getFileAttributed(String, String) case rsvgConvertNotFound case writeFile(String, String) + case createAssetFolder(String) case unknown(String) var localizedDescription: String { @@ -35,6 +36,9 @@ enum ImagesError: Error { 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)" diff --git a/Sources/ResgenSwift/Strings/Generator/StringsFileGenerator.swift b/Sources/ResgenSwift/Strings/Generator/StringsFileGenerator.swift index 6e0f697..1ac694c 100644 --- a/Sources/ResgenSwift/Strings/Generator/StringsFileGenerator.swift +++ b/Sources/ResgenSwift/Strings/Generator/StringsFileGenerator.swift @@ -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) @@ -115,19 +115,13 @@ class StringsFileGenerator { 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) + try extensionFileContent.write(to: extensionFilePathURL, atomically: false, encoding: .utf8) } catch (let error) { let error = StringiumError.writeFile(extensionFilePath, error.localizedDescription) print(error.localizedDescription) diff --git a/Sources/ResgenSwift/Strings/Generator/TagsGenerator.swift b/Sources/ResgenSwift/Strings/Generator/TagsGenerator.swift index 38a1a9f..888aafb 100644 --- a/Sources/ResgenSwift/Strings/Generator/TagsGenerator.swift +++ b/Sources/ResgenSwift/Strings/Generator/TagsGenerator.swift @@ -35,19 +35,13 @@ class TagsGenerator { 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) + try extensionFileContent.write(to: extensionFilePathURL, atomically: false, encoding: .utf8) } catch (let error) { let error = StringiumError.writeFile(extensionFilePath, error.localizedDescription) print(error.localizedDescription) diff --git a/Sources/ResgenSwift/Strings/Model/Definition.swift b/Sources/ResgenSwift/Strings/Model/Definition.swift index c600bdf..b7dbe44 100644 --- a/Sources/ResgenSwift/Strings/Model/Definition.swift +++ b/Sources/ResgenSwift/Strings/Model/Definition.swift @@ -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") diff --git a/Sources/ToolCore/GeneratorChecker.swift b/Sources/ToolCore/GeneratorChecker.swift index 858f7af..f96c6a8 100644 --- a/Sources/ToolCore/GeneratorChecker.swift +++ b/Sources/ToolCore/GeneratorChecker.swift @@ -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 } }