diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/Imagium.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/Imagium.xcscheme new file mode 100644 index 0000000..aa41879 --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcschemes/Imagium.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/ResgenSwift-Package.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/ResgenSwift-Package.xcscheme index 63f224c..7d57f6c 100644 --- a/.swiftpm/xcode/xcshareddata/xcschemes/ResgenSwift-Package.xcscheme +++ b/.swiftpm/xcode/xcshareddata/xcschemes/ResgenSwift-Package.xcscheme @@ -118,6 +118,34 @@ ReferencedContainer = "container:"> + + + + + + + + @@ -178,9 +206,9 @@ runnableDebuggingMode = "0"> diff --git a/Package.swift b/Package.swift index fcfa735..b5095cf 100644 --- a/Package.swift +++ b/Package.swift @@ -15,17 +15,17 @@ let package = Package( // Targets can depend on other targets in this package, and on products in packages this package depends on. .target( name: "ResgenSwift", - dependencies: ["FontToolCore", "ColorToolCore", "Strings"] + dependencies: ["FontTool", "ColorTool", "Strings", "Imagium"] ), .target( - name: "FontToolCore", + name: "FontTool", dependencies: [ "CLIToolCore", .product(name: "ArgumentParser", package: "swift-argument-parser") ] ), .target( - name: "ColorToolCore", + name: "ColorTool", dependencies: [ "CLIToolCore", .product(name: "ArgumentParser", package: "swift-argument-parser") @@ -33,6 +33,14 @@ let package = Package( ), .target( name: "Strings", + dependencies: [ + "CLIToolCore", + .product(name: "ArgumentParser", package: "swift-argument-parser") + ], + sources: ["."] // Force include all subdirectories + ), + .target( + name: "Imagium", dependencies: [ "CLIToolCore", .product(name: "ArgumentParser", package: "swift-argument-parser") diff --git a/SampleFiles/Colors/Generated/UIColor+ColorGenAllScript.swift b/SampleFiles/Colors/Generated/UIColor+ColorGenAllScript.swift index 5b8cce8..23713af 100644 --- a/SampleFiles/Colors/Generated/UIColor+ColorGenAllScript.swift +++ b/SampleFiles/Colors/Generated/UIColor+ColorGenAllScript.swift @@ -1,4 +1,4 @@ -// Generated from ColorToolCore at 2022-01-10 10:57:08 +0000 +// Generated from ColorToolCore at 2022-02-14 09:30:19 +0000 import UIKit diff --git a/SampleFiles/Fonts/Generated/UIFontoptions.+FontGenAllScript.swift b/SampleFiles/Fonts/Generated/UIFontoptions.+FontGenAllScript.swift new file mode 100644 index 0000000..398823b --- /dev/null +++ b/SampleFiles/Fonts/Generated/UIFontoptions.+FontGenAllScript.swift @@ -0,0 +1,62 @@ +// Generated from FontToolCore + +import UIKit + +extension UIFont { + + enum FontName: String { + case LatoItalic = "Lato-Italic" + case LatoLightItalic = "Lato-LightItalic" + case LatoHairline = "Lato-Hairline" + case LatoBold = "Lato-Bold" + case LatoBlack = "Lato-Black" + case LatoRegular = "Lato-Regular" + case LatoBlackItalic = "Lato-BlackItalic" + case LatoBoldItalic = "Lato-BoldItalic" + case LatoLight = "Lato-Light" + case LatoHairlineItalic = "Lato-HairlineItalic" + } + + // MARK: - Getter + + static let LatoItalic: ((_ size: CGFloat) -> UIFont) = { size in + UIFont(name: FontName.LatoItalic.rawValue, size: size)! + } + + static let LatoLightItalic: ((_ size: CGFloat) -> UIFont) = { size in + UIFont(name: FontName.LatoLightItalic.rawValue, size: size)! + } + + static let LatoHairline: ((_ size: CGFloat) -> UIFont) = { size in + UIFont(name: FontName.LatoHairline.rawValue, size: size)! + } + + static let LatoBold: ((_ size: CGFloat) -> UIFont) = { size in + UIFont(name: FontName.LatoBold.rawValue, size: size)! + } + + static let LatoBlack: ((_ size: CGFloat) -> UIFont) = { size in + UIFont(name: FontName.LatoBlack.rawValue, size: size)! + } + + static let LatoRegular: ((_ size: CGFloat) -> UIFont) = { size in + UIFont(name: FontName.LatoRegular.rawValue, size: size)! + } + + static let LatoBlackItalic: ((_ size: CGFloat) -> UIFont) = { size in + UIFont(name: FontName.LatoBlackItalic.rawValue, size: size)! + } + + static let LatoBoldItalic: ((_ size: CGFloat) -> UIFont) = { size in + UIFont(name: FontName.LatoBoldItalic.rawValue, size: size)! + } + + static let LatoLight: ((_ size: CGFloat) -> UIFont) = { size in + UIFont(name: FontName.LatoLight.rawValue, size: size)! + } + + static let LatoHairlineItalic: ((_ size: CGFloat) -> UIFont) = { size in + UIFont(name: FontName.LatoHairlineItalic.rawValue, size: size)! + } + +} \ No newline at end of file diff --git a/SampleFiles/Images/Generated/UIImage+ImageGenAllScript.swift b/SampleFiles/Images/Generated/UIImage+ImageGenAllScript.swift new file mode 100644 index 0000000..ca3752e --- /dev/null +++ b/SampleFiles/Images/Generated/UIImage+ImageGenAllScript.swift @@ -0,0 +1,32 @@ +// Generated from Imagium at 2022-02-14 09:30:23 +0000 +// Images from sampleImages + +import UIKit + +extension UIImage { + + static var article_notification_pull_detail: UIImage { + UIImage(named: "article_notification_pull_detail")! + } + + static var article_notification_pull: UIImage { + UIImage(named: "article_notification_pull")! + } + + static var new_article: UIImage { + UIImage(named: "new_article")! + } + + static var welcome_background: UIImage { + UIImage(named: "welcome_background")! + } + + static var article_trash: UIImage { + UIImage(named: "article_trash")! + } + + static var ic_close_article: UIImage { + UIImage(named: "ic_close_article")! + } + +} \ No newline at end of file diff --git a/SampleFiles/Images/InputImages/Articles/New/new_article.svg b/SampleFiles/Images/InputImages/Articles/New/new_article.svg new file mode 100644 index 0000000..7bc8f21 --- /dev/null +++ b/SampleFiles/Images/InputImages/Articles/New/new_article.svg @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SampleFiles/Images/InputImages/Articles/article_notification_pull.svg b/SampleFiles/Images/InputImages/Articles/article_notification_pull.svg new file mode 100644 index 0000000..051989a --- /dev/null +++ b/SampleFiles/Images/InputImages/Articles/article_notification_pull.svg @@ -0,0 +1,21 @@ + + + article_notification_pull + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/SampleFiles/Images/InputImages/Articles/article_notification_pull_detail.svg b/SampleFiles/Images/InputImages/Articles/article_notification_pull_detail.svg new file mode 100644 index 0000000..15ae41f --- /dev/null +++ b/SampleFiles/Images/InputImages/Articles/article_notification_pull_detail.svg @@ -0,0 +1,21 @@ + + + article_notification_pull_detail + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/SampleFiles/Images/InputImages/Articles/article_trash.svg b/SampleFiles/Images/InputImages/Articles/article_trash.svg new file mode 100644 index 0000000..8a65860 --- /dev/null +++ b/SampleFiles/Images/InputImages/Articles/article_trash.svg @@ -0,0 +1,4 @@ + + + + diff --git a/SampleFiles/Images/InputImages/ic_close_article.svg b/SampleFiles/Images/InputImages/ic_close_article.svg new file mode 100644 index 0000000..9e247a8 --- /dev/null +++ b/SampleFiles/Images/InputImages/ic_close_article.svg @@ -0,0 +1,4 @@ + + + diff --git a/SampleFiles/Images/InputImages/welcome_background.png b/SampleFiles/Images/InputImages/welcome_background.png new file mode 100644 index 0000000..a604692 Binary files /dev/null and b/SampleFiles/Images/InputImages/welcome_background.png differ diff --git a/SampleFiles/Images/imagium.xcassets/article_notification_pull.imageset/Contents.json b/SampleFiles/Images/imagium.xcassets/article_notification_pull.imageset/Contents.json new file mode 100644 index 0000000..a83e01f --- /dev/null +++ b/SampleFiles/Images/imagium.xcassets/article_notification_pull.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "article_notification_pull.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "article_notification_pull@2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x", + "filename" : "article_notification_pull@3x.png" + } + ], + "info" : { + "version" : 1, + "author" : "ResgenSwift-Imagium" + } +} \ No newline at end of file diff --git a/SampleFiles/Images/imagium.xcassets/article_notification_pull.imageset/article_notification_pull.png b/SampleFiles/Images/imagium.xcassets/article_notification_pull.imageset/article_notification_pull.png new file mode 100644 index 0000000..59607c1 Binary files /dev/null and b/SampleFiles/Images/imagium.xcassets/article_notification_pull.imageset/article_notification_pull.png differ diff --git a/SampleFiles/Images/imagium.xcassets/article_notification_pull.imageset/article_notification_pull@2x.png b/SampleFiles/Images/imagium.xcassets/article_notification_pull.imageset/article_notification_pull@2x.png new file mode 100644 index 0000000..ac7e5af Binary files /dev/null and b/SampleFiles/Images/imagium.xcassets/article_notification_pull.imageset/article_notification_pull@2x.png differ diff --git a/SampleFiles/Images/imagium.xcassets/article_notification_pull.imageset/article_notification_pull@3x.png b/SampleFiles/Images/imagium.xcassets/article_notification_pull.imageset/article_notification_pull@3x.png new file mode 100644 index 0000000..5546ca1 Binary files /dev/null and b/SampleFiles/Images/imagium.xcassets/article_notification_pull.imageset/article_notification_pull@3x.png differ diff --git a/SampleFiles/Images/imagium.xcassets/article_notification_pull_detail.imageset/Contents.json b/SampleFiles/Images/imagium.xcassets/article_notification_pull_detail.imageset/Contents.json new file mode 100644 index 0000000..caa7fed --- /dev/null +++ b/SampleFiles/Images/imagium.xcassets/article_notification_pull_detail.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "article_notification_pull_detail.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "article_notification_pull_detail@2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x", + "filename" : "article_notification_pull_detail@3x.png" + } + ], + "info" : { + "version" : 1, + "author" : "ResgenSwift-Imagium" + } +} \ No newline at end of file diff --git a/SampleFiles/Images/imagium.xcassets/article_notification_pull_detail.imageset/article_notification_pull_detail.png b/SampleFiles/Images/imagium.xcassets/article_notification_pull_detail.imageset/article_notification_pull_detail.png new file mode 100644 index 0000000..fcb75e1 Binary files /dev/null and b/SampleFiles/Images/imagium.xcassets/article_notification_pull_detail.imageset/article_notification_pull_detail.png differ diff --git a/SampleFiles/Images/imagium.xcassets/article_notification_pull_detail.imageset/article_notification_pull_detail@2x.png b/SampleFiles/Images/imagium.xcassets/article_notification_pull_detail.imageset/article_notification_pull_detail@2x.png new file mode 100644 index 0000000..3966828 Binary files /dev/null and b/SampleFiles/Images/imagium.xcassets/article_notification_pull_detail.imageset/article_notification_pull_detail@2x.png differ diff --git a/SampleFiles/Images/imagium.xcassets/article_notification_pull_detail.imageset/article_notification_pull_detail@3x.png b/SampleFiles/Images/imagium.xcassets/article_notification_pull_detail.imageset/article_notification_pull_detail@3x.png new file mode 100644 index 0000000..b16455b Binary files /dev/null and b/SampleFiles/Images/imagium.xcassets/article_notification_pull_detail.imageset/article_notification_pull_detail@3x.png differ diff --git a/SampleFiles/Images/imagium.xcassets/article_trash.imageset/Contents.json b/SampleFiles/Images/imagium.xcassets/article_trash.imageset/Contents.json new file mode 100644 index 0000000..1ec6fce --- /dev/null +++ b/SampleFiles/Images/imagium.xcassets/article_trash.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "article_trash.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "article_trash@2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x", + "filename" : "article_trash@3x.png" + } + ], + "info" : { + "version" : 1, + "author" : "ResgenSwift-Imagium" + } +} \ No newline at end of file diff --git a/SampleFiles/Images/imagium.xcassets/article_trash.imageset/article_trash.png b/SampleFiles/Images/imagium.xcassets/article_trash.imageset/article_trash.png new file mode 100644 index 0000000..3b1d655 Binary files /dev/null and b/SampleFiles/Images/imagium.xcassets/article_trash.imageset/article_trash.png differ diff --git a/SampleFiles/Images/imagium.xcassets/article_trash.imageset/article_trash@2x.png b/SampleFiles/Images/imagium.xcassets/article_trash.imageset/article_trash@2x.png new file mode 100644 index 0000000..9232208 Binary files /dev/null and b/SampleFiles/Images/imagium.xcassets/article_trash.imageset/article_trash@2x.png differ diff --git a/SampleFiles/Images/imagium.xcassets/article_trash.imageset/article_trash@3x.png b/SampleFiles/Images/imagium.xcassets/article_trash.imageset/article_trash@3x.png new file mode 100644 index 0000000..940edca Binary files /dev/null and b/SampleFiles/Images/imagium.xcassets/article_trash.imageset/article_trash@3x.png differ diff --git a/SampleFiles/Images/imagium.xcassets/ic_close_article.imageset/Contents.json b/SampleFiles/Images/imagium.xcassets/ic_close_article.imageset/Contents.json new file mode 100644 index 0000000..c3c805a --- /dev/null +++ b/SampleFiles/Images/imagium.xcassets/ic_close_article.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "ic_close_article.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "ic_close_article@2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x", + "filename" : "ic_close_article@3x.png" + } + ], + "info" : { + "version" : 1, + "author" : "ResgenSwift-Imagium" + } +} \ No newline at end of file diff --git a/SampleFiles/Images/imagium.xcassets/ic_close_article.imageset/ic_close_article.png b/SampleFiles/Images/imagium.xcassets/ic_close_article.imageset/ic_close_article.png new file mode 100644 index 0000000..3f1c26f Binary files /dev/null and b/SampleFiles/Images/imagium.xcassets/ic_close_article.imageset/ic_close_article.png differ diff --git a/SampleFiles/Images/imagium.xcassets/ic_close_article.imageset/ic_close_article@2x.png b/SampleFiles/Images/imagium.xcassets/ic_close_article.imageset/ic_close_article@2x.png new file mode 100644 index 0000000..bf30527 Binary files /dev/null and b/SampleFiles/Images/imagium.xcassets/ic_close_article.imageset/ic_close_article@2x.png differ diff --git a/SampleFiles/Images/imagium.xcassets/ic_close_article.imageset/ic_close_article@3x.png b/SampleFiles/Images/imagium.xcassets/ic_close_article.imageset/ic_close_article@3x.png new file mode 100644 index 0000000..fbf5cd2 Binary files /dev/null and b/SampleFiles/Images/imagium.xcassets/ic_close_article.imageset/ic_close_article@3x.png differ diff --git a/SampleFiles/Images/imagium.xcassets/new_article.imageset/Contents.json b/SampleFiles/Images/imagium.xcassets/new_article.imageset/Contents.json new file mode 100644 index 0000000..ef1f43e --- /dev/null +++ b/SampleFiles/Images/imagium.xcassets/new_article.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "new_article.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "new_article@2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x", + "filename" : "new_article@3x.png" + } + ], + "info" : { + "version" : 1, + "author" : "ResgenSwift-Imagium" + } +} \ No newline at end of file diff --git a/SampleFiles/Images/imagium.xcassets/new_article.imageset/new_article.png b/SampleFiles/Images/imagium.xcassets/new_article.imageset/new_article.png new file mode 100644 index 0000000..c525aa4 Binary files /dev/null and b/SampleFiles/Images/imagium.xcassets/new_article.imageset/new_article.png differ diff --git a/SampleFiles/Images/imagium.xcassets/new_article.imageset/new_article@2x.png b/SampleFiles/Images/imagium.xcassets/new_article.imageset/new_article@2x.png new file mode 100644 index 0000000..3ca8bd0 Binary files /dev/null and b/SampleFiles/Images/imagium.xcassets/new_article.imageset/new_article@2x.png differ diff --git a/SampleFiles/Images/imagium.xcassets/new_article.imageset/new_article@3x.png b/SampleFiles/Images/imagium.xcassets/new_article.imageset/new_article@3x.png new file mode 100644 index 0000000..bb92267 Binary files /dev/null and b/SampleFiles/Images/imagium.xcassets/new_article.imageset/new_article@3x.png differ diff --git a/SampleFiles/Images/imagium.xcassets/welcome_background.imageset/Contents.json b/SampleFiles/Images/imagium.xcassets/welcome_background.imageset/Contents.json new file mode 100644 index 0000000..03a3ce7 --- /dev/null +++ b/SampleFiles/Images/imagium.xcassets/welcome_background.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "welcome_background.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "welcome_background@2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x", + "filename" : "welcome_background@3x.png" + } + ], + "info" : { + "version" : 1, + "author" : "ResgenSwift-Imagium" + } +} \ No newline at end of file diff --git a/SampleFiles/Images/imagium.xcassets/welcome_background.imageset/welcome_background.png b/SampleFiles/Images/imagium.xcassets/welcome_background.imageset/welcome_background.png new file mode 100644 index 0000000..88a5366 Binary files /dev/null and b/SampleFiles/Images/imagium.xcassets/welcome_background.imageset/welcome_background.png differ diff --git a/SampleFiles/Images/imagium.xcassets/welcome_background.imageset/welcome_background@2x.png b/SampleFiles/Images/imagium.xcassets/welcome_background.imageset/welcome_background@2x.png new file mode 100644 index 0000000..9df1caa Binary files /dev/null and b/SampleFiles/Images/imagium.xcassets/welcome_background.imageset/welcome_background@2x.png differ diff --git a/SampleFiles/Images/imagium.xcassets/welcome_background.imageset/welcome_background@3x.png b/SampleFiles/Images/imagium.xcassets/welcome_background.imageset/welcome_background@3x.png new file mode 100644 index 0000000..ff0ba0e Binary files /dev/null and b/SampleFiles/Images/imagium.xcassets/welcome_background.imageset/welcome_background@3x.png differ diff --git a/SampleFiles/Images/sampleImages.txt b/SampleFiles/Images/sampleImages.txt new file mode 100644 index 0000000..0bb9c1e --- /dev/null +++ b/SampleFiles/Images/sampleImages.txt @@ -0,0 +1,6 @@ +i article_notification_pull_detail 150 ? +i article_notification_pull 150 ? +i new_article 150 ? +i welcome_background 320 ? +id article_trash 35 ? +di ic_close_article 11 11 diff --git a/SampleFiles/Strings/Generated/String+StringGenAllScript.swift b/SampleFiles/Strings/Generated/String+StringGenAllScript.swift index a2b90cf..69b5753 100644 --- a/SampleFiles/Strings/Generated/String+StringGenAllScript.swift +++ b/SampleFiles/Strings/Generated/String+StringGenAllScript.swift @@ -1,4 +1,4 @@ -// Generated from Strings-Stringium at 2022-01-10 10:57:09 +0000 +// Generated from Strings-Stringium at 2022-02-14 09:30:20 +0000 import UIKit diff --git a/SampleFiles/Tags/Generated/Tags+TagGenAllScript.swift b/SampleFiles/Tags/Generated/Tags+TagGenAllScript.swift index e0b2cda..0881d01 100644 --- a/SampleFiles/Tags/Generated/Tags+TagGenAllScript.swift +++ b/SampleFiles/Tags/Generated/Tags+TagGenAllScript.swift @@ -1,9 +1,9 @@ -// Generated from Strings-Tags at 2022-01-10 10:57:09 +0000 - -import UIKit +// Generated from Strings-Tags at 2022-02-14 09:30:20 +0000 // typelias Tags = String +import UIKit + extension Tags { // MARK: - ScreenTag @@ -20,4 +20,4 @@ extension Tags { "Ecran deux" } -} +} \ No newline at end of file diff --git a/SampleFiles/genAllRessources.sh b/SampleFiles/genAllRessources.sh index e1f5b72..2c94a16 100755 --- a/SampleFiles/genAllRessources.sh +++ b/SampleFiles/genAllRessources.sh @@ -3,19 +3,23 @@ FORCE_FLAG="$1" # Font -swift run -c release FontToolCore $FORCE_FLAG "./Fonts/sampleFontsAll.txt" \ +swift run -c release FontTool $FORCE_FLAG "./Fonts/sampleFontsAll.txt" \ --extension-output-path "./Fonts/Generated" \ --extension-name "UIFont" \ --extension-suffix "GenAllScript" +echo "\n-------------------------\n" + # Color -swift run -c release ColorToolCore $FORCE_FLAG "./Colors/sampleColors1.txt" \ +swift run -c release ColorTool $FORCE_FLAG "./Colors/sampleColors1.txt" \ --style all \ --xcassets-path "./Colors/colors.xcassets" \ --extension-output-path "./Colors/Generated/" \ --extension-name "UIColor" \ --extension-suffix "GenAllScript" +echo "\n-------------------------\n" + # Twine swift run -c release Strings twine $FORCE_FLAG "./Twine/sampleStrings.txt" \ --output-path "./Twine/Generated" \ @@ -23,6 +27,8 @@ swift run -c release Strings twine $FORCE_FLAG "./Twine/sampleStrings.txt" \ --default-lang "en" \ --extension-output-path "./Twine/Generated" +echo "\n-------------------------\n" + # Strings swift run -c release Strings stringium $FORCE_FLAG "./Strings/sampleStrings.txt" \ --output-path "./Strings/Generated" \ @@ -31,12 +37,21 @@ swift run -c release Strings stringium $FORCE_FLAG "./Strings/sampleStrings.txt" --extension-output-path "./Strings/Generated" \ --extension-name "String" \ --extension-suffix "GenAllScript" - + +echo "\n-------------------------\n" + # Tags swift run -c release Strings tags $FORCE_FLAG "./Tags/sampleTags.txt" \ - --lang "ium" \ + --lang "ium" \ --extension-output-path "./Tags/Generated" \ --extension-name "Tags" \ --extension-suffix "GenAllScript" +echo "\n-------------------------\n" + # Images +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" diff --git a/Sources/CLIToolCore/Shell.swift b/Sources/CLIToolCore/Shell.swift index 4cb573d..0f8c28b 100644 --- a/Sources/CLIToolCore/Shell.swift +++ b/Sources/CLIToolCore/Shell.swift @@ -28,4 +28,24 @@ public class Shell { return (terminationStatus: task.terminationStatus, output: output) } + + @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) + } } diff --git a/Sources/ColorToolCore/ColorExtensionGenerator.swift b/Sources/ColorTool/ColorExtensionGenerator.swift similarity index 100% rename from Sources/ColorToolCore/ColorExtensionGenerator.swift rename to Sources/ColorTool/ColorExtensionGenerator.swift diff --git a/Sources/ColorToolCore/ColorToolError.swift b/Sources/ColorTool/ColorToolError.swift similarity index 100% rename from Sources/ColorToolCore/ColorToolError.swift rename to Sources/ColorTool/ColorToolError.swift diff --git a/Sources/ColorTool/ColorToolOptions.swift b/Sources/ColorTool/ColorToolOptions.swift new file mode 100644 index 0000000..0cda982 --- /dev/null +++ b/Sources/ColorTool/ColorToolOptions.swift @@ -0,0 +1,32 @@ +// +// ColorToolOptions.swift +// +// +// Created by Thibaut Schmitt on 17/01/2022. +// + +import Foundation +import ArgumentParser + +struct ColorToolOptions: ParsableArguments { + @Flag(name: [.customShort("f"), .customShort("F")], help: "Should force generation") + var forceGeneration = false + + @Argument(help: "Input files where colors ared defined.", transform: { $0.replaceTiltWithHomeDirectoryPath() }) + var inputFile: String + + @Option(help: "Color style to generate: light for light colors only, or all for dark and light colors") + var style: String + + @Option(help: "Path of xcassets where to generate colors", transform: { $0.replaceTiltWithHomeDirectoryPath() }) + var xcassetsPath: String + + @Option(help: "Path where to generate the extension.", transform: { $0.replaceTiltWithHomeDirectoryPath() }) + var extensionOutputPath: String + + @Option(help: "Extension name. If not specified, it will generate an UIColor extension. Using default extension name will generate static property.") + var extensionName: String = ColorTool.defaultExtensionName + + @Option(help: "Extension suffix. Ex: MyApp, it will generate {extensionName}+ColorsMyApp.swift") + var extensionSuffix: String = "" +} diff --git a/Sources/ColorToolCore/ColorXcassetHelper.swift b/Sources/ColorTool/ColorXcassetHelper.swift similarity index 100% rename from Sources/ColorToolCore/ColorXcassetHelper.swift rename to Sources/ColorTool/ColorXcassetHelper.swift diff --git a/Sources/ColorToolCore/GenColor.swift b/Sources/ColorTool/GenColor.swift similarity index 100% rename from Sources/ColorToolCore/GenColor.swift rename to Sources/ColorTool/GenColor.swift diff --git a/Sources/ColorToolCore/main.swift b/Sources/ColorTool/main.swift similarity index 72% rename from Sources/ColorToolCore/main.swift rename to Sources/ColorTool/main.swift index b20301e..c5b06b2 100644 --- a/Sources/ColorToolCore/main.swift +++ b/Sources/ColorTool/main.swift @@ -18,70 +18,51 @@ struct ColorTool: ParsableCommand { static let defaultExtensionName = "UIColor" static let assetsColorsFolderName = "Colors" - @Flag(name: .customShort("f"), help: "Should force generation") - var forceGeneration = false + @OptionGroup var options: ColorToolOptions - @Argument(help: "Input files where colors ared defined.", transform: { $0.replaceTiltWithHomeDirectoryPath() }) - var inputFile: String - - @Option(help: "Color style to generate: light for light colors only, or all for dark and light colors") - var style: String - - @Option(help: "Path of xcassets where to generate colors", transform: { $0.replaceTiltWithHomeDirectoryPath() }) - var xcassetsPath: String - - @Option(help: "Path where to generate the extension.", transform: { $0.replaceTiltWithHomeDirectoryPath() }) - var extensionOutputPath: String - - @Option(help: "Extension name. If not specified, it will generate an UIColor extension. Using default extension name will generate static property.") - var extensionName: String = Self.defaultExtensionName - - @Option(help: "Extension suffix. Ex: MyApp, it will generate {extensionName}+ColorsMyApp.swift") - var extensionSuffix: String = "" - - var colorStyle: ColorStyle { ColorStyle(rawValue: style) ?? .all } - var extensionFileName: String { "\(extensionName)+Color\(extensionSuffix).swift" } - var extensionFilePath: String { "\(extensionOutputPath)/\(extensionFileName)" } + var colorStyle: ColorStyle { ColorStyle(rawValue: options.style) ?? .all } + var extensionFileName: String { "\(options.extensionName)+Color\(options.extensionSuffix).swift" } + var extensionFilePath: String { "\(options.extensionOutputPath)/\(extensionFileName)" } public func run() throws { print("[ColorTool] Starting colors generation") - print("[ColorTool] Will use inputFile \(inputFile) to generate \(colorStyle) colors in xcassets \(xcassetsPath)") + print("[ColorTool] Will use inputFile \(options.inputFile) to generate \(colorStyle) colors in xcassets \(options.xcassetsPath)") print("[ColorTool] Extension will be \(extensionFilePath)") // Check requirements let fileManager = FileManager() - guard fileManager.fileExists(atPath: xcassetsPath) else { - let error = ColorToolError.fileNotExists(xcassetsPath) + guard fileManager.fileExists(atPath: options.xcassetsPath) else { + let error = ColorToolError.fileNotExists(options.xcassetsPath) print(error.localizedDescription) ColorTool.exit(withError: error) } - guard fileManager.fileExists(atPath: inputFile) else { - let error = ColorToolError.fileNotExists(inputFile) + guard fileManager.fileExists(atPath: options.inputFile) else { + let error = ColorToolError.fileNotExists(options.inputFile) print(error.localizedDescription) ColorTool.exit(withError: error) } // Check if needed to regenerate - guard GeneratorChecker.shouldGenerate(force: forceGeneration, inputFilePath: inputFile, extensionFilePath: extensionFilePath) else { + guard GeneratorChecker.shouldGenerate(force: options.forceGeneration, inputFilePath: options.inputFile, extensionFilePath: extensionFilePath) else { print("[ColorTool] Colors are already up to date :) ") return } print("[ColorTool] Will generate colors") // Delete current colors - Shell.shell("rm", "-rf", "\(xcassetsPath)/Colors/*") + Shell.shell("rm", "-rf", "\(options.xcassetsPath)/Colors/*") // Get colors let colorsToGen = getColorsGen() // Generate all colors in xcassets - let colorAssetHelper = ColorXcassetHelper(xcassetsPath: xcassetsPath, colors: colorsToGen) + let colorAssetHelper = ColorXcassetHelper(xcassetsPath: options.xcassetsPath, colors: colorsToGen) colorAssetHelper.generateXcassetColors() // Generate extension let extensionGenerator = ColorExtensionGenerator(colors: colorsToGen, - extensionClassname: extensionName, + extensionClassname: options.extensionName, isUIColorExtension: isUIColorExtension()) let extensionHeader = extensionGenerator.getHeader() let extensionProperties = extensionGenerator.getProperties() @@ -115,8 +96,8 @@ struct ColorTool: ParsableCommand { private func getColorsGen() -> [GenColor] { // Get content of input file - let inputFileContent = try! String(contentsOfFile: inputFile, encoding: .utf8) - let colorsByLines = inputFileContent.components(separatedBy: .newlines) + let inputFileContent = try! String(contentsOfFile: options.inputFile, encoding: .utf8) + let colorsByLines = inputFileContent.components(separatedBy: CharacterSet.newlines) // Iterate on each line of input file return colorsByLines.enumerated().compactMap { lineNumber, colorLine in @@ -155,7 +136,7 @@ struct ColorTool: ParsableCommand { // MARK: - Helpers private func isUIColorExtension() -> Bool { - extensionName == Self.defaultExtensionName + options.extensionName == Self.defaultExtensionName } } diff --git a/Sources/FontTool/FontOptions.swift b/Sources/FontTool/FontOptions.swift new file mode 100644 index 0000000..5a6bf7c --- /dev/null +++ b/Sources/FontTool/FontOptions.swift @@ -0,0 +1,26 @@ +// +// FontOptions.swift +// +// +// Created by Thibaut Schmitt on 17/01/2022. +// + +import Foundation +import ArgumentParser + +struct FontOptions: ParsableArguments { + @Flag(name: [.customShort("f"), .customShort("F")], help: "Should force generation") + var forceGeneration = false + + @Argument(help: "Input files where fonts ared defined.", transform: { $0.replaceTiltWithHomeDirectoryPath() }) + var inputFile: String + + @Option(help: "Path where to generate the extension.", transform: { $0.replaceTiltWithHomeDirectoryPath() }) + var extensionOutputPath: String + + @Option(help: "Extension name. If not specified, it will generate an UIFont extension. Using default extension name will generate static property.") + var extensionName: String = FontTool.defaultExtensionName + + @Option(help: "Extension suffix. Ex: MyApp, it will generate {extensionName}+FontsMyApp.swift") + var extensionSuffix: String = "" +} diff --git a/Sources/FontToolCore/FontToolContentGenerator.swift b/Sources/FontTool/FontToolContentGenerator.swift similarity index 100% rename from Sources/FontToolCore/FontToolContentGenerator.swift rename to Sources/FontTool/FontToolContentGenerator.swift diff --git a/Sources/FontToolCore/FontToolError.swift b/Sources/FontTool/FontToolError.swift similarity index 100% rename from Sources/FontToolCore/FontToolError.swift rename to Sources/FontTool/FontToolError.swift diff --git a/Sources/FontToolCore/FontToolHelper.swift b/Sources/FontTool/FontToolHelper.swift similarity index 100% rename from Sources/FontToolCore/FontToolHelper.swift rename to Sources/FontTool/FontToolHelper.swift diff --git a/Sources/FontToolCore/main.swift b/Sources/FontTool/main.swift similarity index 64% rename from Sources/FontToolCore/main.swift rename to Sources/FontTool/main.swift index a2ee033..53db7ca 100644 --- a/Sources/FontToolCore/main.swift +++ b/Sources/FontTool/main.swift @@ -10,39 +10,27 @@ import CLIToolCore import ArgumentParser struct FontTool: ParsableCommand { + static var configuration = CommandConfiguration(abstract: "Generate fonts plist info and extension to access fonts easily.") static let defaultExtensionName = "UIFont" - @Flag(name: .customShort("f"), help: "Should force generation") - var forceGeneration = false + @OptionGroup var options: FontOptions - @Argument(help: "Input files where fonts ared defined.", transform: { $0.replaceTiltWithHomeDirectoryPath() }) - var inputFile: String - - @Option(help: "Path where to generate the extension.", transform: { $0.replaceTiltWithHomeDirectoryPath() }) - var extensionOutputPath: String - - @Option(help: "Extension name. If not specified, it will generate an UIFont extension. Using default extension name will generate static property.") - var extensionName: String = Self.defaultExtensionName - - @Option(help: "Extension suffix. Ex: MyApp, it will generate {extensionName}+FontsMyApp.swift") - var extensionSuffix: String = "" - - var extensionFileName: String { "\(extensionName)+Font\(extensionSuffix).swift" } - var extensionFilePath: String { "\(extensionOutputPath)/\(extensionFileName)" } + var extensionFileName: String { "\(options.extensionName)options.+Font\(options.extensionSuffix).swift" } + var extensionFilePath: String { "\(options.extensionOutputPath)/\(extensionFileName)" } public func run() throws { print("[FontTool] Starting fonts generation") // Check requirements let fileManager = FileManager() - guard fileManager.fileExists(atPath: inputFile) else { - let error = FontToolError.fileNotExists(inputFile) + guard fileManager.fileExists(atPath: options.inputFile) else { + let error = FontToolError.fileNotExists(options.inputFile) print(error.localizedDescription) FontTool.exit(withError: error) } // Check if needed to regenerate - guard GeneratorChecker.shouldGenerate(force: forceGeneration, inputFilePath: inputFile, extensionFilePath: extensionFilePath) else { + guard GeneratorChecker.shouldGenerate(force: options.forceGeneration, inputFilePath: options.inputFile, extensionFilePath: extensionFilePath) else { print("[FontTool] Fonts are already up to date :) ") return } @@ -51,7 +39,7 @@ struct FontTool: ParsableCommand { // Get fonts to generate let fontsToGenerate = getFontsToGenerate() - let inputFolder = URL(fileURLWithPath: inputFile).deletingLastPathComponent().relativePath + let inputFolder = URL(fileURLWithPath: options.inputFile).deletingLastPathComponent().relativePath let fontsFilenames = FontToolHelper .getFontsFilenames(fromInputFolder: inputFolder) .filter { fontNameWithPath in @@ -71,7 +59,7 @@ struct FontTool: ParsableCommand { // Adding fontsFilenames to header (ex: path/to/font.ttf) to make check of regeneration faster let extensionHeader = FontToolContentGenerator.getExtensionHeader(fontsNames: fontsFilenames) - let extensionDefinitionOpening = "extension \(extensionName) {\n" + let extensionDefinitionOpening = "extension \(options.extensionName) {\n" let extensionFontsNamesEnum = FontToolContentGenerator.getFontNameEnum(fontsNames: fontsNames) let extensionFontsMethods = FontToolContentGenerator.getFontMethods(fontsNames: fontsNames, isUIFontExtension: isUIFontExtension()) let extensionDefinitionClosing = "}" @@ -117,23 +105,13 @@ struct FontTool: ParsableCommand { // MARK: - Helpers private func getFontsToGenerate() -> [String] { - let inputFileContent = try! String(contentsOfFile: inputFile, encoding: .utf8) - return inputFileContent.components(separatedBy: .newlines) + let inputFileContent = try! String(contentsOfFile: options.inputFile, encoding: .utf8) + return inputFileContent.components(separatedBy: CharacterSet.newlines) } private func isUIFontExtension() -> Bool { - extensionName == Self.defaultExtensionName + options.extensionName == Self.defaultExtensionName } } FontTool.main() - -/* - - 1. R2Font extension - swift run -c release FontToolCore ./SampleFiles/Fonts/sampleFonts.txt --extension-output-path ./SampleFiles/Fonts/Generated --extension-name R2Font - - 1. UIFont defualt extension (will gen static property) - swift run -c release FontToolCore ./SampleFiles/Fonts/sampleFonts.txt --extension-output-path ./SampleFiles/Fonts/Generated - - */ diff --git a/Sources/Imagium/ConvertArgument.swift b/Sources/Imagium/ConvertArgument.swift new file mode 100644 index 0000000..c66566b --- /dev/null +++ b/Sources/Imagium/ConvertArgument.swift @@ -0,0 +1,13 @@ +// +// ConvertArgument.swift +// +// +// Created by Thibaut Schmitt on 24/01/2022. +// + +import Foundation + +struct ConvertArgument { + let width: String? + let height: String? +} diff --git a/Sources/Imagium/FileManagerExtensions.swift b/Sources/Imagium/FileManagerExtensions.swift new file mode 100644 index 0000000..0b98d2d --- /dev/null +++ b/Sources/Imagium/FileManagerExtensions.swift @@ -0,0 +1,56 @@ +// +// FileManagerExtensions.swift +// +// +// Created by Thibaut Schmitt on 24/01/2022. +// + +import Foundation + +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)") + print(error.localizedDescription) + Imagium.exit(withError: error) + } + + for case let fileURL as URL in enumerator { + do { + let fileAttributes = try fileURL.resourceValues(forKeys:[.isRegularFileKey]) + if fileAttributes.isRegularFile! { + files.append(fileURL.relativePath) + } + } catch { + let error = ImagiumError.getFileAttributed(fileURL.relativePath, error.localizedDescription) + print(error.localizedDescription) + Imagium.exit(withError: error) + } + } + return files + } + + 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)") + print(error.localizedDescription) + Imagium.exit(withError: error) + } + + for case let fileURL as URL in enumerator { + do { + let fileAttributes = try fileURL.resourceValues(forKeys:[.isDirectoryKey]) + if fileAttributes.isDirectory! && fileURL.lastPathComponent.hasSuffix(".imageset") { + files.append(fileURL.lastPathComponent) + } + } catch { + let error = ImagiumError.getFileAttributed(fileURL.relativePath, error.localizedDescription) + print(error.localizedDescription) + Imagium.exit(withError: error) + } + } + return files + } +} diff --git a/Sources/Imagium/ImageExtensionGenerator.swift b/Sources/Imagium/ImageExtensionGenerator.swift new file mode 100644 index 0000000..9c72735 --- /dev/null +++ b/Sources/Imagium/ImageExtensionGenerator.swift @@ -0,0 +1,87 @@ +// +// ImageExtensionGenerator.swift +// +// +// Created by Thibaut Schmitt on 14/02/2022. +// + +import CLIToolCore +import Foundation + +class ImageExtensionGenerator { + + // MARK: - Extension files + + static func writeStringsFiles(images: [ImageToGen], 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 from Imagium at \(Date()) + // Images from \(inputFilename) + + import UIKit + + extension \(extensionClassname) { + """ + } + + private static func getFooter() -> String { + """ + } + """ + } +} + +//@objc var onboarding_foreground3: UIImage { +// return UIImage(named: "onboarding_foreground3")! +// } diff --git a/Sources/Imagium/ImageFileParser.swift b/Sources/Imagium/ImageFileParser.swift new file mode 100644 index 0000000..7355963 --- /dev/null +++ b/Sources/Imagium/ImageFileParser.swift @@ -0,0 +1,47 @@ +// +// ImageFileParser.swift +// +// +// Created by Thibaut Schmitt on 24/01/2022. +// + +import Foundation + +class ImageFileParser { + + static func parse(_ inputFile: String, platform: PlatormTag) -> [ImageToGen] { + let inputFileContent = try! String(contentsOfFile: inputFile, encoding: .utf8) + let stringsByLines = inputFileContent.components(separatedBy: .newlines) + + var imagesToGenerate = [ImageToGen]() + + // Parse file + stringsByLines.forEach { + guard $0.removeLeadingTrailingWhitespace().isEmpty == false, $0.first != "#" else { + return + } + + let splittedLine = $0.split(separator: " ") + + let width: Int = { + if splittedLine[2] == "?" { + return -1 + } + return Int(splittedLine[2])! + }() + let height: Int = { + if splittedLine[3] == "?" { + return -1 + } + return Int(splittedLine[3])! + }() + + let image = ImageToGen(name: String(splittedLine[1]), tags: String(splittedLine[0]), width: width, height: height) + imagesToGenerate.append(image) + } + + return imagesToGenerate.filter { + $0.tags.contains(platform.rawValue) + } + } +} diff --git a/Sources/Imagium/ImageToGen.swift b/Sources/Imagium/ImageToGen.swift new file mode 100644 index 0000000..760c821 --- /dev/null +++ b/Sources/Imagium/ImageToGen.swift @@ -0,0 +1,90 @@ +// +// File.swift +// +// +// Created by Thibaut Schmitt on 24/01/2022. +// + +import Foundation + +struct ImageToGen { + let name: String + let tags: String + let width: Int + let height: Int + + // MARK: - Convert + + var convertArguments: (x1: ConvertArgument, x2: ConvertArgument, x3: ConvertArgument) { + var width1x = "" + var height1x = "" + var width2x = "" + var height2x = "" + var width3x = "" + var height3x = "" + + if width != -1 { + width1x = "\(width)" + width2x = "\(width * 2)" + width3x = "\(width * 3)" + } + + if height != -1 { + height1x = "\(height)" + height2x = "\(height * 2)" + height3x = "\(height * 3)" + } + + return (x1: ConvertArgument(width: width1x, height: height1x), + x2: ConvertArgument(width: width2x, height: height2x), + x3: ConvertArgument(width: width3x, height: height3x)) + } + + // MARK: - Assets + + var contentJson: String { + """ + { + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "\(name).\(XcassetsGenerator.outputImageExtension)" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "\(name)@2x.\(XcassetsGenerator.outputImageExtension)" + }, + { + "idiom" : "universal", + "scale" : "3x", + "filename" : "\(name)@3x.\(XcassetsGenerator.outputImageExtension)" + } + ], + "info" : { + "version" : 1, + "author" : "ResgenSwift-Imagium" + } + } + """ + } + + // MARK: - Extension property + + func getImageProperty() -> String { + """ + var \(name): UIImage { + UIImage(named: "\(name)")! + } + """ + } + + func getStaticImageProperty() -> String { + """ + static var \(name): UIImage { + UIImage(named: "\(name)")! + } + """ + } +} diff --git a/Sources/Imagium/ImagiumError.swift b/Sources/Imagium/ImagiumError.swift new file mode 100644 index 0000000..baa7cc4 --- /dev/null +++ b/Sources/Imagium/ImagiumError.swift @@ -0,0 +1,43 @@ +// +// 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)" + } + } +} diff --git a/Sources/Imagium/ImagiumOptions.swift b/Sources/Imagium/ImagiumOptions.swift new file mode 100644 index 0000000..6fc88c2 --- /dev/null +++ b/Sources/Imagium/ImagiumOptions.swift @@ -0,0 +1,41 @@ +// +// ImagiumOptions.swift +// +// +// Created by Thibaut Schmitt on 24/01/2022. +// + +import Foundation +import ArgumentParser + +struct ImagiumOptions: ParsableArguments { + @Flag(name: .customShort("f"), help: "Should force script execution") + var forceExecution = false + + @Flag(name: .customShort("F"), help: "Regenerate all images") + var forceExecutionAndGeneration = false + + @Argument(help: "Input files where strings ared defined.", transform: { $0.replaceTiltWithHomeDirectoryPath() }) + var inputFile: String + + @Option(help: "Xcassets path where to generate images.", transform: { $0.replaceTiltWithHomeDirectoryPath() }) + var xcassetsPath: String + + @Option(help: "Path where to generate the extension.", transform: { $0.replaceTiltWithHomeDirectoryPath() }) + var extensionOutputPath: String + + @Option(help: "Extension name. If not specified, it will generate an UIImage extension. Using default extension name will generate static property.") + var extensionName: String = Imagium.defaultExtensionName + + @Option(help: "Extension suffix. Ex: MyApp, it will generate {extensionName}+Image{extensionSuffix}.swift") + var extensionSuffix: String = "" +} + + +/* + 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" + */ diff --git a/Sources/Imagium/XcassetsGenerator.swift b/Sources/Imagium/XcassetsGenerator.swift new file mode 100644 index 0000000..54a5ea0 --- /dev/null +++ b/Sources/Imagium/XcassetsGenerator.swift @@ -0,0 +1,189 @@ +// +// File.swift +// +// +// Created by Thibaut Schmitt on 24/01/2022. +// + +import Foundation +import CLIToolCore + +class XcassetsGenerator { + + static let outputImageExtension = "png" + + let forceGeneration: Bool + + // MARK: - Init + + init(forceGeneration: Bool) { + self.forceGeneration = forceGeneration + } + + // MARK: - Assets generation + + func generateXcassets(inputPath: String, imagesToGenerate: [ImageToGen], xcassetsPath: String) { + let fileManager = FileManager() + let svgConverter = Imagium.getSvgConverterPath() + let allSubFiles = fileManager.getAllRegularFileIn(directory: inputPath) + + var generatedAssetsPaths = [String]() + + // Generate new assets + imagesToGenerate.forEach { imageToGen in + // Get image path + let imageData: (path: String, ext: String) = { + for subfile in allSubFiles { + if subfile.hasSuffix("/" + imageToGen.name + ".svg") { + return (subfile, "svg") + } + if subfile.hasSuffix("/" + imageToGen.name + ".png") { + return (subfile, "png") + } + if subfile.hasSuffix("/" + imageToGen.name + ".jpg") { + return (subfile, "jpg") + } + if subfile.hasSuffix("/" + imageToGen.name + ".jepg") { + return (subfile, "jepg") + } + } + let error = ImagiumError.unknownImageExtension(imageToGen.name) + print(error.localizedDescription) + Imagium.exit(withError: error) + }() + + // Create imageset folder + let imagesetName = "\(imageToGen.name).imageset" + let imagesetPath = "\(xcassetsPath)/\(imagesetName)" + Shell.shell("mkdir", "-p", imagesetPath) + + // Store managed images path + generatedAssetsPaths.append(imagesetName) + + // Generate output images path + let output1x = "\(imagesetPath)/\(imageToGen.name).\(XcassetsGenerator.outputImageExtension)" + let output2x = "\(imagesetPath)/\(imageToGen.name)@2x.\(XcassetsGenerator.outputImageExtension)" + let output3x = "\(imagesetPath)/\(imageToGen.name)@3x.\(XcassetsGenerator.outputImageExtension)" + + // Check if we need to convert image + if self.shouldBypassGeneration(for: imageToGen, xcassetImagePath: output1x) { + print("\(imageToGen.name) -> Not regenerating") + return + } + + // Convert image + let convertArguments = imageToGen.convertArguments + if imageData.ext == "svg" { + // /usr/local/bin/rsvg-convert path/to/image.png -w 200 -h 300 -o path/to/output.png + // /usr/local/bin/rsvg-convert path/to/image.png -w 200 -o path/to/output.png + // /usr/local/bin/rsvg-convert path/to/image.png -h 300 -o path/to/output.png + var command1x = ["\(svgConverter)", "\(imageData.path)"] + var command2x = ["\(svgConverter)", "\(imageData.path)"] + var command3x = ["\(svgConverter)", "\(imageData.path)"] + + self.addConvertArgument(command: &command1x, convertArgument: convertArguments.x1) + self.addConvertArgument(command: &command2x, convertArgument: convertArguments.x2) + self.addConvertArgument(command: &command3x, convertArgument: convertArguments.x3) + + command1x.append(contentsOf: ["-o", output1x]) + command2x.append(contentsOf: ["-o", output2x]) + command3x.append(contentsOf: ["-o", output3x]) + + Shell.shell(command1x) + Shell.shell(command2x) + Shell.shell(command3x) + } else { + // 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) + } + + // Write Content.json + let imagesetContentJson = imageToGen.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) + + print("\(imageToGen.name) -> Generated") + } + + // Success info + let generatedAssetsCount = generatedAssetsPaths.count + print("Images generated: \(generatedAssetsCount)") + + // Delete old assets + let allImagesetName = Set(fileManager.getAllImageSetFolderIn(directory: xcassetsPath)) + let imagesetToRemove = allImagesetName.subtracting(Set(generatedAssetsPaths)) + + imagesetToRemove.forEach { + print("Will remove: \($0)") + } + + imagesetToRemove.forEach { itemToRemove in + try! fileManager.removeItem(atPath: "\(xcassetsPath)/\(itemToRemove)") + } + print("Removed \(imagesetToRemove.count) images") + } + + // MARK: - Helpers: SVG command + + private func addConvertArgument(command: inout [String], convertArgument: ConvertArgument) { + if let width = convertArgument.width, width.isEmpty == false { + command.append("-w") + command.append("\(width)") + } + if let height = convertArgument.height, height.isEmpty == false { + command.append("-h") + command.append("\(height)") + } + } + + // MARK: - Helpers: bypass generation + + private func shouldBypassGeneration(for image: ImageToGen, 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 { + return true + } + + return false + } + +} diff --git a/Sources/Imagium/main.swift b/Sources/Imagium/main.swift new file mode 100644 index 0000000..683f40c --- /dev/null +++ b/Sources/Imagium/main.swift @@ -0,0 +1,111 @@ +// +// Imagium.swift +// +// +// Created by Thibaut Schmitt on 24/01/2022. +// + +import Foundation +import ArgumentParser +import CLIToolCore + +enum PlatormTag: String { + case droid = "d" + case ios = "i" +} + +struct Imagium: ParsableCommand { + + static var configuration = CommandConfiguration( + abstract: "A utility for generate images.", + version: "0.1.0" + ) + + static let toolName = "Imagium" + static let defaultExtensionName = "UIImage" + + var extensionFileName: String { "\(options.extensionName)+Image\(options.extensionSuffix).swift" } + var extensionFilePath: String { "\(options.extensionOutputPath)/\(extensionFileName)" } + var inputFilenameWithoutExt: String { + URL(fileURLWithPath: options.inputFile) + .deletingPathExtension() + .lastPathComponent + } + + @OptionGroup var options: ImagiumOptions + + mutating func run() { + print("[\(Self.toolName)] Starting images generation") + + // Check requirements + guard checkRequirements() else { return } + + print("[\(Self.toolName)] Will generate images") + + // Parse input file + let imagesToGenerate = ImageFileParser.parse(options.inputFile, platform: PlatormTag.ios) + + // Generate xcassets files + 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) + + + print("[\(Self.toolName)] Images generated") + } + + // MARK: - Requirements + + private func checkRequirements() -> Bool { + guard options.forceExecutionAndGeneration == false else { + return true + } + + let fileManager = FileManager() + + // Input file + guard fileManager.fileExists(atPath: options.inputFile) else { + let error = ImagiumError.fileNotExists(options.inputFile) + print(error.localizedDescription) + Imagium.exit(withError: error) + } + + // RSVG-Converter + _ = Imagium.getSvgConverterPath() + + // Check if needed to regenerate + guard GeneratorChecker.shouldGenerate(force: options.forceExecution, inputFilePath: options.inputFile, extensionFilePath: extensionFilePath) else { + print("[\(Self.toolName)] Images are already up to date :) ") + return false + } + + return true + } + + // MARK: - Helpers + + @discardableResult + static func getSvgConverterPath() -> String { + let taskSvgConverter = Shell.shell("which", "rsvg-convert") + if taskSvgConverter.terminationStatus == 0 { + return taskSvgConverter.output!.removeCharacters(from: CharacterSet.whitespacesAndNewlines) + } + + let error = ImagiumError.rsvgConvertNotFound + print(error.localizedDescription) + Imagium.exit(withError: error) + } +} + +Imagium.main() diff --git a/Sources/ResgenSwift/main.swift b/Sources/ResgenSwift/main.swift index 1830a03..c2d7115 100644 --- a/Sources/ResgenSwift/main.swift +++ b/Sources/ResgenSwift/main.swift @@ -1 +1,13 @@ print("Welcome ResgenSwift") + +/* + Make resgen as main command and font/color... as subcommands. It could look like: + Resgen + -> ColorTool + -> FontTool + -> ImageTool + -> Strings + -> Twine + -> Stringium + -> Tag + */ diff --git a/Sources/Strings/Generator/StringsFileGenerator.swift b/Sources/Strings/Generator/StringsFileGenerator.swift index 35ff919..e4d41d4 100644 --- a/Sources/Strings/Generator/StringsFileGenerator.swift +++ b/Sources/Strings/Generator/StringsFileGenerator.swift @@ -8,121 +8,120 @@ import Foundation import CLIToolCore -extension Strings { - class StringsFileGenerator { - - // MARK: - Strings Files - - static func writeStringsFiles(sections: [Section], langs: [String], defaultLang: String, tags: [String], outputPath: String, inputFilenameWithoutExt: String) { - var stringsFilesContent = [String: String]() - for lang in langs { - stringsFilesContent[lang] = Self.generateStringsFileContent(lang: lang, - defaultLang: defaultLang, - tags: tags, - sections: sections) - } - - // Write strings file content - langs.forEach { lang in - guard let fileContent = stringsFilesContent[lang] else { return } - - let stringsFilePath = "\(outputPath)/\(lang).lproj/\(inputFilenameWithoutExt).strings" - let stringsFilePathURL = URL(fileURLWithPath: stringsFilePath) - do { - try fileContent.write(to: stringsFilePathURL, atomically: true, encoding: .utf8) - } catch (let error) { - let error = StringiumError.writeFile(error.localizedDescription, stringsFilePath) - print(error.localizedDescription) - Stringium.exit(withError: error) - } - } +class StringsFileGenerator { + + // MARK: - Strings Files + + static func writeStringsFiles(sections: [Section], langs: [String], defaultLang: String, tags: [String], outputPath: String, inputFilenameWithoutExt: String) { + var stringsFilesContent = [String: String]() + for lang in langs { + stringsFilesContent[lang] = Self.generateStringsFileContent(lang: lang, + defaultLang: defaultLang, + tags: tags, + sections: sections) } - private static func generateStringsFileContent(lang: String, defaultLang: String, tags inputTags: [String], sections: [Section]) -> String { - var stringsFileContent = """ + // Write strings file content + langs.forEach { lang in + guard let fileContent = stringsFilesContent[lang] else { return } + + let stringsFilePath = "\(outputPath)/\(lang).lproj/\(inputFilenameWithoutExt).strings" + let stringsFilePathURL = URL(fileURLWithPath: stringsFilePath) + do { + try fileContent.write(to: stringsFilePathURL, atomically: true, encoding: .utf8) + } catch (let error) { + let error = StringiumError.writeFile(error.localizedDescription, stringsFilePath) + print(error.localizedDescription) + Stringium.exit(withError: error) + } + } + } + + private static func generateStringsFileContent(lang: String, defaultLang: String, tags inputTags: [String], sections: [Section]) -> String { + var stringsFileContent = """ /** * Apple Strings File * Generated by ResgenSwift 1.0.0 * Language: \(lang) */\n """ + + sections.forEach { section in + // Check that at least one string will be generated + guard section.hasOneOrMoreMatchingTags(tags: inputTags) else { + return // Go to next section + } + stringsFileContent += "\n/********** \(section.name) **********/\n\n" + section.definitions.forEach { definition in + let translationOpt: String? = { + if definition.tags.contains(Stringium.noTranslationTag) { + return definition.translations[defaultLang] + } + return definition.translations[lang] + }() + + if let translation = translationOpt { + stringsFileContent += "\"\(definition.name)\" = \"\(translation)\";\n\n" + } else { + let error = StringiumError.langNotDefined(lang, definition.name, definition.reference != nil) + print(error.localizedDescription) + Stringium.exit(withError: error) + } + } + } + + return stringsFileContent + } + + // 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: inputTags) else { + guard section.hasOneOrMoreMatchingTags(tags: tags) else { return // Go to next section } - stringsFileContent += "\n/********** \(section.name) **********/\n\n" + content += "\n\t// MARK: - \(section.name)" section.definitions.forEach { definition in - let translationOpt: String? = { - if definition.tags.contains(Stringium.noTranslationTag) { - return definition.translations[defaultLang] - } - return definition.translations[lang] - }() - - if let translation = translationOpt { - stringsFileContent += "\"\(definition.name)\" = \"\(translation)\"\n\n" + if staticVar { + content += "\n\n\(definition.getNSLocalizedStringStaticProperty(forLang: lang))" } else { - let error = StringiumError.langNotDefined(lang, definition.name, definition.reference != nil) - print(error.localizedDescription) - Stringium.exit(withError: error) + content += "\n\n\(definition.getNSLocalizedStringProperty(forLang: lang))" } } + content += "\n" } - - return stringsFileContent + return content + }() + + // Create file if not exists + let fileManager = FileManager() + if fileManager.fileExists(atPath: extensionFilePath) == false { + Shell.shell("touch", "\(extensionFilePath)") } - // MARK: - Extension file + // Create extension content + let extensionFileContent = [extensionHeader, extensionContent, extensionFooter].joined(separator: "\n") - 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 - 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") - - // 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) - } + // 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(stringsFilename: String, extensionClassname: String) -> String { + } + + private static func getHeader(stringsFilename: String, extensionClassname: String) -> String { """ // Generated from Strings-Stringium at \(Date()) @@ -132,12 +131,11 @@ extension Strings { extension \(extensionClassname) { """ - } - - private static func getFooter() -> String { + } + + private static func getFooter() -> String { """ } """ - } } } diff --git a/Sources/Strings/Generator/TagsGenerator.swift b/Sources/Strings/Generator/TagsGenerator.swift index 925c1d8..bb7e1f1 100644 --- a/Sources/Strings/Generator/TagsGenerator.swift +++ b/Sources/Strings/Generator/TagsGenerator.swift @@ -9,54 +9,53 @@ import Foundation import CLIToolCore import CoreVideo -extension Strings { - 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" +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 } - 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) + + 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)") } - private static func getHeader(extensionClassname: String) -> String { + // 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 from Strings-Tags at \(Date()) @@ -66,12 +65,11 @@ extension Strings { extension \(extensionClassname) { """ - } - - private static func getFooter() -> String { + } + + private static func getFooter() -> String { """ } """ - } } } diff --git a/Sources/Strings/Model/Definition.swift b/Sources/Strings/Model/Definition.swift index b556dcc..e5fd0e4 100644 --- a/Sources/Strings/Model/Definition.swift +++ b/Sources/Strings/Model/Definition.swift @@ -7,102 +7,100 @@ import Foundation -extension Strings { - class Definition { - let name: String - var tags = [String]() - var comment: String? - var translations = [String: String]() - var reference: String? - var isPlurals = false - - var isValid: Bool { - name.isEmpty == false && - translations.isEmpty == false +class Definition { + let name: String + var tags = [String]() + var comment: String? + var translations = [String: String]() + var reference: String? + var isPlurals = false + + var isValid: Bool { + name.isEmpty == false && + translations.isEmpty == false + } + + init(name: String) { + self.name = name + } + + static func match(_ line: String) -> Definition? { + guard line.range(of: "\\[(.*?)]$", options: .regularExpression, range: nil, locale: nil) != nil else { + return nil } - init(name: String) { - self.name = name + let definitionName = line + .replacingOccurrences(of: ["[", "]"], with: "") + .removeLeadingTrailingWhitespace() + + return Definition(name: definitionName) + } + + // MARK: - + + func getNSLocalizedStringProperty(forLang lang: String) -> String { + guard let translation = translations[lang] else { + let error = StringiumError.langNotDefined(lang, name, reference != nil) + print(error.localizedDescription) + Stringium.exit(withError: error) } - static func match(_ line: String) -> Definition? { - guard line.range(of: "\\[(.*?)]", options: .regularExpression, range: nil, locale: nil) != nil else { - return nil - } - - let definitionName = line - .replacingOccurrences(of: ["[", "]"], with: "") - .removeLeadingTrailingWhitespace() - - return Definition(name: definitionName) - } - - // MARK: - - - func getNSLocalizedStringProperty(forLang lang: String) -> String { - guard let translation = translations[lang] else { - let error = StringiumError.langNotDefined(lang, name, reference != nil) - print(error.localizedDescription) - Stringium.exit(withError: error) - } - - return """ + return """ /// Translation in \(lang) : /// \(translation) var \(name): String { NSLocalizedString("\(name)", tableName: kStringsFileName, bundle: Bundle.main, value: "\(translation)", comment: "") } """ + } + + func getNSLocalizedStringStaticProperty(forLang lang: String) -> String { + guard let translation = translations[lang] else { + let error = StringiumError.langNotDefined(lang, name, reference != nil) + print(error.localizedDescription) + Stringium.exit(withError: error) } - func getNSLocalizedStringStaticProperty(forLang lang: String) -> String { - guard let translation = translations[lang] else { - let error = StringiumError.langNotDefined(lang, name, reference != nil) - print(error.localizedDescription) - Stringium.exit(withError: error) - } - - return """ + return """ /// Translation in \(lang) : /// \(translation) static var \(name): String { NSLocalizedString("\(name)", tableName: kStringsFileName, bundle: Bundle.main, value: "\(translation)", comment: "") } """ + } + + // MARK: - Raw strings + + func getProperty(forLang lang: String) -> String { + guard let translation = translations[lang] else { + let error = StringiumError.langNotDefined(lang, name, reference != nil) + print(error.localizedDescription) + Stringium.exit(withError: error) } - // MARK: - Raw strings - - func getProperty(forLang lang: String) -> String { - guard let translation = translations[lang] else { - let error = StringiumError.langNotDefined(lang, name, reference != nil) - print(error.localizedDescription) - Stringium.exit(withError: error) - } - - return """ + return """ /// Translation in \(lang) : /// \(translation) var \(name): String { "\(translation)" } """ + } + + func getStaticProperty(forLang lang: String) -> String { + guard let translation = translations[lang] else { + let error = StringiumError.langNotDefined(lang, name, reference != nil) + print(error.localizedDescription) + Stringium.exit(withError: error) } - func getStaticProperty(forLang lang: String) -> String { - guard let translation = translations[lang] else { - let error = StringiumError.langNotDefined(lang, name, reference != nil) - print(error.localizedDescription) - Stringium.exit(withError: error) - } - - return """ + return """ /// Translation in \(lang) : /// \(translation) static var \(name): String { "\(translation)" } """ - } } } diff --git a/Sources/Strings/Model/Section.swift b/Sources/Strings/Model/Section.swift index 2d598b5..5c8cee1 100644 --- a/Sources/Strings/Model/Section.swift +++ b/Sources/Strings/Model/Section.swift @@ -7,35 +7,33 @@ import Foundation -extension Strings { - class Section { - let name: String // OnBoarding - var definitions = [Definition]() - - init(name: String) { - self.name = name +class Section { + let name: String // OnBoarding + var definitions = [Definition]() + + init(name: String) { + self.name = name + } + + static func match(_ line: String) -> Section? { + guard line.range(of: "\\[\\[(.*?)]]$", options: .regularExpression, range: nil, locale: nil) != nil else { + return nil } - static func match(_ line: String) -> Section? { - guard line.range(of: "\\[\\[(.*?)]]", options: .regularExpression, range: nil, locale: nil) != nil else { - return nil + let sectionName = line + .replacingOccurrences(of: ["[", "]"], with: "") + .removeLeadingTrailingWhitespace() + return Section(name: sectionName) + } + + func hasOneOrMoreMatchingTags(tags: [String]) -> Bool { + let allTags = definitions.flatMap { $0.tags } + + for tag in tags { + if allTags.contains(tag) { + return true } - - let sectionName = line - .replacingOccurrences(of: ["[", "]"], with: "") - .removeLeadingTrailingWhitespace() - return Section(name: sectionName) - } - - func hasOneOrMoreMatchingTags(tags: [String]) -> Bool { - let allTags = definitions.flatMap { $0.tags } - - for tag in tags { - if allTags.contains(tag) { - return true - } - } - return false } + return false } } diff --git a/Sources/Strings/Parser/TwineFileParser.swift b/Sources/Strings/Parser/TwineFileParser.swift index cd075e1..0c56b66 100644 --- a/Sources/Strings/Parser/TwineFileParser.swift +++ b/Sources/Strings/Parser/TwineFileParser.swift @@ -7,88 +7,91 @@ import Foundation -extension Strings { - - class TwineFileParser { - static func parse(_ inputFile: String) -> [Section] { - let inputFileContent = try! String(contentsOfFile: inputFile, encoding: .utf8) - let stringsByLines = inputFileContent.components(separatedBy: .newlines) +class TwineFileParser { + static func parse(_ inputFile: String) -> [Section] { + let inputFileContent = try! String(contentsOfFile: inputFile, encoding: .utf8) + let stringsByLines = inputFileContent.components(separatedBy: .newlines) + + var sections = [Section]() + + // Parse file + stringsByLines.forEach { + // Section + if let section = Section.match($0) { + sections.append(section) + return + } - var sections = [Section]() + // Definition + if let definition = Definition.match($0) { + sections.last?.definitions.append(definition) + return + } - // Parse file - stringsByLines.forEach { - // Section - if let section = Section.match($0) { - sections.append(section) - return - } + // Definition content + if $0.isEmpty == false { + // fr = Test => ["fr ", " Test"] + let splitLine = $0 + .removeLeadingTrailingWhitespace() + .split(separator: "=") - // Definition - if let definition = Definition.match($0) { - sections.last?.definitions.append(definition) - return - } + guard let lastDefinition = sections.last?.definitions.last, + let leftElement = splitLine.first else { + return + } - // Definition content - if $0.isEmpty == false { - // fr = Test => ["fr ", " Test"] - let splitLine = $0 - .removeLeadingTrailingWhitespace() - .split(separator: "=") - - guard let lastDefinition = sections.last?.definitions.last, - let leftElement = splitLine.first, - let rightElement = splitLine.last else { - return - } - - // "fr " => "fr" - let leftHand = String(leftElement.dropLast()) - // " Test" => "Test" - let rightHand = String(rightElement.dropFirst()) - - // Handle comments, tags and translation - switch leftHand { - case "comments": - lastDefinition.comment = rightHand - - case "tags": - lastDefinition.tags = rightHand - .split(separator: ",") - .map { String($0) } - - case "ref": - lastDefinition.reference = rightHand - - default: - lastDefinition.translations[leftHand] = rightHand.escapeDoubleQuote() - // Is a plurals strings (fr:one = Test) - // Will be handle later - //if leftHand.split(separator: ":").count > 1 { - // lastDefinition.isPlurals = true - //} + let rightElement: String = { + if let last = splitLine.last, splitLine.count == 2 { + return String(last) } + return "" + }() + + // "fr " => "fr" + let leftHand = String(leftElement).removeTrailingWhitespace() + // " Test" => "Test" + let rightHand = String(rightElement).removeLeadingWhitespace() + + // Handle comments, tags and translation + switch leftHand { + case "comments": + lastDefinition.comment = rightHand + + case "tags": + lastDefinition.tags = rightHand + .split(separator: ",") + .map { String($0) } + + case "ref": + lastDefinition.reference = rightHand + + default: + lastDefinition.translations[leftHand] = rightHand.escapeDoubleQuote() + // Is a plurals strings (fr:one = Test) + // Will be handle later + //if leftHand.split(separator: ":").count > 1 { + // lastDefinition.isPlurals = true + //} } } - - // Keep only valid definition - var invalidDefinitionNames = [String]() - sections.forEach { section in - section.definitions = section.definitions - .filter { - if $0.isValid == false { - invalidDefinitionNames.append($0.name) - return false - } - return true - } - } - if invalidDefinitionNames.count > 0 { - print(" warning:[\(Stringium.toolName)] Found \(invalidDefinitionNames.count) definition (\(invalidDefinitionNames.joined(separator: ", "))") - } - - return sections } + + // Keep only valid definition + var invalidDefinitionNames = [String]() + sections.forEach { section in + section.definitions = section.definitions + .filter { + if $0.isValid == false { + invalidDefinitionNames.append($0.name) + return false + } + return true + } + } + if invalidDefinitionNames.count > 0 { + print(" warning:[\(Stringium.toolName)] Found \(invalidDefinitionNames.count) definition (\(invalidDefinitionNames.joined(separator: ", "))") + } + + return sections } } diff --git a/Sources/Strings/Stringium/Stringium.swift b/Sources/Strings/Stringium/Stringium.swift index 3b6c5b9..1746aa2 100644 --- a/Sources/Strings/Stringium/Stringium.swift +++ b/Sources/Strings/Stringium/Stringium.swift @@ -9,103 +9,100 @@ import Foundation import CLIToolCore import ArgumentParser -extension Strings { +struct Stringium: ParsableCommand { + static var configuration = CommandConfiguration(abstract: "Generate strings with custom scripts.") - struct Stringium: ParsableCommand { - static var configuration = CommandConfiguration(abstract: "Generate strings with custom scripts.") - - static let toolName = "Stringium" - static let defaultExtensionName = "String" - static let noTranslationTag: String = "notranslation" - - var extensionFileName: String { "\(options.extensionName)+String\(options.extensionSuffix).swift" } - var extensionFilePath: String { "\(options.extensionOutputPath)/\(extensionFileName)" } - - var langs: [String] { - options.langsRaw - .split(separator: " ") - .map { String($0) } + static let toolName = "Stringium" + static let defaultExtensionName = "String" + static let noTranslationTag: String = "notranslation" + + var extensionFileName: String { "\(options.extensionName)+String\(options.extensionSuffix).swift" } + var extensionFilePath: String { "\(options.extensionOutputPath)/\(extensionFileName)" } + + var langs: [String] { + options.langsRaw + .split(separator: " ") + .map { String($0) } + } + var inputFilenameWithoutExt: String { + URL(fileURLWithPath: options.inputFile) + .deletingPathExtension() + .lastPathComponent + } + var stringsFileOutputPath: String { + var outputPath = options.outputPathRaw + if outputPath.last == "/" { + outputPath = String(outputPath.dropLast()) } - var inputFilenameWithoutExt: String { - URL(fileURLWithPath: options.inputFile) - .deletingPathExtension() - .lastPathComponent - } - var stringsFileOutputPath: String { - var outputPath = options.outputPathRaw - if outputPath.last == "/" { - outputPath = String(outputPath.dropLast()) - } - return outputPath + return outputPath + } + + // The `@OptionGroup` attribute includes the flags, options, and + // arguments defined by another `ParsableArguments` type. + @OptionGroup var options: StringiumOptions + + mutating func run() { + print("[\(Self.toolName)] Starting strings generation") + + // Check requirements + guard checkRequirements() else { return } + + print("[\(Self.toolName)] Will generate strings") + + // Parse input file + let sections = TwineFileParser.parse(options.inputFile) + + // Generate strings files + StringsFileGenerator.writeStringsFiles(sections: sections, + langs: langs, + defaultLang: options.defaultLang, + tags: ["ios", "iosonly", Self.noTranslationTag], + outputPath: stringsFileOutputPath, + inputFilenameWithoutExt: inputFilenameWithoutExt) + + // Generate extension + StringsFileGenerator.writeExtensionFiles(sections: sections, + defaultLang: options.defaultLang, + tags: ["ios", "iosonly", Self.noTranslationTag], + staticVar: options.extensionName == Self.defaultExtensionName, + inputFilename: inputFilenameWithoutExt, + extensionName: options.extensionName, + extensionFilePath: extensionFilePath) + + print("[\(Self.toolName)] Strings generated") + } + + // MARK: - Requirements + + private func checkRequirements() -> Bool { + let fileManager = FileManager() + + // Input file + guard fileManager.fileExists(atPath: options.inputFile) else { + let error = StringiumError.fileNotExists(options.inputFile) + print(error.localizedDescription) + Stringium.exit(withError: error) } - // The `@OptionGroup` attribute includes the flags, options, and - // arguments defined by another `ParsableArguments` type. - @OptionGroup var options: StringiumOptions - - mutating func run() { - print("[\(Self.toolName)] Starting strings generation") - - // Check requirements - guard checkRequirements() else { return } - - print("[\(Self.toolName)] Will generate strings") - - // Parse input file - let sections = TwineFileParser.parse(options.inputFile) - - // Generate strings files - StringsFileGenerator.writeStringsFiles(sections: sections, - langs: langs, - defaultLang: options.defaultLang, - tags: ["ios", "iosonly", Self.noTranslationTag], - outputPath: stringsFileOutputPath, - inputFilenameWithoutExt: inputFilenameWithoutExt) - - // Generate extension - StringsFileGenerator.writeExtensionFiles(sections: sections, - defaultLang: options.defaultLang, - tags: ["ios", "iosonly", Self.noTranslationTag], - staticVar: options.extensionName == Self.defaultExtensionName, - inputFilename: inputFilenameWithoutExt, - extensionName: options.extensionName, - extensionFilePath: extensionFilePath) - - print("[\(Self.toolName)] Strings generated") + // Langs + guard langs.isEmpty == false else { + let error = StringiumError.langsListEmpty + print(error.localizedDescription) + Stringium.exit(withError: error) } - // MARK: - Requirements - - private func checkRequirements() -> Bool { - let fileManager = FileManager() - - // Input file - guard fileManager.fileExists(atPath: options.inputFile) else { - let error = StringiumError.fileNotExists(options.inputFile) - print(error.localizedDescription) - Stringium.exit(withError: error) - } - - // Langs - guard langs.isEmpty == false else { - let error = StringiumError.langsListEmpty - print(error.localizedDescription) - Stringium.exit(withError: error) - } - - guard langs.contains(options.defaultLang) else { - let error = StringiumError.defaultLangsNotInLangs - print(error.localizedDescription) - Stringium.exit(withError: error) - } - - // Check if needed to regenerate - guard GeneratorChecker.shouldGenerate(force: options.forceGeneration, inputFilePath: options.inputFile, extensionFilePath: extensionFilePath) else { - print("[\(Self.toolName)] Strings are already up to date :) ") - return false - } - - return true + guard langs.contains(options.defaultLang) else { + let error = StringiumError.defaultLangsNotInLangs + print(error.localizedDescription) + Stringium.exit(withError: error) } + + // Check if needed to regenerate + guard GeneratorChecker.shouldGenerate(force: options.forceGeneration, inputFilePath: options.inputFile, extensionFilePath: extensionFilePath) else { + print("[\(Self.toolName)] Strings are already up to date :) ") + return false + } + + return true } } diff --git a/Sources/Strings/Stringium/StringiumError.swift b/Sources/Strings/Stringium/StringiumError.swift index 988ba9d..852d7ec 100644 --- a/Sources/Strings/Stringium/StringiumError.swift +++ b/Sources/Strings/Stringium/StringiumError.swift @@ -7,34 +7,32 @@ import Foundation -extension Strings { - enum StringiumError: Error { - case fileNotExists(String) - case langsListEmpty - case defaultLangsNotInLangs - case writeFile(String, String) - case langNotDefined(String, String, Bool) - - var localizedDescription: String { - switch self { - case .fileNotExists(let filename): - return " error:[\(Stringium.toolName)] File \(filename) does not exists " - - case .langsListEmpty: - return " error:[\(Stringium.toolName)] Langs list is empty" - - case .defaultLangsNotInLangs: - return " error:[\(Stringium.toolName)] Langs list does not contains the default lang" - - case .writeFile(let subErrorDescription, let filename): - return " error:[\(Stringium.toolName)] An error occured while writing content to \(filename): \(subErrorDescription)" - - case .langNotDefined(let lang, let definitionName, let isReference): - if isReference { - return " error:[\(Stringium.toolName)] Reference are handled only by TwineTool. Please use it or remove reference from you strings file." - } - return " error:[\(Stringium.toolName)] Lang \"\(lang)\" not found for \"\(definitionName)\"" +enum StringiumError: Error { + case fileNotExists(String) + case langsListEmpty + case defaultLangsNotInLangs + case writeFile(String, String) + case langNotDefined(String, String, Bool) + + var localizedDescription: String { + switch self { + case .fileNotExists(let filename): + return " error:[\(Stringium.toolName)] File \(filename) does not exists " + + case .langsListEmpty: + return " error:[\(Stringium.toolName)] Langs list is empty" + + case .defaultLangsNotInLangs: + return " error:[\(Stringium.toolName)] Langs list does not contains the default lang" + + case .writeFile(let subErrorDescription, let filename): + return " error:[\(Stringium.toolName)] An error occured while writing content to \(filename): \(subErrorDescription)" + + case .langNotDefined(let lang, let definitionName, let isReference): + if isReference { + return " error:[\(Stringium.toolName)] Reference are handled only by TwineTool. Please use it or remove reference from you strings file." } + return " error:[\(Stringium.toolName)] Lang \"\(lang)\" not found for \"\(definitionName)\"" } - } + } } diff --git a/Sources/Strings/Stringium/StringiumOptions.swift b/Sources/Strings/Stringium/StringiumOptions.swift index 9d14ed1..b5b0130 100644 --- a/Sources/Strings/Stringium/StringiumOptions.swift +++ b/Sources/Strings/Stringium/StringiumOptions.swift @@ -8,30 +8,28 @@ import Foundation import ArgumentParser -extension Strings { - struct StringiumOptions: ParsableArguments { - @Flag(name: .customShort("f"), help: "Should force generation") - var forceGeneration = false - - @Argument(help: "Input files where strings ared defined.", transform: { $0.replaceTiltWithHomeDirectoryPath() }) - var inputFile: String - - @Option(name: .customLong("output-path"), help: "Path where to strings file.", transform: { $0.replaceTiltWithHomeDirectoryPath() }) - var outputPathRaw: String - - @Option(name: .customLong("langs"), help: "Langs to generate.") - var langsRaw: String - - @Option(help: "Default langs.") - var defaultLang: String - - @Option(help: "Path where to generate the extension.", transform: { $0.replaceTiltWithHomeDirectoryPath() }) - var extensionOutputPath: String - - @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}+String{extensionSuffix}.swift") - var extensionSuffix: String = "" - } +struct StringiumOptions: ParsableArguments { + @Flag(name: [.customShort("f"), .customShort("F")], help: "Should force generation") + var forceGeneration = false + + @Argument(help: "Input files where strings ared defined.", transform: { $0.replaceTiltWithHomeDirectoryPath() }) + var inputFile: String + + @Option(name: .customLong("output-path"), help: "Path where to strings file.", transform: { $0.replaceTiltWithHomeDirectoryPath() }) + var outputPathRaw: String + + @Option(name: .customLong("langs"), help: "Langs to generate.") + var langsRaw: String + + @Option(help: "Default langs.") + var defaultLang: String + + @Option(help: "Path where to generate the extension.", transform: { $0.replaceTiltWithHomeDirectoryPath() }) + var extensionOutputPath: String + + @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}+String{extensionSuffix}.swift") + var extensionSuffix: String = "" } diff --git a/Sources/Strings/Tag/Tags.swift b/Sources/Strings/Tag/Tags.swift index efe3028..5caed2b 100644 --- a/Sources/Strings/Tag/Tags.swift +++ b/Sources/Strings/Tag/Tags.swift @@ -9,63 +9,60 @@ import Foundation import CLIToolCore import ArgumentParser -extension Strings { +struct Tags: ParsableCommand { + static var configuration = CommandConfiguration(abstract: "Generate tags extension file.") - struct Tags: ParsableCommand { - static var configuration = CommandConfiguration(abstract: "Generate tags extension file.") + static let toolName = "Tags" + static let defaultExtensionName = "Tags" + static let noTranslationTag: String = "notranslation" + + var extensionFileName: String { "\(options.extensionName)+Tag\(options.extensionSuffix).swift" } + var extensionFilePath: String { "\(options.extensionOutputPath)/\(extensionFileName)" } + + // The `@OptionGroup` attribute includes the flags, options, and + // arguments defined by another `ParsableArguments` type. + @OptionGroup var options: TagsOptions + + mutating func run() { + print("[\(Self.toolName)] Starting tagss generation") - static let toolName = "Tags" - static let defaultExtensionName = "Tags" - static let noTranslationTag: String = "notranslation" + // Check requirements + guard checkRequirements() else { return } - var extensionFileName: String { "\(options.extensionName)+Tag\(options.extensionSuffix).swift" } - var extensionFilePath: String { "\(options.extensionOutputPath)/\(extensionFileName)" } + print("[\(Self.toolName)] Will generate tags") - // The `@OptionGroup` attribute includes the flags, options, and - // arguments defined by another `ParsableArguments` type. - @OptionGroup var options: TagsOptions + // Parse input file + let sections = TwineFileParser.parse(options.inputFile) - mutating func run() { - print("[\(Self.toolName)] Starting tagss generation") - - // Check requirements - guard checkRequirements() else { return } - - print("[\(Self.toolName)] Will generate tags") - - // Parse input file - let sections = TwineFileParser.parse(options.inputFile) - - // Generate extension - TagsGenerator.writeExtensionFiles(sections: sections, - lang: options.lang, - tags: ["ios", "iosonly", Self.noTranslationTag], - staticVar: options.extensionName == Self.defaultExtensionName, - extensionName: options.extensionName, - extensionFilePath: extensionFilePath) - - print("[\(Self.toolName)] Tags generated") + // Generate extension + TagsGenerator.writeExtensionFiles(sections: sections, + lang: options.lang, + tags: ["ios", "iosonly", Self.noTranslationTag], + staticVar: options.extensionName == Self.defaultExtensionName, + extensionName: options.extensionName, + extensionFilePath: extensionFilePath) + + print("[\(Self.toolName)] Tags generated") + } + + // MARK: - Requirements + + private func checkRequirements() -> Bool { + let fileManager = FileManager() + + // Input file + guard fileManager.fileExists(atPath: options.inputFile) else { + let error = StringiumError.fileNotExists(options.inputFile) + print(error.localizedDescription) + Stringium.exit(withError: error) } - // MARK: - Requirements - - private func checkRequirements() -> Bool { - let fileManager = FileManager() - - // Input file - guard fileManager.fileExists(atPath: options.inputFile) else { - let error = StringiumError.fileNotExists(options.inputFile) - print(error.localizedDescription) - Stringium.exit(withError: error) - } - - // Check if needed to regenerate - guard GeneratorChecker.shouldGenerate(force: options.forceGeneration, inputFilePath: options.inputFile, extensionFilePath: extensionFilePath) else { - print("[\(Self.toolName)] Tags are already up to date :) ") - return false - } - - return true + // Check if needed to regenerate + guard GeneratorChecker.shouldGenerate(force: options.forceGeneration, inputFilePath: options.inputFile, extensionFilePath: extensionFilePath) else { + print("[\(Self.toolName)] Tags are already up to date :) ") + return false } + + return true } } diff --git a/Sources/Strings/Tag/TagsOptions.swift b/Sources/Strings/Tag/TagsOptions.swift index 527ea57..8cd82cf 100644 --- a/Sources/Strings/Tag/TagsOptions.swift +++ b/Sources/Strings/Tag/TagsOptions.swift @@ -8,24 +8,22 @@ import Foundation import ArgumentParser -extension Strings { - struct TagsOptions: ParsableArguments { - @Flag(name: .customShort("f"), help: "Should force generation") - var forceGeneration = false - - @Argument(help: "Input files where tags ared defined.", transform: { $0.replaceTiltWithHomeDirectoryPath() }) - var inputFile: String - - @Option(help: "Lang to generate. (\"ium\" by default)") - var lang: String = "ium" - - @Option(help: "Path where to generate the extension.", transform: { $0.replaceTiltWithHomeDirectoryPath() }) - var extensionOutputPath: String - - @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 = "" - } +struct TagsOptions: ParsableArguments { + @Flag(name: [.customShort("f"), .customShort("F")], help: "Should force generation") + var forceGeneration = false + + @Argument(help: "Input files where tags ared defined.", transform: { $0.replaceTiltWithHomeDirectoryPath() }) + var inputFile: String + + @Option(help: "Lang to generate. (\"ium\" by default)") + var lang: String = "ium" + + @Option(help: "Path where to generate the extension.", transform: { $0.replaceTiltWithHomeDirectoryPath() }) + var extensionOutputPath: String + + @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 = "" } diff --git a/Sources/Strings/Twine/Twine.swift b/Sources/Strings/Twine/Twine.swift index 74f89cc..0b4e9ca 100644 --- a/Sources/Strings/Twine/Twine.swift +++ b/Sources/Strings/Twine/Twine.swift @@ -9,89 +9,86 @@ import Foundation import CLIToolCore import ArgumentParser -extension Strings { +struct Twine: ParsableCommand { + static var configuration = CommandConfiguration(abstract: "Generate strings with twine.") - struct Twine: ParsableCommand { - static var configuration = CommandConfiguration(abstract: "Generate strings with twine.") + static let toolName = "Twine" + static let defaultExtensionName = "String" + static let twineExecutable = "\(FileManager.default.homeDirectoryForCurrentUser.relativePath)/scripts/twine/twine" + + var langs: [String] { options.langsRaw.split(separator: " ").map { String($0) } } + var inputFilenameWithoutExt: String { URL(fileURLWithPath: options.inputFile) + .deletingPathExtension() + .lastPathComponent + } + + // The `@OptionGroup` attribute includes the flags, options, and + // arguments defined by another `ParsableArguments` type. + @OptionGroup var options: TwineOptions + + mutating func run() { + print("[\(Self.toolName)] Starting strings generation") - static let toolName = "Twine" - static let defaultExtensionName = "String" - static let twineExecutable = "\(FileManager.default.homeDirectoryForCurrentUser.relativePath)/scripts/twine/twine" + // Check requirements + guard checkRequirements() else { return } - var langs: [String] { options.langsRaw.split(separator: " ").map { String($0) } } - var inputFilenameWithoutExt: String { URL(fileURLWithPath: options.inputFile) - .deletingPathExtension() - .lastPathComponent - } + print("[\(Self.toolName)] Will generate strings") - // The `@OptionGroup` attribute includes the flags, options, and - // arguments defined by another `ParsableArguments` type. - @OptionGroup var options: TwineOptions - - mutating func run() { - print("[\(Self.toolName)] Starting strings generation") - - // Check requirements - guard checkRequirements() else { return } - - print("[\(Self.toolName)] Will generate strings") - - // Generate strings files (lproj files) - for lang in langs { - Shell.shell(Self.twineExecutable, - "generate-localization-file", options.inputFile, - "--lang", "\(lang)", - "\(options.outputPath)/\(lang).lproj/\(inputFilenameWithoutExt).strings", - "--tags=ios,iosonly,iosOnly") - } - - // Generate extension - var extensionFilePath: String { "\(options.extensionOutputPath)/\(inputFilenameWithoutExt).swift" } + // Generate strings files (lproj files) + for lang in langs { Shell.shell(Self.twineExecutable, "generate-localization-file", options.inputFile, - "--format", "apple-swift", - "--lang", "\(options.defaultLang)", - extensionFilePath, + "--lang", "\(lang)", + "\(options.outputPath)/\(lang).lproj/\(inputFilenameWithoutExt).strings", "--tags=ios,iosonly,iosOnly") - - print("[\(Self.toolName)] Strings generated") } - // MARK: - Requirements + // Generate extension + var extensionFilePath: String { "\(options.extensionOutputPath)/\(inputFilenameWithoutExt).swift" } + Shell.shell(Self.twineExecutable, + "generate-localization-file", options.inputFile, + "--format", "apple-swift", + "--lang", "\(options.defaultLang)", + extensionFilePath, + "--tags=ios,iosonly,iosOnly") - private func checkRequirements() -> Bool { - let fileManager = FileManager() - - // Input file - guard fileManager.fileExists(atPath: options.inputFile) else { - let error = TwineError.fileNotExists(options.inputFile) - print(error.localizedDescription) - Twine.exit(withError: error) - } - - // Langs - guard langs.isEmpty == false else { - let error = TwineError.langsListEmpty - print(error.localizedDescription) - Twine.exit(withError: error) - } - - guard langs.contains(options.defaultLang) else { - let error = TwineError.defaultLangsNotInLangs - print(error.localizedDescription) - 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 { - print("[\(Self.toolName)] Strings are already up to date :) ") - return false - } - - return true + print("[\(Self.toolName)] Strings generated") + } + + // MARK: - Requirements + + private func checkRequirements() -> Bool { + let fileManager = FileManager() + + // Input file + guard fileManager.fileExists(atPath: options.inputFile) else { + let error = TwineError.fileNotExists(options.inputFile) + print(error.localizedDescription) + Twine.exit(withError: error) } + + // Langs + guard langs.isEmpty == false else { + let error = TwineError.langsListEmpty + print(error.localizedDescription) + Twine.exit(withError: error) + } + + guard langs.contains(options.defaultLang) else { + let error = TwineError.defaultLangsNotInLangs + print(error.localizedDescription) + 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 { + print("[\(Self.toolName)] Strings are already up to date :) ") + return false + } + + return true } } diff --git a/Sources/Strings/Twine/TwineError.swift b/Sources/Strings/Twine/TwineError.swift index 2f0c7af..291f371 100644 --- a/Sources/Strings/Twine/TwineError.swift +++ b/Sources/Strings/Twine/TwineError.swift @@ -7,25 +7,21 @@ import Foundation -extension Strings { +enum TwineError: Error { + case fileNotExists(String) + case langsListEmpty + case defaultLangsNotInLangs - enum TwineError: Error { - case fileNotExists(String) - case langsListEmpty - case defaultLangsNotInLangs - - var localizedDescription: String { - switch self { - case .fileNotExists(let filename): - return " error:[\(Twine.toolName)] File \(filename) does not exists " - - case .langsListEmpty: - return " error:[\(Twine.toolName)] Langs list is empty" - - case .defaultLangsNotInLangs: - return " error:[\(Twine.toolName)] Langs list does not contains the default lang" - } + var localizedDescription: String { + switch self { + case .fileNotExists(let filename): + return " error:[\(Twine.toolName)] File \(filename) does not exists " + + case .langsListEmpty: + return " error:[\(Twine.toolName)] Langs list is empty" + + case .defaultLangsNotInLangs: + return " error:[\(Twine.toolName)] Langs list does not contains the default lang" } } - } diff --git a/Sources/Strings/Twine/TwineOptions.swift b/Sources/Strings/Twine/TwineOptions.swift index 167f956..4d1d559 100644 --- a/Sources/Strings/Twine/TwineOptions.swift +++ b/Sources/Strings/Twine/TwineOptions.swift @@ -9,7 +9,7 @@ import Foundation import ArgumentParser struct TwineOptions: ParsableArguments { - @Flag(name: .customShort("f"), help: "Should force generation") + @Flag(name: [.customShort("f"), .customShort("F")], help: "Should force generation") var forceGeneration = false @Argument(help: "Input files where strings ared defined.", transform: { $0.replaceTiltWithHomeDirectoryPath() }) diff --git a/Sources/Strings/main.swift b/Sources/Strings/main.swift index 9b70151..d88f21e 100644 --- a/Sources/Strings/main.swift +++ b/Sources/Strings/main.swift @@ -14,7 +14,7 @@ struct Strings: ParsableCommand { abstract: "A utility for generate strings.", version: "0.1.0", - // Pass an array to `subcommands` to set up a nested tree of subcommands. + // 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: [Twine.self, Stringium.self, Tags.self] @@ -26,3 +26,4 @@ struct Strings: ParsableCommand { } Strings.main() +