From b0900c10cdd5dad17542ca6b1d3447edfb8bf810 Mon Sep 17 00:00:00 2001 From: Thibaut Schmitt Date: Mon, 10 Jan 2022 12:01:09 +0100 Subject: [PATCH] Generation des strings sans twine et generation des tags. Refactor de toutes les commandes strings (Twine, CustomStrings, Tags) dans une commande avec des sous commandes --- .../xcschemes/ResgenSwift-Package.xcscheme | 63 +++++++- Package.swift | 24 ++- .../Generated/UIColor+ColorGenAllScript.swift | 2 +- .../Generated/UIFont+FontGenAllScript.swift | 62 ++++++++ .../MyString+StringTargetSuffix.swift | 37 +++++ .../Generated/String+StringGenAllScript.swift | 37 +++++ .../Generated/String+StringTargetSuffix.swift | 37 +++++ .../en-us.lproj/sampleStrings.strings | 19 +++ .../Generated/en.lproj/sampleStrings.strings | 19 +++ .../Generated/fr.lproj/sampleStrings.strings | 19 +++ SampleFiles/Strings/sampleStrings.txt | 27 ++++ .../Tags/Generated/Tags+TagGenAllScript.swift | 23 +++ SampleFiles/Tags/sampleTags.txt | 7 + .../Generated/R2String+sampleStrings.swift | 39 +++++ .../en-us.lproj/sampleStrings.strings | 18 +++ .../Generated/en.lproj/sampleStrings.strings | 18 +++ .../Generated/fr.lproj/sampleStrings.strings | 18 +++ SampleFiles/Twine/sampleStrings.txt | 27 ++++ SampleFiles/genAllRessources.sh | 33 +++- Sources/CLIToolCore/SequenceExtensions.swift | 15 ++ Sources/CLIToolCore/Shell.swift | 2 +- ...xtensions.swift => StringExtensions.swift} | 49 ++++-- Sources/ColorToolCore/main.swift | 8 +- Sources/FontToolCore/FontToolError.swift | 5 +- Sources/FontToolCore/main.swift | 13 +- .../Generator/StringsFileGenerator.swift | 143 ++++++++++++++++++ Sources/Strings/Generator/TagsGenerator.swift | 77 ++++++++++ Sources/Strings/Model/Definition.swift | 108 +++++++++++++ Sources/Strings/Model/Section.swift | 41 +++++ Sources/Strings/Parser/TwineFileParser.swift | 94 ++++++++++++ Sources/Strings/Stringium/Stringium.swift | 111 ++++++++++++++ .../Strings/Stringium/StringiumError.swift | 40 +++++ .../Strings/Stringium/StringiumOptions.swift | 37 +++++ Sources/Strings/Tag/Tags.swift | 71 +++++++++ Sources/Strings/Tag/TagsOptions.swift | 31 ++++ Sources/Strings/Twine/Twine.swift | 97 ++++++++++++ Sources/Strings/Twine/TwineError.swift | 31 ++++ Sources/Strings/Twine/TwineOptions.swift | 29 ++++ Sources/Strings/main.swift | 28 ++++ 39 files changed, 1519 insertions(+), 40 deletions(-) create mode 100644 SampleFiles/Fonts/Generated/UIFont+FontGenAllScript.swift create mode 100644 SampleFiles/Strings/Generated/MyString+StringTargetSuffix.swift create mode 100644 SampleFiles/Strings/Generated/String+StringGenAllScript.swift create mode 100644 SampleFiles/Strings/Generated/String+StringTargetSuffix.swift create mode 100644 SampleFiles/Strings/Generated/en-us.lproj/sampleStrings.strings create mode 100644 SampleFiles/Strings/Generated/en.lproj/sampleStrings.strings create mode 100644 SampleFiles/Strings/Generated/fr.lproj/sampleStrings.strings create mode 100644 SampleFiles/Strings/sampleStrings.txt create mode 100644 SampleFiles/Tags/Generated/Tags+TagGenAllScript.swift create mode 100644 SampleFiles/Tags/sampleTags.txt create mode 100644 SampleFiles/Twine/Generated/R2String+sampleStrings.swift create mode 100644 SampleFiles/Twine/Generated/en-us.lproj/sampleStrings.strings create mode 100644 SampleFiles/Twine/Generated/en.lproj/sampleStrings.strings create mode 100644 SampleFiles/Twine/Generated/fr.lproj/sampleStrings.strings create mode 100644 SampleFiles/Twine/sampleStrings.txt create mode 100644 Sources/CLIToolCore/SequenceExtensions.swift rename Sources/CLIToolCore/{Extensions.swift => StringExtensions.swift} (59%) create mode 100644 Sources/Strings/Generator/StringsFileGenerator.swift create mode 100644 Sources/Strings/Generator/TagsGenerator.swift create mode 100644 Sources/Strings/Model/Definition.swift create mode 100644 Sources/Strings/Model/Section.swift create mode 100644 Sources/Strings/Parser/TwineFileParser.swift create mode 100644 Sources/Strings/Stringium/Stringium.swift create mode 100644 Sources/Strings/Stringium/StringiumError.swift create mode 100644 Sources/Strings/Stringium/StringiumOptions.swift create mode 100644 Sources/Strings/Tag/Tags.swift create mode 100644 Sources/Strings/Tag/TagsOptions.swift create mode 100644 Sources/Strings/Twine/Twine.swift create mode 100644 Sources/Strings/Twine/TwineError.swift create mode 100644 Sources/Strings/Twine/TwineOptions.swift create mode 100644 Sources/Strings/main.swift diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/ResgenSwift-Package.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/ResgenSwift-Package.xcscheme index b573efc..63f224c 100644 --- a/.swiftpm/xcode/xcshareddata/xcschemes/ResgenSwift-Package.xcscheme +++ b/.swiftpm/xcode/xcshareddata/xcschemes/ResgenSwift-Package.xcscheme @@ -76,6 +76,48 @@ ReferencedContainer = "container:"> + + + + + + + + + + + + + + + + - + - + diff --git a/Package.swift b/Package.swift index 14cab04..5933c50 100644 --- a/Package.swift +++ b/Package.swift @@ -5,6 +5,7 @@ import PackageDescription let package = Package( name: "ResgenSwift", + platforms: [.macOS(.v10_12)], dependencies: [ // Dependencies declare other packages that this package depends on. .package(url: "https://github.com/apple/swift-argument-parser", from: "1.0.0") @@ -14,7 +15,7 @@ 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"] + dependencies: ["FontToolCore", "ColorToolCore", "TwineToolCore", "StringToolCore", "Strings"] ), .target( name: "FontToolCore", @@ -30,6 +31,27 @@ let package = Package( .product(name: "ArgumentParser", package: "swift-argument-parser") ] ), + .target( + name: "TwineToolCore", + dependencies: [ + "CLIToolCore", + .product(name: "ArgumentParser", package: "swift-argument-parser") + ] + ), + .target( + name: "StringToolCore", + dependencies: [ + "CLIToolCore", + .product(name: "ArgumentParser", package: "swift-argument-parser") + ] + ), + .target( + name: "Strings", + dependencies: [ + "CLIToolCore", + .product(name: "ArgumentParser", package: "swift-argument-parser") + ] + ), // Helper targets .target(name: "CLIToolCore"), // Test targets diff --git a/SampleFiles/Colors/Generated/UIColor+ColorGenAllScript.swift b/SampleFiles/Colors/Generated/UIColor+ColorGenAllScript.swift index 6d934e1..5b8cce8 100644 --- a/SampleFiles/Colors/Generated/UIColor+ColorGenAllScript.swift +++ b/SampleFiles/Colors/Generated/UIColor+ColorGenAllScript.swift @@ -1,4 +1,4 @@ -// Generated from ColorToolCore at 2021-12-22 09:32:10 +0000 +// Generated from ColorToolCore at 2022-01-10 10:57:08 +0000 import UIKit diff --git a/SampleFiles/Fonts/Generated/UIFont+FontGenAllScript.swift b/SampleFiles/Fonts/Generated/UIFont+FontGenAllScript.swift new file mode 100644 index 0000000..398823b --- /dev/null +++ b/SampleFiles/Fonts/Generated/UIFont+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/Strings/Generated/MyString+StringTargetSuffix.swift b/SampleFiles/Strings/Generated/MyString+StringTargetSuffix.swift new file mode 100644 index 0000000..38795d5 --- /dev/null +++ b/SampleFiles/Strings/Generated/MyString+StringTargetSuffix.swift @@ -0,0 +1,37 @@ +// Generated from StringToolCore at 2022-01-10 08:27:11 +0000 + +import UIKit + +fileprivate let kStringsFileName = "sampleStrings" + +extension MyString { + + // MARK: - Webservice + + /// Translation in en : + /// en + var param_lang: String { + NSLocalizedString("param_lang", tableName: kStringsFileName, bundle: Bundle.main, value: "en", comment: "") + } + + // MARK: - Generic + + /// Translation in en : + /// Back + var generic_back: String { + NSLocalizedString("generic_back", tableName: kStringsFileName, bundle: Bundle.main, value: "Back", comment: "") + } + + /// Translation in en : + /// Loading data... + var generic_loading_data: String { + NSLocalizedString("generic_loading_data", tableName: kStringsFileName, bundle: Bundle.main, value: "Loading data...", comment: "") + } + + /// Translation in en : + /// Welcome %@ ! + var generic_welcome_firstname_format: String { + NSLocalizedString("generic_welcome_firstname_format", tableName: kStringsFileName, bundle: Bundle.main, value: "Welcome %@ !", comment: "") + } + +} \ No newline at end of file diff --git a/SampleFiles/Strings/Generated/String+StringGenAllScript.swift b/SampleFiles/Strings/Generated/String+StringGenAllScript.swift new file mode 100644 index 0000000..a2b90cf --- /dev/null +++ b/SampleFiles/Strings/Generated/String+StringGenAllScript.swift @@ -0,0 +1,37 @@ +// Generated from Strings-Stringium at 2022-01-10 10:57:09 +0000 + +import UIKit + +fileprivate let kStringsFileName = "sampleStrings" + +extension String { + + // MARK: - Webservice + + /// Translation in en : + /// en + static var param_lang: String { + NSLocalizedString("param_lang", tableName: kStringsFileName, bundle: Bundle.main, value: "en", comment: "") + } + + // MARK: - Generic + + /// Translation in en : + /// Back + static var generic_back: String { + NSLocalizedString("generic_back", tableName: kStringsFileName, bundle: Bundle.main, value: "Back", comment: "") + } + + /// Translation in en : + /// Loading data... + static var generic_loading_data: String { + NSLocalizedString("generic_loading_data", tableName: kStringsFileName, bundle: Bundle.main, value: "Loading data...", comment: "") + } + + /// Translation in en : + /// Welcome \"%@\" ! + static var generic_welcome_firstname_format: String { + NSLocalizedString("generic_welcome_firstname_format", tableName: kStringsFileName, bundle: Bundle.main, value: "Welcome \"%@\" !", comment: "") + } + +} \ No newline at end of file diff --git a/SampleFiles/Strings/Generated/String+StringTargetSuffix.swift b/SampleFiles/Strings/Generated/String+StringTargetSuffix.swift new file mode 100644 index 0000000..fcf6786 --- /dev/null +++ b/SampleFiles/Strings/Generated/String+StringTargetSuffix.swift @@ -0,0 +1,37 @@ +// Generated from StringToolCore at 2022-01-10 08:39:52 +0000 + +import UIKit + +fileprivate let kStringsFileName = "sampleStrings" + +extension String { + + // MARK: - Webservice + + /// Translation in en : + /// en + static var param_lang: String { + NSLocalizedString("param_lang", tableName: kStringsFileName, bundle: Bundle.main, value: "en", comment: "") + } + + // MARK: - Generic + + /// Translation in en : + /// Back + static var generic_back: String { + NSLocalizedString("generic_back", tableName: kStringsFileName, bundle: Bundle.main, value: "Back", comment: "") + } + + /// Translation in en : + /// Loading data... + static var generic_loading_data: String { + NSLocalizedString("generic_loading_data", tableName: kStringsFileName, bundle: Bundle.main, value: "Loading data...", comment: "") + } + + /// Translation in en : + /// Welcome \"%@\" ! + static var generic_welcome_firstname_format: String { + NSLocalizedString("generic_welcome_firstname_format", tableName: kStringsFileName, bundle: Bundle.main, value: "Welcome \"%@\" !", comment: "") + } + +} \ No newline at end of file diff --git a/SampleFiles/Strings/Generated/en-us.lproj/sampleStrings.strings b/SampleFiles/Strings/Generated/en-us.lproj/sampleStrings.strings new file mode 100644 index 0000000..6277aac --- /dev/null +++ b/SampleFiles/Strings/Generated/en-us.lproj/sampleStrings.strings @@ -0,0 +1,19 @@ +/** + * Apple Strings File + * Generated by ResgenSwift 1.0.0 + * Language: en-us + */ + +/********** Webservice **********/ + +"param_lang" = "en-us" + + +/********** Generic **********/ + +"generic_back" = "Back" + +"generic_loading_data" = "Loading data..." + +"generic_welcome_firstname_format" = "Welcome \"%@\" !" + diff --git a/SampleFiles/Strings/Generated/en.lproj/sampleStrings.strings b/SampleFiles/Strings/Generated/en.lproj/sampleStrings.strings new file mode 100644 index 0000000..06a479e --- /dev/null +++ b/SampleFiles/Strings/Generated/en.lproj/sampleStrings.strings @@ -0,0 +1,19 @@ +/** + * Apple Strings File + * Generated by ResgenSwift 1.0.0 + * Language: en + */ + +/********** Webservice **********/ + +"param_lang" = "en" + + +/********** Generic **********/ + +"generic_back" = "Back" + +"generic_loading_data" = "Loading data..." + +"generic_welcome_firstname_format" = "Welcome \"%@\" !" + diff --git a/SampleFiles/Strings/Generated/fr.lproj/sampleStrings.strings b/SampleFiles/Strings/Generated/fr.lproj/sampleStrings.strings new file mode 100644 index 0000000..ad5e541 --- /dev/null +++ b/SampleFiles/Strings/Generated/fr.lproj/sampleStrings.strings @@ -0,0 +1,19 @@ +/** + * Apple Strings File + * Generated by ResgenSwift 1.0.0 + * Language: fr + */ + +/********** Webservice **********/ + +"param_lang" = "fr" + + +/********** Generic **********/ + +"generic_back" = "Retour" + +"generic_loading_data" = "Chargement des données..." + +"generic_welcome_firstname_format" = "Bienvenue \"%@\" !" + diff --git a/SampleFiles/Strings/sampleStrings.txt b/SampleFiles/Strings/sampleStrings.txt new file mode 100644 index 0000000..bfcb217 --- /dev/null +++ b/SampleFiles/Strings/sampleStrings.txt @@ -0,0 +1,27 @@ +[[Webservice]] + [param_lang] + en = en + tags = droid,ios + comments = + fr = fr + en-us = en-us + +[[Generic]] + [generic_back] + en = Back + tags = droid,ios + comments = + fr = Retour + en-us = Back + [generic_loading_data] + en = Loading data... + tags = droid,ios + comments = + fr = Chargement des données... + en-us = Loading data... + [generic_welcome_firstname_format] + en = Welcome "%@" ! + tags = droid,ios + comments = + fr = Bienvenue "%@" ! + en-us = Welcome "%@" ! diff --git a/SampleFiles/Tags/Generated/Tags+TagGenAllScript.swift b/SampleFiles/Tags/Generated/Tags+TagGenAllScript.swift new file mode 100644 index 0000000..e0b2cda --- /dev/null +++ b/SampleFiles/Tags/Generated/Tags+TagGenAllScript.swift @@ -0,0 +1,23 @@ +// Generated from Strings-Tags at 2022-01-10 10:57:09 +0000 + +import UIKit + +// typelias Tags = String + +extension Tags { + + // MARK: - ScreenTag + + /// Translation in ium : + /// Ecran un + static var screen_one: String { + "Ecran un" + } + + /// Translation in ium : + /// Ecran deux + static var screen_two: String { + "Ecran deux" + } + +} diff --git a/SampleFiles/Tags/sampleTags.txt b/SampleFiles/Tags/sampleTags.txt new file mode 100644 index 0000000..98f67d4 --- /dev/null +++ b/SampleFiles/Tags/sampleTags.txt @@ -0,0 +1,7 @@ +[[ScreenTag]] + [screen_one] + ium = Ecran un + tags = droid,ios + [screen_two] + ium = Ecran deux + tags = droid,ios diff --git a/SampleFiles/Twine/Generated/R2String+sampleStrings.swift b/SampleFiles/Twine/Generated/R2String+sampleStrings.swift new file mode 100644 index 0000000..3d044de --- /dev/null +++ b/SampleFiles/Twine/Generated/R2String+sampleStrings.swift @@ -0,0 +1,39 @@ +// +// Generated by Twine 1.0.4 +// + +import UIKit + +fileprivate let kStringsFileName = "sampleStrings" + +extension R2String { + + // MARK: - Webservice + + /// Translation in en : + /// en + var param_lang: String { + return NSLocalizedString("param_lang", tableName: kStringsFileName, bundle: Bundle.main, value: "en", comment: "") + } + + // MARK: - Generic + + /// Translation in en : + /// Back + var generic_back: String { + return NSLocalizedString("generic_back", tableName: kStringsFileName, bundle: Bundle.main, value: "Back", comment: "") + } + + /// Translation in en : + /// "Loading" data... + var generic_loading_data: String { + return NSLocalizedString("generic_loading_data", tableName: kStringsFileName, bundle: Bundle.main, value: "\"Loading\" data...", comment: "") + } + + /// Translation in en : + /// Other + var generic_other: String { + return NSLocalizedString("generic_other", tableName: kStringsFileName, bundle: Bundle.main, value: "Other", comment: "") + } + +} diff --git a/SampleFiles/Twine/Generated/en-us.lproj/sampleStrings.strings b/SampleFiles/Twine/Generated/en-us.lproj/sampleStrings.strings new file mode 100644 index 0000000..4fda80e --- /dev/null +++ b/SampleFiles/Twine/Generated/en-us.lproj/sampleStrings.strings @@ -0,0 +1,18 @@ +/** + * Apple Strings File + * Generated by Twine 1.0.4 + * Language: en-us + */ + +/********** Webservice **********/ + +"param_lang" = "en-us"; + + +/********** Generic **********/ + +"generic_back" = "Back"; + +"generic_loading_data" = "\"Loading\" data..."; + +"generic_other" = "Other"; diff --git a/SampleFiles/Twine/Generated/en.lproj/sampleStrings.strings b/SampleFiles/Twine/Generated/en.lproj/sampleStrings.strings new file mode 100644 index 0000000..8336af5 --- /dev/null +++ b/SampleFiles/Twine/Generated/en.lproj/sampleStrings.strings @@ -0,0 +1,18 @@ +/** + * Apple Strings File + * Generated by Twine 1.0.4 + * Language: en + */ + +/********** Webservice **********/ + +"param_lang" = "en"; + + +/********** Generic **********/ + +"generic_back" = "Back"; + +"generic_loading_data" = "\"Loading\" data..."; + +"generic_other" = "Other"; diff --git a/SampleFiles/Twine/Generated/fr.lproj/sampleStrings.strings b/SampleFiles/Twine/Generated/fr.lproj/sampleStrings.strings new file mode 100644 index 0000000..42e99ec --- /dev/null +++ b/SampleFiles/Twine/Generated/fr.lproj/sampleStrings.strings @@ -0,0 +1,18 @@ +/** + * Apple Strings File + * Generated by Twine 1.0.4 + * Language: fr + */ + +/********** Webservice **********/ + +"param_lang" = "fr"; + + +/********** Generic **********/ + +"generic_back" = "Retour"; + +"generic_loading_data" = "\"Chargement\" des données..."; + +"generic_other" = "Autre"; diff --git a/SampleFiles/Twine/sampleStrings.txt b/SampleFiles/Twine/sampleStrings.txt new file mode 100644 index 0000000..3878b33 --- /dev/null +++ b/SampleFiles/Twine/sampleStrings.txt @@ -0,0 +1,27 @@ +[[Webservice]] + [param_lang] + en = en + tags = droid,ios + comments = + fr = fr + en-us = en-us + +[[Generic]] + [generic_back] + en = Back + tags = droid,ios + comments = + fr = Retour + en-us = Back + [generic_loading_data] + en = "Loading" data... + tags = droid,ios + comments = + fr = "Chargement" des données... + en-us = "Loading" data... + [generic_other] + en = Other + tags = droid,ios + comments = + fr = Autre + en-us = Other diff --git a/SampleFiles/genAllRessources.sh b/SampleFiles/genAllRessources.sh index c1d139c..e1f5b72 100755 --- a/SampleFiles/genAllRessources.sh +++ b/SampleFiles/genAllRessources.sh @@ -1,15 +1,42 @@ #/bin/bash +FORCE_FLAG="$1" + # Font -swift run -c release FontToolCore "./Fonts/sampleFontsAl.txt" \ +swift run -c release FontToolCore $FORCE_FLAG "./Fonts/sampleFontsAll.txt" \ --extension-output-path "./Fonts/Generated" \ - --extension-name "R2Font" \ + --extension-name "UIFont" \ --extension-suffix "GenAllScript" # Color -swift run -c release ColorToolCore "./Colors/sampleColors1.txt" \ +swift run -c release ColorToolCore $FORCE_FLAG "./Colors/sampleColors1.txt" \ --style all \ --xcassets-path "./Colors/colors.xcassets" \ --extension-output-path "./Colors/Generated/" \ --extension-name "UIColor" \ --extension-suffix "GenAllScript" + +# Twine +swift run -c release Strings twine $FORCE_FLAG "./Twine/sampleStrings.txt" \ + --output-path "./Twine/Generated" \ + --langs "fr en en-us" \ + --default-lang "en" \ + --extension-output-path "./Twine/Generated" + +# Strings +swift run -c release Strings stringium $FORCE_FLAG "./Strings/sampleStrings.txt" \ + --output-path "./Strings/Generated" \ + --langs "fr en en-us" \ + --default-lang "en" \ + --extension-output-path "./Strings/Generated" \ + --extension-name "String" \ + --extension-suffix "GenAllScript" + +# Tags +swift run -c release Strings tags $FORCE_FLAG "./Tags/sampleTags.txt" \ + --lang "ium" \ + --extension-output-path "./Tags/Generated" \ + --extension-name "Tags" \ + --extension-suffix "GenAllScript" + +# Images diff --git a/Sources/CLIToolCore/SequenceExtensions.swift b/Sources/CLIToolCore/SequenceExtensions.swift new file mode 100644 index 0000000..2608276 --- /dev/null +++ b/Sources/CLIToolCore/SequenceExtensions.swift @@ -0,0 +1,15 @@ +// +// SequenceExtension.swift +// +// +// Created by Thibaut Schmitt on 04/01/2022. +// + +import Foundation + +public extension Sequence where Iterator.Element: Hashable { + func unique() -> [Iterator.Element] { + var seen: [Iterator.Element: Bool] = [:] + return self.filter { seen.updateValue(true, forKey: $0) == nil } + } +} diff --git a/Sources/CLIToolCore/Shell.swift b/Sources/CLIToolCore/Shell.swift index 40ffbbe..4cb573d 100644 --- a/Sources/CLIToolCore/Shell.swift +++ b/Sources/CLIToolCore/Shell.swift @@ -1,5 +1,5 @@ // -// File.swift +// Shell.swift // // // Created by Thibaut Schmitt on 22/12/2021. diff --git a/Sources/CLIToolCore/Extensions.swift b/Sources/CLIToolCore/StringExtensions.swift similarity index 59% rename from Sources/CLIToolCore/Extensions.swift rename to Sources/CLIToolCore/StringExtensions.swift index bbe4a58..47a7327 100644 --- a/Sources/CLIToolCore/Extensions.swift +++ b/Sources/CLIToolCore/StringExtensions.swift @@ -7,8 +7,6 @@ import Foundation -// MARK: - String - public extension String { func removeCharacters(from forbiddenChars: CharacterSet) -> String { let passed = self.unicodeScalars.filter { !forbiddenChars.contains($0) } @@ -19,7 +17,15 @@ public extension String { return removeCharacters(from: CharacterSet(charactersIn: from)) } - func removeTrailingWhitespace() -> String { + func replacingOccurrences(of: [String], with: String) -> Self { + var tmp = self + for e in of { + tmp = tmp.replacingOccurrences(of: e, with: with) + } + return tmp + } + + func removeTrailingWhitespace() -> Self { var newString = self while newString.last?.isWhitespace == true { @@ -29,6 +35,33 @@ public extension String { return newString } + func removeLeadingWhitespace() -> Self { + var newString = self + + while newString.first?.isWhitespace == true { + newString = String(newString.dropFirst()) + } + + return newString + } + + func removeLeadingTrailingWhitespace() -> Self { + var newString = self + + newString = newString.removeLeadingWhitespace() + newString = newString.removeTrailingWhitespace() + + return newString + } + + func escapeDoubleQuote() -> Self { + replacingOccurrences(of: "\"", with: "\\\"") + } + + func replaceTiltWithHomeDirectoryPath() -> Self { + replacingOccurrences(of: "~", with: "\(FileManager.default.homeDirectoryForCurrentUser.relativePath)") + } + func colorComponent() -> (alpha: String, red: String, green: String, blue: String) { var alpha: String = "FF" var red: String @@ -52,13 +85,3 @@ public extension String { return (alpha: alpha, red: red, green: green, blue: blue) } } - -// MARK: - Sequence - -extension Sequence where Iterator.Element: Hashable { - public func unique() -> [Iterator.Element] { - var seen: [Iterator.Element: Bool] = [:] - return self.filter { seen.updateValue(true, forKey: $0) == nil } - } -} - diff --git a/Sources/ColorToolCore/main.swift b/Sources/ColorToolCore/main.swift index f6aa216..b20301e 100644 --- a/Sources/ColorToolCore/main.swift +++ b/Sources/ColorToolCore/main.swift @@ -21,19 +21,19 @@ struct ColorTool: ParsableCommand { @Flag(name: .customShort("f"), help: "Should force generation") var forceGeneration = false - @Argument(help: "Input files where colors ared defined.") + @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") + @Option(help: "Path of xcassets where to generate colors", transform: { $0.replaceTiltWithHomeDirectoryPath() }) var xcassetsPath: String - @Option(help: "Path where to generate the extension.") + @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") + @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") diff --git a/Sources/FontToolCore/FontToolError.swift b/Sources/FontToolCore/FontToolError.swift index d932155..1eb6512 100644 --- a/Sources/FontToolCore/FontToolError.swift +++ b/Sources/FontToolCore/FontToolError.swift @@ -1,5 +1,5 @@ // -// File.swift +// FontToolError.swift // // // Created by Thibaut Schmitt on 13/12/2021. @@ -7,7 +7,6 @@ import Foundation - enum FontToolError: Error { case fcScan(String, Int32, String?) case inputFolderNotFound(String) @@ -22,7 +21,7 @@ enum FontToolError: Error { return " error:[FontTool] Input folder not found: \(inputFolder)" case .fileNotExists(let filename): - return " error:[FontTool] File \(filename) does not exists " + return " error:[FontTool] File \(filename) does not exists" } } } diff --git a/Sources/FontToolCore/main.swift b/Sources/FontToolCore/main.swift index 099a788..a2ee033 100644 --- a/Sources/FontToolCore/main.swift +++ b/Sources/FontToolCore/main.swift @@ -9,27 +9,22 @@ import Foundation import CLIToolCore import ArgumentParser -/* - Lire l'infoPlist et check si les fonts dedans sont les memes que celles à générer - */ - -//swift run -c release FontToolCore ./SampleFiles/Fonts/sampleFonts.txt --extension-output-path ~/Desktop --extension-name R2Font struct FontTool: ParsableCommand { static let defaultExtensionName = "UIFont" @Flag(name: .customShort("f"), help: "Should force generation") var forceGeneration = false - @Argument(help: "Input files where fonts ared defined.") + @Argument(help: "Input files where fonts ared defined.", transform: { $0.replaceTiltWithHomeDirectoryPath() }) var inputFile: String - @Option(help: "Path where to generate the extension.") + @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") + @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}+ColorsMyApp.swift") + @Option(help: "Extension suffix. Ex: MyApp, it will generate {extensionName}+FontsMyApp.swift") var extensionSuffix: String = "" var extensionFileName: String { "\(extensionName)+Font\(extensionSuffix).swift" } diff --git a/Sources/Strings/Generator/StringsFileGenerator.swift b/Sources/Strings/Generator/StringsFileGenerator.swift new file mode 100644 index 0000000..35ff919 --- /dev/null +++ b/Sources/Strings/Generator/StringsFileGenerator.swift @@ -0,0 +1,143 @@ +// +// StringsFileGenerator.swift +// +// +// Created by Thibaut Schmitt on 04/01/2022. +// + +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) + } + } + } + + 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: 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) + } + } + + private static func getHeader(stringsFilename: String, extensionClassname: String) -> String { + """ + // Generated from Strings-Stringium at \(Date()) + + import UIKit + + fileprivate let kStringsFileName = "\(stringsFilename)" + + extension \(extensionClassname) { + """ + } + + private static func getFooter() -> String { + """ + } + """ + } + } +} diff --git a/Sources/Strings/Generator/TagsGenerator.swift b/Sources/Strings/Generator/TagsGenerator.swift new file mode 100644 index 0000000..925c1d8 --- /dev/null +++ b/Sources/Strings/Generator/TagsGenerator.swift @@ -0,0 +1,77 @@ +// +// TagsGenerator.swift +// +// +// Created by Thibaut Schmitt on 10/01/2022. +// + +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" + } + return content + }() + + // Create file if not exists + let fileManager = FileManager() + if fileManager.fileExists(atPath: extensionFilePath) == false { + Shell.shell("touch", "\(extensionFilePath)") + } + + // Create extension content + let extensionFileContent = [extensionHeader, extensionContent, extensionFooter].joined(separator: "\n") + + // Write content + let extensionFilePathURL = URL(fileURLWithPath: extensionFilePath) + do { + try extensionFileContent.write(to: extensionFilePathURL, atomically: true, encoding: .utf8) + } catch (let error) { + let error = StringiumError.writeFile(extensionFilePath, error.localizedDescription) + print(error.localizedDescription) + Stringium.exit(withError: error) + } + } + + private static func getHeader(extensionClassname: String) -> String { + """ + // Generated from Strings-Tags at \(Date()) + + // typelias Tags = String + + import UIKit + + extension \(extensionClassname) { + """ + } + + private static func getFooter() -> String { + """ + } + """ + } + } +} diff --git a/Sources/Strings/Model/Definition.swift b/Sources/Strings/Model/Definition.swift new file mode 100644 index 0000000..b556dcc --- /dev/null +++ b/Sources/Strings/Model/Definition.swift @@ -0,0 +1,108 @@ +// +// Definition.swift +// +// +// Created by Thibaut Schmitt on 04/01/2022. +// + +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 + } + + 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 + } + + 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 """ + /// 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) + } + + 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) + } + + 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) + } + + return """ + /// Translation in \(lang) : + /// \(translation) + static var \(name): String { + "\(translation)" + } + """ + } + } +} diff --git a/Sources/Strings/Model/Section.swift b/Sources/Strings/Model/Section.swift new file mode 100644 index 0000000..2d598b5 --- /dev/null +++ b/Sources/Strings/Model/Section.swift @@ -0,0 +1,41 @@ +// +// Section.swift +// +// +// Created by Thibaut Schmitt on 04/01/2022. +// + +import Foundation + +extension Strings { + 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 + } + + 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 + } + } +} diff --git a/Sources/Strings/Parser/TwineFileParser.swift b/Sources/Strings/Parser/TwineFileParser.swift new file mode 100644 index 0000000..cd075e1 --- /dev/null +++ b/Sources/Strings/Parser/TwineFileParser.swift @@ -0,0 +1,94 @@ +// +// TwineFileParser.swift +// +// +// Created by Thibaut Schmitt on 10/01/2022. +// + +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) + + var sections = [Section]() + + // Parse file + stringsByLines.forEach { + // Section + if let section = Section.match($0) { + sections.append(section) + return + } + + // Definition + if let definition = Definition.match($0) { + sections.last?.definitions.append(definition) + 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 + //} + } + } + } + + // 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 new file mode 100644 index 0000000..3b6c5b9 --- /dev/null +++ b/Sources/Strings/Stringium/Stringium.swift @@ -0,0 +1,111 @@ +// +// Stringium.swift +// +// +// Created by Thibaut Schmitt on 10/01/2022. +// + +import Foundation +import CLIToolCore +import ArgumentParser + +extension Strings { + + 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) } + } + 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 + } + + // 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) + } + + // 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 + } + } +} diff --git a/Sources/Strings/Stringium/StringiumError.swift b/Sources/Strings/Stringium/StringiumError.swift new file mode 100644 index 0000000..988ba9d --- /dev/null +++ b/Sources/Strings/Stringium/StringiumError.swift @@ -0,0 +1,40 @@ +// +// StringToolError.swift +// +// +// Created by Thibaut Schmitt on 05/01/2022. +// + +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)\"" + } + } + } +} diff --git a/Sources/Strings/Stringium/StringiumOptions.swift b/Sources/Strings/Stringium/StringiumOptions.swift new file mode 100644 index 0000000..9d14ed1 --- /dev/null +++ b/Sources/Strings/Stringium/StringiumOptions.swift @@ -0,0 +1,37 @@ +// +// StringiumOptions.swift +// +// +// Created by Thibaut Schmitt on 10/01/2022. +// + +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 = "" + } +} diff --git a/Sources/Strings/Tag/Tags.swift b/Sources/Strings/Tag/Tags.swift new file mode 100644 index 0000000..efe3028 --- /dev/null +++ b/Sources/Strings/Tag/Tags.swift @@ -0,0 +1,71 @@ +// +// Tag.swift +// +// +// Created by Thibaut Schmitt on 10/01/2022. +// + +import Foundation +import CLIToolCore +import ArgumentParser + +extension Strings { + + 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") + + // 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") + } + + // 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 + } + } +} diff --git a/Sources/Strings/Tag/TagsOptions.swift b/Sources/Strings/Tag/TagsOptions.swift new file mode 100644 index 0000000..527ea57 --- /dev/null +++ b/Sources/Strings/Tag/TagsOptions.swift @@ -0,0 +1,31 @@ +// +// TagOptions.swift +// +// +// Created by Thibaut Schmitt on 10/01/2022. +// + +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 = "" + } +} diff --git a/Sources/Strings/Twine/Twine.swift b/Sources/Strings/Twine/Twine.swift new file mode 100644 index 0000000..74f89cc --- /dev/null +++ b/Sources/Strings/Twine/Twine.swift @@ -0,0 +1,97 @@ +// +// Twine.swift +// +// +// Created by Thibaut Schmitt on 10/01/2022. +// + +import Foundation +import CLIToolCore +import ArgumentParser + +extension Strings { + + 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") + + // 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" } + Shell.shell(Self.twineExecutable, + "generate-localization-file", options.inputFile, + "--format", "apple-swift", + "--lang", "\(options.defaultLang)", + extensionFilePath, + "--tags=ios,iosonly,iosOnly") + + 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 new file mode 100644 index 0000000..2f0c7af --- /dev/null +++ b/Sources/Strings/Twine/TwineError.swift @@ -0,0 +1,31 @@ +// +// TwineError.swift +// +// +// Created by Thibaut Schmitt on 10/01/2022. +// + +import Foundation + +extension Strings { + + 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" + } + } + } + +} diff --git a/Sources/Strings/Twine/TwineOptions.swift b/Sources/Strings/Twine/TwineOptions.swift new file mode 100644 index 0000000..167f956 --- /dev/null +++ b/Sources/Strings/Twine/TwineOptions.swift @@ -0,0 +1,29 @@ +// +// File.swift +// +// +// Created by Thibaut Schmitt on 10/01/2022. +// + +import Foundation +import ArgumentParser + +struct TwineOptions: 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(help: "Path where to strings file.", transform: { $0.replaceTiltWithHomeDirectoryPath() }) + var outputPath: 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 +} diff --git a/Sources/Strings/main.swift b/Sources/Strings/main.swift new file mode 100644 index 0000000..9b70151 --- /dev/null +++ b/Sources/Strings/main.swift @@ -0,0 +1,28 @@ +// +// main.swift +// +// +// Created by Thibaut Schmitt on 10/01/2022. +// + +import Foundation +import ArgumentParser + +struct Strings: ParsableCommand { + + static var configuration = CommandConfiguration( + abstract: "A utility for generate strings.", + version: "0.1.0", + + // 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] + + // A default subcommand, when provided, is automatically selected if a + // subcommand is not given on the command line. + //defaultSubcommand: Twine.self + ) +} + +Strings.main()