diff --git a/.swiftlint.yml b/.swiftlint.yml
index d09d0d3..1f42e64 100644
--- a/.swiftlint.yml
+++ b/.swiftlint.yml
@@ -1,43 +1,276 @@
-disabled_rules: # rule identifiers to exclude from running
- - leading_whitespace
- - trailing_whitespace
- - identifier_name
- - large_tuple
- - file_length
- - line_length
- - force_try
- - shorthand_operator
- - type_body_length
- - function_body_length
- - function_parameter_count
- - redundant_string_enum_value
- - unused_closure_parameter
- - cyclomatic_complexity
- - syntactic_sugar
- - empty_enum_arguments
- - force_cast
- - multiple_closures_with_trailing_closure
- - private_over_fileprivate
- - trailing_comma
- - comment_spacing
-excluded: # paths to ignore during linting. Takes precedence over `included`.
- - DerivedData
- - Carthage
- - Pods
- - vendor
- - Vendor
- - "*/R2Tag+tags.swift"
-type_name:
- min_length: 1 # only warning
- max_length: # warning and error
- warning: 50
- error: 60
- allowed_symbols: ["_"]
-nesting:
- type_level:
- warning: 3
- error: 6
- statement_level:
- warning: 5
- error: 10
+# All rules here : https://realm.github.io/SwiftLint/rule-directory.html
+analyzer_rules:
+ - capture_variable
+ - typesafe_array_init
+ - unused_declaration
+ - unused_import
+
+included:
+ - Sources
+
+## Rules configuration
+
+attributes:
+ always_on_line_above: ["@InjectedValue", "@ViewBuilder", "@IBOutlet"]
+ always_on_same_line: ["@Environment", "@EnvironmentObject", "@StateObject", "@State"]
+
+identifier_name:
+ min_length:
+ - 2
+ max_length:
+ - 60
+ excluded:
+ - x
+ - y
+
+type_name:
+ min_length: 3
+ max_length: 60
+ excluded:
+ - T
+ allowed_symbols:
+ - _
+
+# line_length:
+# warning: 150
+
+disabled_rules:
+ - blanket_disable_command # do not warn when rule is not re-enable later in the file
+ - type_contents_order
+ - legacy_objc_type
+ - indentation_width
+ - function_parameter_count
+ - line_length
+ - function_body_length
+ - cyclomatic_complexity
+ - optional_data_string_conversion
+
+opt_in_rules:
+ # Default rules :
+ - block_based_kvo
+ - class_delegate_protocol
+ - closing_brace
+ - closure_parameter_position
+ - colon
+ - comma
+ - comment_spacing
+ - compiler_protocol_init
+ - computed_accessors_order
+ - control_statement
+ - custom_rules
+ # - cyclomatic_complexity
+ - deployment_target
+ - discouraged_direct_init
+ - duplicate_enum_cases
+ - duplicate_imports
+ - duplicated_key_in_dictionary_literal
+ - dynamic_inline
+ - empty_enum_arguments
+ - empty_parameters
+ - empty_parentheses_with_trailing_closure
+ - file_length
+ - for_where
+ - force_unwrapping
+ - force_cast
+ - force_try
+ - trailing_whitespace
+ # - function_body_length
+ # - function_parameter_count
+ - generic_type_name
+ - identifier_name
+ - implicit_getter
+ - inclusive_language
+ - is_disjoint
+ - large_tuple
+ - leading_whitespace
+ - legacy_cggeometry_functions
+ - legacy_constant
+ - legacy_constructor
+ - legacy_hashing
+ - legacy_nsgeometry_functions
+ - legacy_random
+ # - line_length
+ - mark
+ - multiple_closures_with_trailing_closure
+ - nesting
+ - no_fallthrough_only
+ - no_space_in_method_call
+ - notification_center_detachment
+ - ns_number_init_as_function_reference
+ - nsobject_prefer_isequal
+ - opening_brace
+ - operator_whitespace
+ - orphaned_doc_comment
+ - private_over_fileprivate
+ - private_unit_test
+ - protocol_property_accessors_order
+ - reduce_boolean
+ - redundant_discardable_let
+ - redundant_objc_attribute
+ - redundant_optional_initialization
+ - redundant_set_access_control
+ - redundant_string_enum_value
+ - redundant_void_return
+ - return_arrow_whitespace
+ - self_in_property_initialization
+ - shorthand_operator
+ - statement_position
+ - superfluous_disable_command
+ - switch_case_alignment
+ - syntactic_sugar
+ - todo
+ - trailing_comma
+ - trailing_newline
+ - trailing_semicolon
+ - type_body_length
+ - type_name
+ - unavailable_condition
+ - unneeded_break_in_switch
+ - unused_closure_parameter
+ - unused_control_flow_label
+ - unused_enumerated
+ - unused_optional_binding
+ - unused_setter_value
+ - valid_ibinspectable
+ - vertical_parameter_alignment
+ - vertical_whitespace
+ - void_function_in_ternary
+ - void_return
+ - xctfail_message
+ - accessibility_trait_for_button
+ - array_init
+ - attributes
+ - closure_body_length
+ - closure_end_indentation
+ - closure_spacing
+ - collection_alignment
+ - comma_inheritance
+ - contains_over_filter_count
+ - contains_over_filter_is_empty
+ - contains_over_first_not_nil
+ - contains_over_range_nil_comparison
+ - convenience_type
+ - discarded_notification_center_observer
+ - discouraged_assert
+ - empty_count
+ - empty_string
+ - empty_xctest_method
+ - enum_case_associated_values_count
+ - explicit_init
+ - fallthrough
+ - fatal_error_message
+ - file_header
+ - first_where
+ - flatmap_over_map_reduce
+ - ibinspectable_in_extension
+ - implicit_return
+ - implicitly_unwrapped_optional
+ - joined_default_parameter
+ - last_where
+ - legacy_multiple
+ - let_var_whitespace
+ - literal_expression_end_indentation
+ - lower_acl_than_parent
+ # - missing_docs
+ - modifier_order
+ - multiline_arguments
+ - multiline_arguments_brackets
+ - multiline_function_chains
+ - multiline_literal_brackets
+ - multiline_parameters
+ - multiline_parameters_brackets
+ - nimble_operator
+ - no_extension_access_modifier
+ - no_grouping_extension
+ - nslocalizedstring_key
+ - nslocalizedstring_require_bundle
+ - number_separator
+ - operator_usage_whitespace
+ - optional_enum_case_matching
+ - overridden_super_call
+ - override_in_extension
+ - pattern_matching_keywords
+ - prefer_self_in_static_references
+ - prefer_self_type_over_type_of_self
+ - prefer_zero_over_explicit_init
+ - prefixed_toplevel_constant
+ - private_action
+ - private_outlet
+ - prohibited_interface_builder
+ - prohibited_super_call
+ - quick_discouraged_call
+ - quick_discouraged_focused_test
+ - quick_discouraged_pending_test
+ - redundant_nil_coalescing
+ - redundant_type_annotation
+ - required_enum_case
+ - return_value_from_void_function
+ - self_binding
+ - shorthand_optional_binding
+ - single_test_class
+ - sorted_first_last
+ - sorted_imports
+ - strong_iboutlet
+ - test_case_accessibility
+ - toggle_bool
+ - trailing_closure
+ - unavailable_function
+ - unneeded_parentheses_in_closure_argument
+ - unowned_variable_capture
+ - untyped_error_in_catch
+ - vertical_parameter_alignment_on_call
+ - vertical_whitespace_between_cases
+ - vertical_whitespace_closing_braces
+ - weak_delegate
+ - xct_specific_matcher
+
+custom_rules:
+
+ # Empty line before and after MARK -------------------------------------------
+ # mark_spacing:
+ # name: "Surround MARK by empty lines"
+ # regex: '\n[^\n]([^\n]*\/\/ MARK[^\n]*)\n[^\n]'
+ # message: "Surround MARK by empty lines"
+ # severity: warning
+
+ # Empty line -----------------------------------------------------------------
+ # no_empty_line_after_func:
+ # name: "No empty line after init or func"
+ # regex: '(func|init|let\s|var\s)[^\n]*\{[^\n\{\}]*\n\n'
+ # message: "No empty line after init or func"
+ # severity: warning
+
+ # Empty line after canImport -----------------------------------------------------------------
+ no_empty_line_after_can_import:
+ name: "Add empty line after #if canImport"
+ regex: '#if canImport\(.*\)\n[^\n]*(import|class|struct|enum|extension|protocol)'
+ message: "Add empty line after #if canImport"
+ severity: warning
+
+ # Spacings -------------------------------------------------------------------
+ empty_line_required:
+ name: "Add empty line after class, struct, enum, extension or protocol"
+ regex: '(class |struct |enum |extension |protocol )[^\n]*\{\n[^\n]*(class|struct|enum|extension|protocol|func|let|var|weak|private|internal|public|open|static|final|\/\/|init|case|@)'
+ message: "Add empty line after class, struct, enum, extension or protocol"
+ severity: warning
+ match_kinds:
+ - argument
+ - attribute.builtin
+ - attribute.id
+ - buildconfig.id
+ - buildconfig.keyword
+ - comment
+ - comment.mark
+ - comment.url
+ - identifier
+ - keyword
+ - number
+ - objectliteral
+ - parameter
+ - placeholder
+ - string
+ - string_interpolation_anchor
+ - typeidentifier
+
+
diff --git a/Jenkinsfile b/Jenkinsfile
index d43fb68..136be57 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -1,8 +1,10 @@
library "openiumpipeline"
-env.DEVELOPER_DIR="/Applications/Xcode-15.4.0.app/Contents/Developer"
-//env.SIMULATOR_DEVICE_TYPES="iPad--7th-generation-"
+env.DEVELOPER_DIR="/Applications/Xcode-16.3.0.app/Contents/Developer"
+// env.SIMULATOR_DEVICE_TYPES="iPhone-14-Pro"
+// env.SLACK_CHANNEL = "prj-skdevkit"
env.IS_PACKAGE_SWIFT=1
env.TARGETS_MACOS=1
+env.PACKAGE_NAME="ResgenSwift" // xcodebuild -list => Only 1 scheme
iOSpipeline()
diff --git a/Package.resolved b/Package.resolved
index 824db60..23d472c 100644
--- a/Package.resolved
+++ b/Package.resolved
@@ -1,32 +1,5 @@
{
"pins" : [
- {
- "identity" : "collectionconcurrencykit",
- "kind" : "remoteSourceControl",
- "location" : "https://github.com/JohnSundell/CollectionConcurrencyKit.git",
- "state" : {
- "revision" : "b4f23e24b5a1bff301efc5e70871083ca029ff95",
- "version" : "0.2.0"
- }
- },
- {
- "identity" : "cryptoswift",
- "kind" : "remoteSourceControl",
- "location" : "https://github.com/krzyzanowskim/CryptoSwift.git",
- "state" : {
- "revision" : "c9c3df6ab812de32bae61fc0cd1bf6d45170ebf0",
- "version" : "1.8.2"
- }
- },
- {
- "identity" : "sourcekitten",
- "kind" : "remoteSourceControl",
- "location" : "https://github.com/jpsim/SourceKitten.git",
- "state" : {
- "revision" : "b6dc09ee51dfb0c66e042d2328c017483a1a5d56",
- "version" : "0.34.1"
- }
- },
{
"identity" : "swift-argument-parser",
"kind" : "remoteSourceControl",
@@ -37,39 +10,12 @@
}
},
{
- "identity" : "swift-syntax",
+ "identity" : "swiftlintplugin",
"kind" : "remoteSourceControl",
- "location" : "https://github.com/apple/swift-syntax.git",
+ "location" : "https://github.com/lukepistrol/SwiftLintPlugin",
"state" : {
- "revision" : "6ad4ea24b01559dde0773e3d091f1b9e36175036",
- "version" : "509.0.2"
- }
- },
- {
- "identity" : "swiftlint",
- "kind" : "remoteSourceControl",
- "location" : "https://github.com/realm/SwiftLint.git",
- "state" : {
- "revision" : "f17a4f9dfb6a6afb0408426354e4180daaf49cee",
- "version" : "0.54.0"
- }
- },
- {
- "identity" : "swiftytexttable",
- "kind" : "remoteSourceControl",
- "location" : "https://github.com/scottrhoyt/SwiftyTextTable.git",
- "state" : {
- "revision" : "c6df6cf533d120716bff38f8ff9885e1ce2a4ac3",
- "version" : "0.9.0"
- }
- },
- {
- "identity" : "swxmlhash",
- "kind" : "remoteSourceControl",
- "location" : "https://github.com/drmohundro/SWXMLHash.git",
- "state" : {
- "revision" : "a853604c9e9a83ad9954c7e3d2a565273982471f",
- "version" : "7.0.2"
+ "revision" : "87454f5c9ff4d644086aec2a0df1ffba678e7f3c",
+ "version" : "0.57.1"
}
},
{
diff --git a/Package.swift b/Package.swift
index f9cc54c..b5bece5 100644
--- a/Package.swift
+++ b/Package.swift
@@ -1,16 +1,25 @@
-// swift-tools-version:5.6
+// swift-tools-version:5.9
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "ResgenSwift",
- platforms: [.macOS(.v12)],
+ platforms: [.macOS(.v14), .iOS(.v15)],
dependencies: [
// Dependencies declare other packages that this package depends on.
- .package(url: "https://github.com/apple/swift-argument-parser", from: "1.0.0"),
- .package(url: "https://github.com/jpsim/Yams.git", from: "5.0.1"),
- .package(url: "https://github.com/realm/SwiftLint.git", .upToNextMajor(from: "0.54.0")),
+ .package(
+ url: "https://github.com/apple/swift-argument-parser",
+ from: "1.0.0"
+ ),
+ .package(
+ url: "https://github.com/jpsim/Yams.git",
+ from: "5.0.1"
+ ),
+ .package(
+ url: "https://github.com/lukepistrol/SwiftLintPlugin",
+ exact: "0.57.1"
+ ),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
@@ -19,10 +28,15 @@ let package = Package(
name: "ResgenSwift",
dependencies: [
"ToolCore",
- .product(name: "ArgumentParser", package: "swift-argument-parser"),
- "Yams"
+ "Yams",
+ .product(
+ name: "ArgumentParser",
+ package: "swift-argument-parser"
+ )
],
- plugins: [.plugin(name: "SwiftLintPlugin", package: "SwiftLint")]
+ plugins: [
+ .plugin(name: "SwiftLint", package: "SwiftLintPlugin")
+ ]
),
// Helper targets
diff --git a/SampleFiles/Fonts/Generated/test.plist b/SampleFiles/Fonts/Generated/test.plist
index 32c2b75..2033456 100644
--- a/SampleFiles/Fonts/Generated/test.plist
+++ b/SampleFiles/Fonts/Generated/test.plist
@@ -6,16 +6,16 @@
UIAppFonts
- Lato-Italic
- Lato-LightItalic
- Lato-Hairline
- Lato-Bold
- Lato-Black
- Lato-Regular
- Lato-BlackItalic
- Lato-BoldItalic
- Lato-Light
- Lato-HairlineItalic
+ Lato-Italic.ttf
+ Lato-LightItalic.ttf
+ Lato-Thin.ttf
+ Lato-Bold.ttf
+ Lato-Black.ttf
+ Lato-Regular.ttf
+ Lato-BlackItalic.ttf
+ Lato-BoldItalic.ttf
+ Lato-Light.ttf
+ Lato-ThinItalic.ttf
diff --git a/SampleFiles/Fonts/Generated/test2.plist b/SampleFiles/Fonts/Generated/test2.plist
index 32c2b75..2033456 100644
--- a/SampleFiles/Fonts/Generated/test2.plist
+++ b/SampleFiles/Fonts/Generated/test2.plist
@@ -6,16 +6,16 @@
UIAppFonts
- Lato-Italic
- Lato-LightItalic
- Lato-Hairline
- Lato-Bold
- Lato-Black
- Lato-Regular
- Lato-BlackItalic
- Lato-BoldItalic
- Lato-Light
- Lato-HairlineItalic
+ Lato-Italic.ttf
+ Lato-LightItalic.ttf
+ Lato-Thin.ttf
+ Lato-Bold.ttf
+ Lato-Black.ttf
+ Lato-Regular.ttf
+ Lato-BlackItalic.ttf
+ Lato-BoldItalic.ttf
+ Lato-Light.ttf
+ Lato-ThinItalic.ttf
diff --git a/SampleFiles/genAllRessources.sh b/SampleFiles/genAllRessources.sh
index 63c1949..425221d 100755
--- a/SampleFiles/genAllRessources.sh
+++ b/SampleFiles/genAllRessources.sh
@@ -3,12 +3,12 @@
FORCE_FLAG="$1"
## Font
-#swift run -c release ResgenSwift fonts $FORCE_FLAG "./Fonts/sampleFontsAll.txt" \
-# --extension-output-path "./Fonts/Generated" \
-# --extension-name "FontYolo" \
-# --extension-name-ui-kit "UIFontYolo" \
-# --extension-suffix "GenAllScript" \
-# --info-plist-paths "./Fonts/Generated/test.plist ./Fonts/Generated/test2.plist"
+swift run -c release ResgenSwift fonts $FORCE_FLAG "./Fonts/sampleFontsAll.txt" \
+ --extension-output-path "./Fonts/Generated" \
+ --extension-name "FontYolo" \
+ --extension-name-ui-kit "UIFontYolo" \
+ --extension-suffix "GenAllScript" \
+ --info-plist-paths "./Fonts/Generated/test.plist ./Fonts/Generated/test2.plist"
#
#echo "\n-------------------------\n"
#
diff --git a/Sources/ResgenSwift/Analytics/Analytics.swift b/Sources/ResgenSwift/Analytics/Analytics.swift
index 2e51484..ab31f03 100644
--- a/Sources/ResgenSwift/Analytics/Analytics.swift
+++ b/Sources/ResgenSwift/Analytics/Analytics.swift
@@ -5,30 +5,30 @@
// Created by Loris Perret on 08/12/2023.
//
-import ToolCore
-import Foundation
import ArgumentParser
+import Foundation
+import ToolCore
struct Analytics: ParsableCommand {
-
+
// MARK: - Command Configuration
-
+
static var configuration = CommandConfiguration(
abstract: "Generate analytics extension file.",
version: ResgenSwiftVersion
)
-
+
// MARK: - Static
-
+
static let toolName = "Analytics"
static let defaultExtensionName = "Analytics"
-
+
// MARK: - Command Options
-
+
@OptionGroup var options: AnalyticsOptions
-
+
// MARK: - Run
-
+
mutating func run() {
print("[\(Self.toolName)] Starting analytics generation")
print("[\(Self.toolName)] Will use inputFile \(options.inputFile) to generate analytics for target: \(options.target)")
@@ -37,50 +37,54 @@ struct Analytics: ParsableCommand {
guard checkRequirements() else { return }
print("[\(Self.toolName)] Will generate analytics")
-
+
// Check requirements
guard checkRequirements() else { return }
-
+
// Parse input file
- let sections = AnalyticsFileParser.parse(options.inputFile, target: options.target)
-
+ let sections = AnalyticsFileParser().parse(options.inputFile, target: options.target)
+
// Generate extension
- AnalyticsGenerator.writeExtensionFiles(sections: sections,
- target: options.target,
- tags: ["ios", "iosonly"],
- staticVar: options.staticMembers,
- extensionName: options.extensionName,
- extensionFilePath: options.extensionFilePath)
-
+ AnalyticsGenerator.writeExtensionFiles(
+ sections: sections,
+ target: options.target,
+ tags: ["ios", "iosonly"],
+ staticVar: options.staticMembers,
+ extensionName: options.extensionName,
+ extensionFilePath: options.extensionFilePath
+ )
+
print("[\(Self.toolName)] Analytics generated")
}
-
+
// MARK: - Requirements
-
+
private func checkRequirements() -> Bool {
let fileManager = FileManager()
-
+
// Input file
guard fileManager.fileExists(atPath: options.inputFile) else {
let error = AnalyticsError.fileNotExists(options.inputFile)
print(error.description)
- Analytics.exit(withError: error)
+ Self.exit(withError: error)
}
guard TrackerType.hasValidTarget(in: options.target) else {
let error = AnalyticsError.noValidTracker(options.target)
print(error.description)
- Analytics.exit(withError: error)
+ Self.exit(withError: error)
}
// Check if needed to regenerate
- guard GeneratorChecker.shouldGenerate(force: options.forceGeneration,
- inputFilePath: options.inputFile,
- extensionFilePath: options.extensionFilePath) else {
+ guard GeneratorChecker.shouldGenerate(
+ force: options.forceGeneration,
+ inputFilePath: options.inputFile,
+ extensionFilePath: options.extensionFilePath
+ ) else {
print("[\(Self.toolName)] Analytics are already up to date :) ")
return false
}
-
+
return true
}
}
diff --git a/Sources/ResgenSwift/Analytics/AnalyticsError.swift b/Sources/ResgenSwift/Analytics/AnalyticsError.swift
index b32198d..dbc2de2 100644
--- a/Sources/ResgenSwift/Analytics/AnalyticsError.swift
+++ b/Sources/ResgenSwift/Analytics/AnalyticsError.swift
@@ -8,13 +8,14 @@
import Foundation
enum AnalyticsError: Error {
+
case noValidTracker(String)
case fileNotExists(String)
case missingElement(String)
case invalidParameter(String)
case parseFailed(String)
case writeFile(String, String)
-
+
var description: String {
switch self {
case .noValidTracker(let inputTargets):
@@ -22,17 +23,17 @@ enum AnalyticsError: Error {
case .fileNotExists(let filename):
return "error: [\(Analytics.toolName)] File \(filename) does not exists"
-
+
case .missingElement(let element):
return "error: [\(Analytics.toolName)] Missing \(element) for Matomo"
-
+
case .invalidParameter(let reason):
return "error: [\(Analytics.toolName)] Invalid parameter \(reason)"
-
+
case .parseFailed(let baseError):
return "error: [\(Analytics.toolName)] Parse input file failed: \(baseError)"
- case .writeFile(let subErrorDescription, let filename):
+ case let .writeFile(subErrorDescription, filename):
return "error: [\(Analytics.toolName)] An error occured while writing content to \(filename): \(subErrorDescription)"
}
}
diff --git a/Sources/ResgenSwift/Analytics/AnalyticsOptions.swift b/Sources/ResgenSwift/Analytics/AnalyticsOptions.swift
index 0f3172e..b306aad 100644
--- a/Sources/ResgenSwift/Analytics/AnalyticsOptions.swift
+++ b/Sources/ResgenSwift/Analytics/AnalyticsOptions.swift
@@ -5,28 +5,31 @@
// Created by Loris Perret on 08/12/2023.
//
-import Foundation
import ArgumentParser
+import Foundation
+
+// swiftlint:disable no_grouping_extension
struct AnalyticsOptions: 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: "Target(s) analytics to generate. (\"matomo\" | \"firebase\")")
var target: String
-
+
@Option(help: "Path where to generate the extension.", transform: { $0.replaceTiltWithHomeDirectoryPath() })
var extensionOutputPath: String
-
+
@Option(help: "Tell if it will generate static properties or not")
var staticMembers: Bool = false
-
+
@Option(help: "Extension name. If not specified, it will generate a Analytics extension.")
var extensionName: String = Analytics.defaultExtensionName
-
+
@Option(help: "Extension suffix. Ex: MyApp, it will generate {extensionName}+Analytics{extensionSuffix}.swift")
var extensionSuffix: String?
}
@@ -34,13 +37,14 @@ struct AnalyticsOptions: ParsableArguments {
// MARK: - Computed var
extension AnalyticsOptions {
+
var extensionFileName: String {
- if let extensionSuffix = extensionSuffix {
+ if let extensionSuffix {
return "\(extensionName)+\(extensionSuffix).swift"
}
return "\(extensionName).swift"
}
-
+
var extensionFilePath: String {
"\(extensionOutputPath)/\(extensionFileName)"
}
diff --git a/Sources/ResgenSwift/Analytics/Generator/AnalyticsGenerator.swift b/Sources/ResgenSwift/Analytics/Generator/AnalyticsGenerator.swift
index 02973c6..fdcc03e 100644
--- a/Sources/ResgenSwift/Analytics/Generator/AnalyticsGenerator.swift
+++ b/Sources/ResgenSwift/Analytics/Generator/AnalyticsGenerator.swift
@@ -5,112 +5,147 @@
// Created by Loris Perret on 08/12/2023.
//
+import CoreVideo
import Foundation
import ToolCore
-import CoreVideo
-class AnalyticsGenerator {
- static var targets: [TrackerType] = []
+// Disabled cause it's a pain to handle in generated string
- static func writeExtensionFiles(sections: [AnalyticsCategory], target: String, tags: [String], staticVar: Bool, extensionName: String, extensionFilePath: String) {
+enum AnalyticsGenerator {
+
+ // MARK: - Write content
+
+ static func writeExtensionFiles(
+ sections: [AnalyticsCategory],
+ target: String,
+ tags: [String],
+ staticVar: Bool,
+ extensionName: String,
+ extensionFilePath: String
+ ) {
// Get target type from enum
let targetsString: [String] = target.components(separatedBy: " ")
-
- TrackerType.allCases.forEach { enumTarget in
- if targetsString.contains(enumTarget.value) {
- targets.append(enumTarget)
+ let targets = {
+ var targets = [TrackerType]()
+ TrackerType.allCases.forEach { enumTarget in
+ if targetsString.contains(enumTarget.value) {
+ targets.append(enumTarget)
+ }
}
- }
-
+ return targets
+ }()
+
// Get extension content
- let extensionFileContent = Self.getExtensionContent(sections: sections,
- tags: tags,
- staticVar: staticVar,
- extensionName: extensionName)
-
+ let extensionFileContent = getExtensionContent(
+ targets: targets,
+ sections: sections,
+ tags: tags,
+ staticVar: staticVar,
+ extensionName: extensionName
+ )
+
// Write content
let extensionFilePathURL = URL(fileURLWithPath: extensionFilePath)
do {
try extensionFileContent.write(to: extensionFilePathURL, atomically: false, encoding: .utf8)
- } catch let error {
+ } catch {
let error = AnalyticsError.writeFile(extensionFilePath, error.localizedDescription)
print(error.description)
Analytics.exit(withError: error)
}
}
-
+
// MARK: - Extension content
-
- static func getExtensionContent(sections: [AnalyticsCategory], tags: [String], staticVar: Bool, extensionName: String) -> String {
+
+ static func getExtensionContent(
+ targets: [TrackerType],
+ sections: [AnalyticsCategory],
+ tags: [String],
+ staticVar: Bool,
+ extensionName: String
+ ) -> String {
[
- Self.getHeader(extensionClassname: extensionName, staticVar: staticVar),
- Self.getProperties(sections: sections, tags: tags, staticVar: staticVar),
- Self.getFooter()
+ getHeader(
+ targets: targets,
+ extensionClassname: extensionName,
+ staticVar: staticVar
+ ),
+ getProperties(
+ sections: sections,
+ tags: tags,
+ staticVar: staticVar
+ ),
+ getFooter()
]
.joined(separator: "\n")
}
-
+
// MARK: - Extension part
-
- private static func getHeader(extensionClassname: String, staticVar: Bool) -> String {
+
+ private static func getHeader(
+ targets: [TrackerType],
+ extensionClassname: String,
+ staticVar: Bool
+ ) -> String {
"""
// Generated by ResgenSwift.\(Analytics.toolName) \(ResgenSwiftVersion)
-
- \(Self.getImport())
-
- \(Self.getAnalyticsProtocol())
+
+ \(getImport(targets: targets))
+
+ \(getAnalyticsProtocol(targets: targets))
// MARK: - Manager
-
+
class AnalyticsManager {
+
static var shared = AnalyticsManager()
-
+
// MARK: - Properties
-
+
var managers: [AnalyticsManagerProtocol] = []
-
- \(Self.getEnabledContent())
-
- \(Self.getAnalyticsProperties())
-
- \(Self.getPrivateLogFunction())
+
+ \(getEnabledContent())
+
+ \(getAnalyticsProperties(targets: targets))
+
+ \(getPrivateLogFunction())
"""
}
-
+
private static func getEnabledContent() -> String {
"""
private var isEnabled: Bool = true
-
+
// MARK: - Methods
-
+
func setAnalyticsEnabled(_ enable: Bool) {
isEnabled = enable
}
"""
}
-
- private static func getImport() -> String {
+
+ private static func getImport(targets: [TrackerType]) -> String {
var result: [String] = []
-
+
if targets.contains(TrackerType.matomo) {
result.append("import MatomoTracker")
}
if targets.contains(TrackerType.firebase) {
result.append("import FirebaseAnalytics")
}
-
+
return result.joined(separator: "\n")
}
-
+
private static func getPrivateLogFunction() -> String {
"""
private func logScreen(name: String, path: String) {
guard isEnabled else { return }
-
+
managers.forEach { manager in
manager.logScreen(name: name, path: path)
}
}
-
+
private func logEvent(
name: String,
action: String,
@@ -118,7 +153,7 @@ class AnalyticsGenerator {
params: [String: Any]?
) {
guard isEnabled else { return }
-
+
managers.forEach { manager in
manager.logEvent(
name: name,
@@ -130,18 +165,18 @@ class AnalyticsGenerator {
}
"""
}
-
- private static func getAnalyticsProperties() -> String {
+
+ private static func getAnalyticsProperties(targets: [TrackerType]) -> String {
var header = ""
var content: [String] = []
let footer = " }"
-
+
if targets.contains(TrackerType.matomo) {
header = "func configure(siteId: String, url: String) {"
} else if targets.contains(TrackerType.firebase) {
header = "func configure() {"
}
-
+
if targets.contains(TrackerType.matomo) {
content.append("""
managers.append(
@@ -155,7 +190,7 @@ class AnalyticsGenerator {
if targets.contains(TrackerType.firebase) {
content.append(" managers.append(FirebaseAnalyticsManager())")
}
-
+
return [
header,
content.joined(separator: "\n"),
@@ -163,12 +198,13 @@ class AnalyticsGenerator {
]
.joined(separator: "\n")
}
-
- private static func getAnalyticsProtocol() -> String {
+
+ private static func getAnalyticsProtocol(targets: [TrackerType]) -> String {
let proto = """
// MARK: - Protocol
-
+
protocol AnalyticsManagerProtocol {
+
func logScreen(name: String, path: String)
func logEvent(
name: String,
@@ -177,36 +213,40 @@ class AnalyticsGenerator {
params: [String: Any]?
)
}
-
+
"""
-
+
var result: [String] = [proto]
-
+
if targets.contains(TrackerType.matomo) {
result.append(MatomoGenerator.service)
}
-
+
if targets.contains(TrackerType.firebase) {
result.append(FirebaseGenerator.service)
}
-
+
return result.joined(separator: "\n")
}
-
- private static func getProperties(sections: [AnalyticsCategory], tags: [String], staticVar: Bool) -> String {
+
+ private static func getProperties(
+ sections: [AnalyticsCategory],
+ tags: [String],
+ staticVar: Bool
+ ) -> String {
sections
.compactMap { section in
// Check that at least one string will be generated
guard section.hasOneOrMoreMatchingTags(tags: tags) else {
return nil// Go to next section
}
-
+
var res = "\n // MARK: - \(section.id)"
section.definitions.forEach { definition in
guard definition.hasOneOrMoreMatchingTags(inputTags: tags) == true else {
return // Go to next definition
}
-
+
if staticVar {
res += "\n\n\(definition.getStaticProperty())"
} else {
@@ -217,11 +257,11 @@ class AnalyticsGenerator {
}
.joined(separator: "\n")
}
-
+
private static func getFooter() -> String {
"""
}
-
+
"""
}
}
diff --git a/Sources/ResgenSwift/Analytics/Generator/FirebaseGenerator.swift b/Sources/ResgenSwift/Analytics/Generator/FirebaseGenerator.swift
index 1957156..e5aef8c 100644
--- a/Sources/ResgenSwift/Analytics/Generator/FirebaseGenerator.swift
+++ b/Sources/ResgenSwift/Analytics/Generator/FirebaseGenerator.swift
@@ -5,20 +5,22 @@
// Created by Loris Perret on 05/12/2023.
//
+// CPD-OFF
+
import Foundation
enum FirebaseGenerator {
static var service: String {
[
- FirebaseGenerator.header,
- FirebaseGenerator.logScreen,
- FirebaseGenerator.logEvent,
- FirebaseGenerator.footer
+ Self.header,
+ Self.logScreen,
+ Self.logEvent,
+ Self.footer
]
.joined(separator: "\n")
}
-
+
// MARK: - Private vars
private static var header: String {
@@ -28,23 +30,23 @@ enum FirebaseGenerator {
class FirebaseAnalyticsManager: AnalyticsManagerProtocol {
"""
}
-
+
private static var logScreen: String {
"""
func logScreen(name: String, path: String) {
var parameters = [
AnalyticsParameterScreenName: name as NSObject
]
-
+
Analytics.logEvent(
AnalyticsEventScreenView,
parameters: parameters
)
}
-
+
"""
}
-
+
private static var logEvent: String {
"""
func logEvent(
@@ -57,7 +59,7 @@ enum FirebaseGenerator {
"action": action as NSObject,
"category": category as NSObject,
]
-
+
if let supplementaryParameters = params {
for (newKey, newValue) in supplementaryParameters {
if parameters.contains(where: { (key: String, value: NSObject) in
@@ -65,11 +67,11 @@ enum FirebaseGenerator {
}) {
continue
}
-
+
parameters[newKey] = newValue as? NSObject
}
}
-
+
Analytics.logEvent(
name.replacingOccurrences(of: [" "], with: "_"),
parameters: parameters
@@ -77,11 +79,13 @@ enum FirebaseGenerator {
}
"""
}
-
+
private static var footer: String {
"""
}
-
+
"""
}
}
+
+// CPD-ON
diff --git a/Sources/ResgenSwift/Analytics/Generator/MatomoGenerator.swift b/Sources/ResgenSwift/Analytics/Generator/MatomoGenerator.swift
index 740cf95..4bd4320 100644
--- a/Sources/ResgenSwift/Analytics/Generator/MatomoGenerator.swift
+++ b/Sources/ResgenSwift/Analytics/Generator/MatomoGenerator.swift
@@ -1,6 +1,6 @@
//
// MatomoGenerator.swift
-//
+//
//
// Created by Loris Perret on 05/12/2023.
//
@@ -11,15 +11,15 @@ enum MatomoGenerator {
static var service: String {
[
- MatomoGenerator.header,
- MatomoGenerator.setup,
- MatomoGenerator.logScreen,
- MatomoGenerator.logEvent,
- MatomoGenerator.footer
+ Self.header,
+ Self.setup,
+ Self.logScreen,
+ Self.logEvent,
+ Self.footer
]
.joined(separator: "\n")
}
-
+
// MARK: - Private vars
private static var header: String {
@@ -27,18 +27,18 @@ enum MatomoGenerator {
// MARK: - Matomo
class MatomoAnalyticsManager: AnalyticsManagerProtocol {
-
+
// MARK: - Properties
-
+
private var tracker: MatomoTracker
-
+
"""
}
-
+
private static var setup: String {
"""
// MARK: - Init
-
+
init(siteId: String, url: String) {
debugPrint("[Matomo service] Server URL: \\(url)")
debugPrint("[Matomo service] Site ID: \\(siteId)")
@@ -46,40 +46,40 @@ enum MatomoGenerator {
siteId: siteId,
baseURL: URL(string: url)!
)
-
+
#if DEBUG
tracker.dispatchInterval = 5
#endif
-
+
#if DEBUG
tracker.logger = DefaultLogger(minLevel: .verbose)
#endif
-
+
debugPrint("[Matomo service] Configured with content base: \\(tracker.contentBase?.absoluteString ?? "-")")
debugPrint("[Matomo service] Opt out: \\(tracker.isOptedOut)")
}
-
+
// MARK: - Methods
-
+
"""
}
-
+
private static var logScreen: String {
"""
func logScreen(name: String, path: String) {
guard !tracker.isOptedOut else { return }
guard let trackerUrl = tracker.contentBase?.absoluteString else { return }
-
+
let urlString = URL(string: "\\(trackerUrl)" + "/" + "\\(path)" + "iOS")
tracker.track(
view: [name],
url: urlString
)
}
-
+
"""
}
-
+
private static var logEvent: String {
"""
func logEvent(
@@ -89,7 +89,7 @@ enum MatomoGenerator {
params: [String: Any]?
) {
guard !tracker.isOptedOut else { return }
-
+
tracker.track(
eventWithCategory: category,
action: action,
@@ -100,11 +100,11 @@ enum MatomoGenerator {
}
"""
}
-
+
private static var footer: String {
"""
}
-
+
"""
}
}
diff --git a/Sources/ResgenSwift/Analytics/Model/AnalyticsCategory.swift b/Sources/ResgenSwift/Analytics/Model/AnalyticsCategory.swift
index 9451d99..a2d4311 100644
--- a/Sources/ResgenSwift/Analytics/Model/AnalyticsCategory.swift
+++ b/Sources/ResgenSwift/Analytics/Model/AnalyticsCategory.swift
@@ -8,21 +8,24 @@
import Foundation
class AnalyticsCategory {
+
+ // MARK: - Properties
+
let id: String // OnBoarding
var definitions = [AnalyticsDefinition]()
-
+
// MARK: - Init
init(id: String) {
self.id = id
}
-
+
// MARK: - Methods
func hasOneOrMoreMatchingTags(tags: [String]) -> Bool {
let allTags = definitions.flatMap { $0.tags }
let allTagsSet = Set(allTags)
-
+
let intersection = Set(tags).intersection(allTagsSet)
if intersection.isEmpty {
return false
diff --git a/Sources/ResgenSwift/Analytics/Model/AnalyticsDefinition.swift b/Sources/ResgenSwift/Analytics/Model/AnalyticsDefinition.swift
index 2f350cf..41ade03 100644
--- a/Sources/ResgenSwift/Analytics/Model/AnalyticsDefinition.swift
+++ b/Sources/ResgenSwift/Analytics/Model/AnalyticsDefinition.swift
@@ -9,6 +9,9 @@ import Foundation
import ToolCore
class AnalyticsDefinition {
+
+ // MARK: - Properties
+
let id: String
var name: String
var path: String = ""
@@ -18,7 +21,7 @@ class AnalyticsDefinition {
var tags: [String] = []
var parameters: [AnalyticsParameter] = []
var type: TagType
-
+
// MARK: - Init
init(id: String, name: String, type: TagType) {
@@ -26,7 +29,7 @@ class AnalyticsDefinition {
self.name = name
self.type = type
}
-
+
// MARK: - Methods
func hasOneOrMoreMatchingTags(inputTags: [String]) -> Bool {
@@ -35,7 +38,7 @@ class AnalyticsDefinition {
}
return true
}
-
+
// MARK: - Private Methods
private func getFuncName() -> String {
@@ -43,24 +46,24 @@ class AnalyticsDefinition {
id.components(separatedBy: "_").forEach { word in
pascalCaseTitle.append(contentsOf: word.uppercasedFirst())
}
-
+
return "log\(type == .screen ? "Screen" : "Event")\(pascalCaseTitle)"
}
-
+
private func getParameters() -> String {
var params = parameters
var result: String
-
+
if type == .screen {
params = params.filter { param in
!param.replaceIn.isEmpty
}
}
-
+
let paramsString = params.map { parameter in
"\(parameter.name): \(parameter.type)"
}
-
+
if paramsString.count > 2 {
result = """
(
@@ -72,10 +75,10 @@ class AnalyticsDefinition {
(\(paramsString.joined(separator: ", ")))
"""
}
-
+
return result
}
-
+
private func replaceIn() {
for parameter in parameters {
for rep in parameter.replaceIn {
@@ -89,15 +92,15 @@ class AnalyticsDefinition {
}
}
}
-
+
private func getlogFunction() -> String {
var params: [String] = []
var result: String
-
+
let supplementaryParams = parameters.filter { param in
param.replaceIn.isEmpty
}
-
+
supplementaryParams.forEach { param in
params.append("\"\(param.name)\": \(param.name)")
}
@@ -115,7 +118,7 @@ class AnalyticsDefinition {
} else {
result = "[:]"
}
-
+
if type == .screen {
return """
logScreen(
@@ -134,9 +137,9 @@ class AnalyticsDefinition {
"""
}
}
-
+
// MARK: - Raw strings
-
+
func getProperty() -> String {
replaceIn()
return """
@@ -145,7 +148,7 @@ class AnalyticsDefinition {
}
"""
}
-
+
func getStaticProperty() -> String {
replaceIn()
return """
diff --git a/Sources/ResgenSwift/Analytics/Model/AnalyticsFile.swift b/Sources/ResgenSwift/Analytics/Model/AnalyticsFile.swift
index d775768..bbfaba0 100644
--- a/Sources/ResgenSwift/Analytics/Model/AnalyticsFile.swift
+++ b/Sources/ResgenSwift/Analytics/Model/AnalyticsFile.swift
@@ -8,37 +8,42 @@
import Foundation
struct AnalyticsFile: Codable {
+
var categories: [AnalyticsCategoryDTO]
}
struct AnalyticsCategoryDTO: Codable {
+
var id: String
var screens: [AnalyticsDefinitionScreenDTO]?
var events: [AnalyticsDefinitionEventDTO]?
}
struct AnalyticsDefinitionScreenDTO: Codable {
+
var id: String
var name: String
var tags: String
var comments: String?
var parameters: [AnalyticsParameterDTO]?
-
+
var path: String?
}
struct AnalyticsDefinitionEventDTO: Codable {
+
var id: String
var name: String
var tags: String
var comments: String?
var parameters: [AnalyticsParameterDTO]?
-
+
var category: String?
var action: String?
}
struct AnalyticsParameterDTO: Codable {
+
var name: String
var type: String
var replaceIn: String?
diff --git a/Sources/ResgenSwift/Analytics/Model/AnalyticsParameter.swift b/Sources/ResgenSwift/Analytics/Model/AnalyticsParameter.swift
index 9c659c3..3bb7c3a 100644
--- a/Sources/ResgenSwift/Analytics/Model/AnalyticsParameter.swift
+++ b/Sources/ResgenSwift/Analytics/Model/AnalyticsParameter.swift
@@ -8,12 +8,15 @@
import Foundation
class AnalyticsParameter {
+
+ // MARK: - Properties
+
var name: String
var type: String
var replaceIn: [String] = []
-
+
// MARK: - Init
-
+
init(name: String, type: String) {
self.name = name
self.type = type
diff --git a/Sources/ResgenSwift/Analytics/Model/TagType.swift b/Sources/ResgenSwift/Analytics/Model/TagType.swift
index 62d3d28..ae466ce 100644
--- a/Sources/ResgenSwift/Analytics/Model/TagType.swift
+++ b/Sources/ResgenSwift/Analytics/Model/TagType.swift
@@ -8,8 +8,9 @@
import Foundation
extension AnalyticsDefinition {
-
+
enum TagType {
+
case screen
case event
}
diff --git a/Sources/ResgenSwift/Analytics/Model/TargetType.swift b/Sources/ResgenSwift/Analytics/Model/TargetType.swift
index 412d5c3..b9d0ebb 100644
--- a/Sources/ResgenSwift/Analytics/Model/TargetType.swift
+++ b/Sources/ResgenSwift/Analytics/Model/TargetType.swift
@@ -7,7 +7,8 @@
import Foundation
-enum TrackerType: CaseIterable {
+enum TrackerType: CaseIterable, Sendable {
+
case matomo
case firebase
@@ -15,6 +16,7 @@ enum TrackerType: CaseIterable {
switch self {
case .matomo:
"matomo"
+
case .firebase:
"firebase"
}
diff --git a/Sources/ResgenSwift/Analytics/Parser/AnalyticsFileParser.swift b/Sources/ResgenSwift/Analytics/Parser/AnalyticsFileParser.swift
index 1137f8e..6168b73 100644
--- a/Sources/ResgenSwift/Analytics/Parser/AnalyticsFileParser.swift
+++ b/Sources/ResgenSwift/Analytics/Parser/AnalyticsFileParser.swift
@@ -9,16 +9,54 @@ import Foundation
import Yams
class AnalyticsFileParser {
- private static var inputFile: String = ""
- private static var target: String = ""
-
- private static func parseYaml() -> AnalyticsFile {
+
+ // MARK: - Properties
+
+ private var inputFile: String = ""
+ private var target: String = ""
+
+ // MARK: - Methods
+
+ func parse(_ inputFile: String, target: String) -> [AnalyticsCategory] {
+ self.inputFile = inputFile
+ self.target = target
+
+ let tagFile = parseYaml()
+
+ return tagFile
+ .categories
+ .map { categorie in
+ let section = AnalyticsCategory(id: categorie.id)
+
+ if let screens = categorie.screens {
+ section
+ .definitions
+ .append(
+ contentsOf: getTagDefinitionScreen(from: screens)
+ )
+ }
+
+ if let events = categorie.events {
+ section
+ .definitions
+ .append(
+ contentsOf: getTagDefinitionEvent(from: events)
+ )
+ }
+
+ return section
+ }
+ }
+
+ // MARK: - Private methods
+
+ private func parseYaml() -> AnalyticsFile {
guard let data = FileManager().contents(atPath: inputFile) else {
let error = AnalyticsError.fileNotExists(inputFile)
print(error.description)
Analytics.exit(withError: error)
}
-
+
do {
let tagFile = try YAMLDecoder().decode(AnalyticsFile.self, from: data)
return tagFile
@@ -29,13 +67,12 @@ class AnalyticsFileParser {
}
}
- private static func getParameters(from parameters: [AnalyticsParameterDTO]) -> [AnalyticsParameter] {
+ private func getParameters(from parameters: [AnalyticsParameterDTO]) -> [AnalyticsParameter] {
parameters.map { dtoParameter in
// Type
-
let type = dtoParameter.type.uppercasedFirst()
- guard
+ guard
type == "String" ||
type == "Int" ||
type == "Double" ||
@@ -59,7 +96,7 @@ class AnalyticsFileParser {
}
}
- private static func getTagDefinition(
+ private func getTagDefinition(
id: String,
name: String,
type: AnalyticsDefinition.TagType,
@@ -72,20 +109,20 @@ class AnalyticsFileParser {
.components(separatedBy: ",")
.map { $0.removeLeadingTrailingWhitespace() }
- if let comments = comments {
+ if let comments {
definition.comments = comments
}
-
- if let parameters = parameters {
- definition.parameters = Self.getParameters(from: parameters)
+
+ if let parameters {
+ definition.parameters = getParameters(from: parameters)
}
-
+
return definition
}
-
- private static func getTagDefinitionScreen(from screens: [AnalyticsDefinitionScreenDTO]) -> [AnalyticsDefinition] {
+
+ private func getTagDefinitionScreen(from screens: [AnalyticsDefinitionScreenDTO]) -> [AnalyticsDefinition] {
screens.map { screen in
- let definition: AnalyticsDefinition = Self.getTagDefinition(
+ let definition: AnalyticsDefinition = getTagDefinition(
id: screen.id,
name: screen.name,
type: .screen,
@@ -109,10 +146,10 @@ class AnalyticsFileParser {
return definition
}
}
-
- private static func getTagDefinitionEvent(from events: [AnalyticsDefinitionEventDTO]) -> [AnalyticsDefinition] {
+
+ private func getTagDefinitionEvent(from events: [AnalyticsDefinitionEventDTO]) -> [AnalyticsDefinition] {
events.map { event in
- let definition: AnalyticsDefinition = Self.getTagDefinition(
+ let definition: AnalyticsDefinition = getTagDefinition(
id: event.id,
name: event.name,
type: .event,
@@ -144,35 +181,4 @@ class AnalyticsFileParser {
return definition
}
}
-
- static func parse(_ inputFile: String, target: String) -> [AnalyticsCategory] {
- self.inputFile = inputFile
- self.target = target
-
- let tagFile = Self.parseYaml()
-
- return tagFile
- .categories
- .map { categorie in
- let section: AnalyticsCategory = AnalyticsCategory(id: categorie.id)
-
- if let screens = categorie.screens {
- section
- .definitions
- .append(
- contentsOf: Self.getTagDefinitionScreen(from: screens)
- )
- }
-
- if let events = categorie.events {
- section
- .definitions
- .append(
- contentsOf: Self.getTagDefinitionEvent(from: events)
- )
- }
-
- return section
- }
- }
}
diff --git a/Sources/ResgenSwift/Colors/Colors.swift b/Sources/ResgenSwift/Colors/Colors.swift
index 722d08a..0d5cab2 100644
--- a/Sources/ResgenSwift/Colors/Colors.swift
+++ b/Sources/ResgenSwift/Colors/Colors.swift
@@ -1,124 +1,134 @@
//
// main.swift
-//
+//
//
// Created by Thibaut Schmitt on 20/12/2021.
//
-import ToolCore
+@preconcurrency import ArgumentParser
import Foundation
-import ArgumentParser
+import ToolCore
struct Colors: ParsableCommand {
-
+
// MARK: - CommandConfiguration
-
- static var configuration = CommandConfiguration(
+
+ static let configuration = CommandConfiguration(
abstract: "A utility for generate colors assets and their getters.",
version: ResgenSwiftVersion
)
-
+
// MARK: - Static
-
+
static let toolName = "Color"
static let defaultExtensionName = "Color"
static let defaultExtensionNameUIKit = "UIColor"
static let assetsColorsFolderName = "Colors"
-
+
// MARK: - Command options
-
+
@OptionGroup var options: ColorsToolOptions
-
+
// MARK: - Run
-
- public func run() throws {
+
+ func run() throws {
print("[\(Self.toolName)] Starting colors generation")
-
+
// Check requirements
guard checkRequirements() else { return }
-
+
print("[\(Self.toolName)] Will generate colors")
-
+
// Delete current colors
deleteCurrentColors()
// Get colors to generate
- let parsedColors = ColorFileParser.parse(options.inputFile,
- colorStyle: options.style)
+ let parsedColors = ColorFileParser.parse(
+ options.inputFile,
+ colorStyle: options.style
+ )
// -> Time: 0.0020350217819213867 seconds
// Generate all colors in xcassets
- ColorXcassetHelper.generateXcassetColors(colors: parsedColors,
- to: options.xcassetsPath)
+ ColorXcassetHelper.generateXcassetColors(
+ colors: parsedColors,
+ to: options.xcassetsPath
+ )
// -> Time: 3.4505380392074585 seconds
// Generate extension
- ColorExtensionGenerator.writeExtensionFile(colors: parsedColors,
- staticVar: options.staticMembers,
- extensionName: options.extensionName,
- extensionFilePath: options.extensionFilePath,
- isSwiftUI: true)
-
+ ColorExtensionGenerator.writeExtensionFile(
+ colors: parsedColors,
+ staticVar: options.staticMembers,
+ extensionName: options.extensionName,
+ extensionFilePath: options.extensionFilePath,
+ isSwiftUI: true
+ )
+
// Generate extension
- ColorExtensionGenerator.writeExtensionFile(colors: parsedColors,
- staticVar: options.staticMembers,
- extensionName: options.extensionNameUIKit,
- extensionFilePath: options.extensionFilePathUIKit,
- isSwiftUI: false)
-
+ ColorExtensionGenerator.writeExtensionFile(
+ colors: parsedColors,
+ staticVar: options.staticMembers,
+ extensionName: options.extensionNameUIKit,
+ extensionFilePath: options.extensionFilePathUIKit,
+ isSwiftUI: false
+ )
+
print("[\(Self.toolName)] Colors generated")
}
-
+
// MARK: - Requirements
-
+
private func checkRequirements() -> Bool {
let fileManager = FileManager()
-
+
// Check if input file exists
guard fileManager.fileExists(atPath: options.inputFile) else {
let error = ColorsToolError.fileNotExists(options.inputFile)
print(error.description)
- Colors.exit(withError: error)
+ Self.exit(withError: error)
}
-
+
// Check if xcassets file exists
guard fileManager.fileExists(atPath: options.xcassetsPath) else {
let error = ColorsToolError.fileNotExists(options.xcassetsPath)
print(error.description)
- Colors.exit(withError: error)
+ Self.exit(withError: error)
}
-
+
// Extension for UIKit and SwiftUI should have different name
guard options.extensionName != options.extensionNameUIKit else {
let error = ColorsToolError.extensionNamesCollision(options.extensionName)
print(error.description)
- Colors.exit(withError: error)
+ Self.exit(withError: error)
}
-
+
// Check if needed to regenerate
- guard GeneratorChecker.shouldGenerate(force: options.forceGeneration,
- inputFilePath: options.inputFile,
- extensionFilePath: options.extensionFilePath) else {
+ guard GeneratorChecker.shouldGenerate(
+ force: options.forceGeneration,
+ inputFilePath: options.inputFile,
+ extensionFilePath: options.extensionFilePath
+ ) else {
print("[\(Self.toolName)] Colors are already up to date :) ")
return false
}
-
+
return true
}
-
+
// MARK: - Helpers
-
+
private func deleteCurrentColors() {
let fileManager = FileManager()
let assetsColorPath = "\(options.xcassetsPath)/Colors"
-
+
if fileManager.fileExists(atPath: assetsColorPath) {
do {
try fileManager.removeItem(atPath: assetsColorPath)
} catch {
let error = ColorsToolError.deleteExistingColors("\(options.xcassetsPath)/Colors")
print(error.description)
- Colors.exit(withError: error)
+ Self.exit(withError: error)
}
}
}
diff --git a/Sources/ResgenSwift/Colors/ColorsToolError.swift b/Sources/ResgenSwift/Colors/ColorsToolError.swift
index e45f15c..2a54abf 100644
--- a/Sources/ResgenSwift/Colors/ColorsToolError.swift
+++ b/Sources/ResgenSwift/Colors/ColorsToolError.swift
@@ -8,6 +8,7 @@
import Foundation
enum ColorsToolError: Error {
+
case extensionNamesCollision(String)
case badFormat(String)
case writeAsset(String)
@@ -16,30 +17,30 @@ enum ColorsToolError: Error {
case fileNotExists(String)
case badColorDefinition(String, String)
case deleteExistingColors(String)
-
+
var description: String {
switch self {
case .extensionNamesCollision(let extensionName):
return "error: [\(Fonts.toolName)] Error on extension names, extension name and SwiftUI extension name should be different (\(extensionName) is used on both)"
-
+
case .badFormat(let info):
return "error: [\(Colors.toolName)] Bad line format: \(info). Accepted format are: colorName=\"#RGB/#ARGB\"; colorName \"#RGB/#ARGB\"; colorName \"#RGB/#ARGB\" \"#RGB/#ARGB\""
-
+
case .writeAsset(let info):
return "error: [\(Colors.toolName)] An error occured while writing color in Xcasset: \(info)"
-
+
case .createAssetFolder(let assetsFolder):
return "error: [\(Colors.toolName)] An error occured while creating colors folder `\(assetsFolder)`"
-
- case .writeExtension(let filename, let info):
+
+ case let .writeExtension(filename, info):
return "error: [\(Colors.toolName)] An error occured while writing extension in \(filename): \(info)"
-
+
case .fileNotExists(let filename):
return "error: [\(Colors.toolName)] File \(filename) does not exists"
-
- case .badColorDefinition(let lightColor, let darkColor):
+
+ case let .badColorDefinition(lightColor, darkColor):
return "error: [\(Colors.toolName)] One of these two colors has invalid synthax: -\(lightColor)- or -\(darkColor)-"
-
+
case .deleteExistingColors(let assetsFolder):
return "error: [\(Colors.toolName)] An error occured while deleting colors folder `\(assetsFolder)`"
}
diff --git a/Sources/ResgenSwift/Colors/ColorsToolOptions.swift b/Sources/ResgenSwift/Colors/ColorsToolOptions.swift
index 868d753..97eb31d 100644
--- a/Sources/ResgenSwift/Colors/ColorsToolOptions.swift
+++ b/Sources/ResgenSwift/Colors/ColorsToolOptions.swift
@@ -1,38 +1,41 @@
//
// ColorsToolOptions.swift
-//
+//
//
// Created by Thibaut Schmitt on 17/01/2022.
//
-import Foundation
import ArgumentParser
+import Foundation
+
+// swiftlint:disable no_grouping_extension
struct ColorsToolOptions: 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: ColorStyle
-
+
@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: "Tell if it will generate static properties or not")
var staticMembers: Bool = false
-
+
@Option(help: "Extension name. If not specified, it will generate an Color extension.")
var extensionName: String = Colors.defaultExtensionName
-
+
@Option(help: "SwiftUI Extension name. If not specified, it will generate an UIColor extension.")
var extensionNameUIKit: String = Colors.defaultExtensionNameUIKit
-
+
@Option(help: "Extension suffix. Ex: MyApp, it will generate {extensionName}+ColorsMyApp.swift")
var extensionSuffix: String?
}
@@ -40,29 +43,29 @@ struct ColorsToolOptions: ParsableArguments {
// MARK: - Computed var
extension ColorsToolOptions {
-
+
// MARK: - SwiftUI
-
+
var extensionFileName: String {
- if let extensionSuffix = extensionSuffix {
+ if let extensionSuffix {
return "\(extensionName)+\(extensionSuffix).swift"
}
return "\(extensionName).swift"
}
-
+
var extensionFilePath: String {
"\(extensionOutputPath)/\(extensionFileName)"
}
-
+
// MARK: - UIKit
-
+
var extensionFileNameUIKit: String {
- if let extensionSuffix = extensionSuffix {
+ if let extensionSuffix {
return "\(extensionNameUIKit)+\(extensionSuffix).swift"
}
return "\(extensionNameUIKit).swift"
}
-
+
var extensionFilePathUIKit: String {
"\(extensionOutputPath)/\(extensionFileNameUIKit)"
}
diff --git a/Sources/ResgenSwift/Colors/Generator/ColorExtensionGenerator.swift b/Sources/ResgenSwift/Colors/Generator/ColorExtensionGenerator.swift
index f974b05..2daefac 100644
--- a/Sources/ResgenSwift/Colors/Generator/ColorExtensionGenerator.swift
+++ b/Sources/ResgenSwift/Colors/Generator/ColorExtensionGenerator.swift
@@ -1,6 +1,6 @@
//
// ColorExtensionGenerator.swift
-//
+//
//
// Created by Thibaut Schmitt on 20/12/2021.
//
@@ -9,38 +9,44 @@ import Foundation
import ToolCore
struct ColorExtensionGenerator {
-
+
let colors: [ParsedColor]
let extensionClassname: String
-
+
// MARK: - UIKit
-
- static func writeExtensionFile(colors: [ParsedColor],
- staticVar: Bool,
- extensionName: String,
- extensionFilePath: String,
- isSwiftUI: Bool) {
+
+ static func writeExtensionFile(
+ colors: [ParsedColor],
+ staticVar: Bool,
+ extensionName: String,
+ extensionFilePath: String,
+ isSwiftUI: Bool
+ ) {
// Create extension content
- let extensionContent = Self.getExtensionContent(colors: colors,
- staticVar: staticVar,
- extensionName: extensionName,
- isSwiftUI: isSwiftUI)
-
+ let extensionContent = Self.getExtensionContent(
+ colors: colors,
+ staticVar: staticVar,
+ extensionName: extensionName,
+ isSwiftUI: isSwiftUI
+ )
+
// Write content
let extensionFilePathURL = URL(fileURLWithPath: extensionFilePath)
do {
try extensionContent.write(to: extensionFilePathURL, atomically: false, encoding: .utf8)
- } catch let error {
+ } catch {
let error = ColorsToolError.writeExtension(extensionFilePath, error.localizedDescription)
print(error.description)
Colors.exit(withError: error)
}
}
-
- static func getExtensionContent(colors: [ParsedColor],
- staticVar: Bool,
- extensionName: String,
- isSwiftUI: Bool) -> String {
+
+ static func getExtensionContent(
+ colors: [ParsedColor],
+ staticVar: Bool,
+ extensionName: String,
+ isSwiftUI: Bool
+ ) -> String {
[
Self.getHeader(extensionClassname: extensionName, isSwiftUI: isSwiftUI),
Self.getProperties(for: colors, withStaticVar: staticVar, isSwiftUI: isSwiftUI),
@@ -48,7 +54,7 @@ struct ColorExtensionGenerator {
]
.joined(separator: "\n")
}
-
+
private static func getHeader(extensionClassname: String, isSwiftUI: Bool) -> String {
"""
// Generated by ResgenSwift.\(Colors.toolName) \(ResgenSwiftVersion)
@@ -58,17 +64,19 @@ struct ColorExtensionGenerator {
extension \(extensionClassname) {\n
"""
}
-
+
private static func getFooter() -> String {
"""
}
-
+
"""
}
-
- private static func getProperties(for colors: [ParsedColor],
- withStaticVar staticVar: Bool,
- isSwiftUI: Bool) -> String {
+
+ private static func getProperties(
+ for colors: [ParsedColor],
+ withStaticVar staticVar: Bool,
+ isSwiftUI: Bool
+ ) -> String {
colors.map {
$0.getColorProperty(isStatic: staticVar, isSwiftUI: isSwiftUI)
}
diff --git a/Sources/ResgenSwift/Colors/Generator/ColorXcassetHelper.swift b/Sources/ResgenSwift/Colors/Generator/ColorXcassetHelper.swift
index 7824264..0daff91 100644
--- a/Sources/ResgenSwift/Colors/Generator/ColorXcassetHelper.swift
+++ b/Sources/ResgenSwift/Colors/Generator/ColorXcassetHelper.swift
@@ -1,6 +1,6 @@
//
// ColorXcassetHelper.swift
-//
+//
//
// Created by Thibaut Schmitt on 20/12/2021.
//
@@ -8,37 +8,39 @@
import Foundation
import ToolCore
-struct ColorXcassetHelper {
-
+enum ColorXcassetHelper {
+
static func generateXcassetColors(colors: [ParsedColor], to xcassetsPath: String) {
colors.forEach {
Self.generateColorSetAssets(from: $0, to: xcassetsPath)
}
}
-
+
// Generate ColorSet in XCAssets file
private static func generateColorSetAssets(from color: ParsedColor, to xcassetsPath: String) {
// Create ColorSet
let colorSetPath = "\(xcassetsPath)/Colors/\(color.name).colorset"
let contentsJsonPath = "\(colorSetPath)/Contents.json"
-
+
let fileManager = FileManager()
if fileManager.fileExists(atPath: colorSetPath) == false {
do {
- try fileManager.createDirectory(atPath: colorSetPath,
- withIntermediateDirectories: true)
+ try fileManager.createDirectory(
+ atPath: colorSetPath,
+ withIntermediateDirectories: true
+ )
} catch {
let error = ColorsToolError.createAssetFolder(colorSetPath)
print(error.description)
Colors.exit(withError: error)
}
}
-
+
// Write content in Contents.json
let contentsJsonPathURL = URL(fileURLWithPath: contentsJsonPath)
do {
try color.contentsJSON().write(to: contentsJsonPathURL, atomically: false, encoding: .utf8)
- } catch let error {
+ } catch {
let error = ColorsToolError.writeAsset(error.localizedDescription)
print(error.description)
Colors.exit(withError: error)
diff --git a/Sources/ResgenSwift/Colors/Model/ColorStyle.swift b/Sources/ResgenSwift/Colors/Model/ColorStyle.swift
index 78948aa..bff63bf 100644
--- a/Sources/ResgenSwift/Colors/Model/ColorStyle.swift
+++ b/Sources/ResgenSwift/Colors/Model/ColorStyle.swift
@@ -5,13 +5,14 @@
// Created by Thibaut Schmitt on 29/08/2022.
//
-import Foundation
import ArgumentParser
+import Foundation
enum ColorStyle: String, Decodable, ExpressibleByArgument {
+
case light
case all
-
+
static var allValueStrings: [String] {
[
Self.light.rawValue,
diff --git a/Sources/ResgenSwift/Colors/Model/ParsedColor.swift b/Sources/ResgenSwift/Colors/Model/ParsedColor.swift
index 98c7a0b..918f936 100644
--- a/Sources/ResgenSwift/Colors/Model/ParsedColor.swift
+++ b/Sources/ResgenSwift/Colors/Model/ParsedColor.swift
@@ -8,28 +8,29 @@
import Foundation
struct ParsedColor {
+
let name: String
let light: String
let dark: String
-
+
// Generate Contents.json content
func contentsJSON() -> String {
let lightARGB = light.colorComponent()
let darkARGB = dark.colorComponent()
-
+
let allComponents = [
lightARGB.alpha, lightARGB.red, lightARGB.green, lightARGB.blue,
darkARGB.alpha, darkARGB.red, darkARGB.green, darkARGB.blue
].map {
$0.isEmpty
}
-
+
guard allComponents.contains(true) == false else {
let error = ColorsToolError.badColorDefinition(light, dark)
print(error.description)
Colors.exit(withError: error)
}
-
+
return """
{
"colors": [
@@ -71,9 +72,9 @@ struct ParsedColor {
}
"""
}
-
+
// MARK: - UIKit
-
+
func getColorProperty(isStatic: Bool, isSwiftUI: Bool) -> String {
if isSwiftUI {
return """
diff --git a/Sources/ResgenSwift/Colors/Parser/ColorFileParser.swift b/Sources/ResgenSwift/Colors/Parser/ColorFileParser.swift
index 1db14c2..297f6aa 100644
--- a/Sources/ResgenSwift/Colors/Parser/ColorFileParser.swift
+++ b/Sources/ResgenSwift/Colors/Parser/ColorFileParser.swift
@@ -7,44 +7,45 @@
import Foundation
-class ColorFileParser {
+enum ColorFileParser {
+
static func parse(_ inputFile: String, colorStyle: ColorStyle) -> [ParsedColor] {
// Get content of input file
- let inputFileContent = try! String(contentsOfFile: inputFile, encoding: .utf8)
+ let inputFileContent = try! String(contentsOfFile: inputFile, encoding: .utf8) // swiftlint:disable:this force_try
let colorsByLines = inputFileContent.components(separatedBy: CharacterSet.newlines)
-
+
// Iterate on each line of input file
return parseLines(lines: colorsByLines, colorStyle: colorStyle)
}
-
+
static func parseLines(lines: [String], colorStyle: ColorStyle) -> [ParsedColor] {
lines
.enumerated()
- .compactMap { lineNumber, colorLine in
- // Required format:
- // colorName = "#RGB/#ARGB", colorName "#RGB/#ARGB", colorName "#RGB/#ARGB" "#RGB/#ARGB"
+ .compactMap { _, colorLine in // swiftlint:disable:this unused_enumerated
+ // Required format:
+ // colorName = "#RGB/#ARGB", colorName "#RGB/#ARGB", colorName "#RGB/#ARGB" "#RGB/#ARGB"
let colorLineCleanedUp = colorLine
.removeLeadingWhitespace()
.removeTrailingWhitespace()
.replacingOccurrences(of: "=", with: "") // Keep compat with current file format
-
+
guard colorLineCleanedUp.hasPrefix("#") == false, colorLineCleanedUp.isEmpty == false else {
// debugPrint("[\(Colors.toolName)] ⚠️ BadFormat or empty line (line number: \(lineNumber + 1)). Skip this line")
return nil
}
-
+
let colorContent = colorLineCleanedUp.split(separator: " ")
-
+
guard colorContent.count >= 2 else {
let error = ColorsToolError.badFormat(colorLine)
print(error.description)
Colors.exit(withError: error)
}
-
+
switch colorStyle {
case .light:
return ParsedColor(name: String(colorContent[0]), light: String(colorContent[1]), dark: String(colorContent[1]))
-
+
case .all:
if colorContent.count == 3 {
return ParsedColor(name: String(colorContent[0]), light: String(colorContent[1]), dark: String(colorContent[2]))
diff --git a/Sources/ResgenSwift/Fonts/FontOptions.swift b/Sources/ResgenSwift/Fonts/FontOptions.swift
index 24d2021..42a3218 100644
--- a/Sources/ResgenSwift/Fonts/FontOptions.swift
+++ b/Sources/ResgenSwift/Fonts/FontOptions.swift
@@ -5,31 +5,34 @@
// Created by Thibaut Schmitt on 17/01/2022.
//
-import Foundation
import ArgumentParser
+import Foundation
+
+// swiftlint:disable no_grouping_extension
struct FontsOptions: 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: "Tell if it will generate static properties or methods")
var staticMembers: Bool = false
-
+
@Option(help: "Extension name. If not specified, it will generate an Font extension.")
var extensionName: String = Fonts.defaultExtensionName
-
+
@Option(help: "Extension name. If not specified, it will generate an UIFont extension.")
var extensionNameUIKit: String = Fonts.defaultExtensionNameUIKit
-
+
@Option(help: "Extension suffix. Ex: MyApp, it will generate {extensionName}+FontsMyApp.swift")
var extensionSuffix: String = ""
-
+
@Option(name: .customLong("info-plist-paths"), help: "Info.plist paths (array). Will be used to update UIAppFonts content")
fileprivate var infoPlistPathsRaw: String = ""
}
@@ -37,29 +40,29 @@ struct FontsOptions: ParsableArguments {
// MARK: - Computed var
extension FontsOptions {
-
+
// MARK: - SwiftUI
-
+
var extensionFileName: String {
if extensionSuffix.isEmpty == false {
return "\(extensionName)+\(extensionSuffix).swift"
}
return "\(extensionName).swift"
}
-
+
var extensionFilePath: String {
"\(extensionOutputPath)/\(extensionFileName)"
}
-
+
// MARK: - UIKit
-
+
var extensionFileNameUIKit: String {
if extensionSuffix.isEmpty == false {
return "\(extensionNameUIKit)+\(extensionSuffix).swift"
}
return "\(extensionNameUIKit).swift"
}
-
+
var extensionFilePathUIKit: String {
"\(extensionOutputPath)/\(extensionFileNameUIKit)"
}
diff --git a/Sources/ResgenSwift/Fonts/Fonts.swift b/Sources/ResgenSwift/Fonts/Fonts.swift
index fdcc166..9e4bbca 100644
--- a/Sources/ResgenSwift/Fonts/Fonts.swift
+++ b/Sources/ResgenSwift/Fonts/Fonts.swift
@@ -1,98 +1,109 @@
//
// Fonts.swift
-//
+//
//
// Created by Thibaut Schmitt on 13/12/2021.
//
-import ToolCore
-import Foundation
import ArgumentParser
+import Foundation
+import ToolCore
struct Fonts: ParsableCommand {
-
+
// MARK: - CommandConfiguration
-
+
static var configuration = CommandConfiguration(
abstract: "A utility to generate an helpful etension to access your custom font from code and also Info.plist UIAppsFont content.",
version: ResgenSwiftVersion
)
-
+
// MARK: - Static
-
+
static let toolName = "Fonts"
static let defaultExtensionName = "Font"
static let defaultExtensionNameUIKit = "UIFont"
-
+
// MARK: - Command Options
-
+
@OptionGroup var options: FontsOptions
-
+
// MARK: - Run
-
- public func run() throws {
+
+ func run() throws {
print("[\(Self.toolName)] Starting fonts generation")
print("[\(Self.toolName)] Will use inputFile \(options.inputFile) to generate fonts")
-
+
// Check requirements
guard checkRequirements() else { return }
-
+
print("[\(Self.toolName)] Will generate fonts")
-
+
// Get fonts to generate
let fontsToGenerate = FontFileParser.parse(options.inputFile)
-
+
// Get real font names
- let inputFolder = URL(fileURLWithPath: options.inputFile).deletingLastPathComponent().relativePath
- let fontsNames = FontsToolHelper.getFontPostScriptName(for: fontsToGenerate,
- inputFolder: inputFolder)
-
+ let inputFolder = URL(fileURLWithPath: options.inputFile)
+ .deletingLastPathComponent()
+ .relativePath
+
+ let fontsNames = FontsToolHelper.getFontPostScriptName(
+ for: fontsToGenerate,
+ inputFolder: inputFolder
+ )
+
// Generate extension
- FontExtensionGenerator.writeExtensionFile(fontsNames: fontsNames,
- staticVar: options.staticMembers,
- extensionName: options.extensionName,
- extensionFilePath: options.extensionFilePath,
- isSwiftUI: true)
-
- FontExtensionGenerator.writeExtensionFile(fontsNames: fontsNames,
- staticVar: options.staticMembers,
- extensionName: options.extensionNameUIKit,
- extensionFilePath: options.extensionFilePathUIKit,
- isSwiftUI: false)
-
+ FontExtensionGenerator.writeExtensionFile(
+ fontsNames: fontsNames,
+ staticVar: options.staticMembers,
+ extensionName: options.extensionName,
+ extensionFilePath: options.extensionFilePath,
+ isSwiftUI: true
+ )
+
+ FontExtensionGenerator.writeExtensionFile(
+ fontsNames: fontsNames,
+ staticVar: options.staticMembers,
+ extensionName: options.extensionNameUIKit,
+ extensionFilePath: options.extensionFilePathUIKit,
+ isSwiftUI: false
+ )
+
print("Info.plist has been updated with:")
print("\(FontPlistGenerator.generatePlistUIAppsFontContent(for: fontsNames, infoPlistPaths: options.infoPlistPaths))")
-
+
print("[\(Self.toolName)] Fonts generated")
}
-
+
// MARK: - Requirements
-
+
private func checkRequirements() -> Bool {
let fileManager = FileManager()
-
+
// Check input file exists
guard fileManager.fileExists(atPath: options.inputFile) else {
let error = FontsToolError.fileNotExists(options.inputFile)
print(error.description)
- Fonts.exit(withError: error)
+ Self.exit(withError: error)
}
-
+
// Extension for UIKit and SwiftUI should have different name
guard options.extensionName != options.extensionNameUIKit else {
let error = FontsToolError.extensionNamesCollision(options.extensionName)
print(error.description)
- Fonts.exit(withError: error)
+ Self.exit(withError: error)
}
-
+
// Check if needed to regenerate
- guard GeneratorChecker.shouldGenerate(force: options.forceGeneration,
- inputFilePath: options.inputFile,
- extensionFilePath: options.extensionFilePath) else {
+ guard GeneratorChecker.shouldGenerate(
+ force: options.forceGeneration,
+ inputFilePath: options.inputFile,
+ extensionFilePath: options.extensionFilePath
+ ) else {
print("[\(Self.toolName)] Fonts are already up to date :) ")
return false
}
-
+
return true
}
}
diff --git a/Sources/ResgenSwift/Fonts/FontsToolError.swift b/Sources/ResgenSwift/Fonts/FontsToolError.swift
index d553cd3..5e851e1 100644
--- a/Sources/ResgenSwift/Fonts/FontsToolError.swift
+++ b/Sources/ResgenSwift/Fonts/FontsToolError.swift
@@ -8,27 +8,28 @@
import Foundation
enum FontsToolError: Error {
+
case extensionNamesCollision(String)
case fcScan(String, Int32, String?)
case inputFolderNotFound(String)
case fileNotExists(String)
case writeExtension(String, String)
-
+
var description: String {
switch self {
case .extensionNamesCollision(let extensionName):
return "error: [\(Fonts.toolName)] Error on extension names, extension name and SwiftUI extension name should be different (\(extensionName) is used on both)"
-
- case .fcScan(let path, let code, let output):
+
+ case let .fcScan(path, code, output):
return "error: [\(Fonts.toolName)] Error while getting fontName (fc-scan --format %{postscriptname} \(path). fc-scan exit with \(code) and output is: \(output ?? "no output")"
-
+
case .inputFolderNotFound(let inputFolder):
return "error: [\(Fonts.toolName)] Input folder not found: \(inputFolder)"
-
+
case .fileNotExists(let filename):
return "error: [\(Fonts.toolName)] File \(filename) does not exists"
-
- case .writeExtension(let filename, let info):
+
+ case let .writeExtension(filename, info):
return "error: [\(Fonts.toolName)] An error occured while writing extension in \(filename): \(info)"
}
}
diff --git a/Sources/ResgenSwift/Fonts/FontsToolHelper.swift b/Sources/ResgenSwift/Fonts/FontsToolHelper.swift
index 02b3b8d..26bd663 100644
--- a/Sources/ResgenSwift/Fonts/FontsToolHelper.swift
+++ b/Sources/ResgenSwift/Fonts/FontsToolHelper.swift
@@ -1,6 +1,6 @@
//
// FontsToolHelper.swift
-//
+//
//
// Created by Thibaut Schmitt on 13/12/2021.
//
@@ -8,32 +8,32 @@
import Foundation
import ToolCore
-class FontsToolHelper {
-
+enum FontsToolHelper {
+
static func getFontPostScriptName(for fonts: [String], inputFolder: String) -> [FontName] {
let fontsFilenames = Self.getFontsFilenames(fromInputFolder: inputFolder)
.filter { fontNameWithPath in
let fontName = URL(fileURLWithPath: fontNameWithPath)
.deletingPathExtension()
.lastPathComponent
-
+
if fonts.contains(fontName) {
return true
}
return false
}
-
+
let fontsFilesnamesWithPath = fontsFilenames.map {
"\(inputFolder)/\($0)"
}
-
+
return fontsFilesnamesWithPath.compactMap {
Self.getFontName(atPath: $0)
}
}
-
+
// MARK: - Private
-
+
private static func getFontsFilenames(fromInputFolder inputFolder: String) -> [String] {
// Get a enumerator for all files
let fileManager = FileManager()
@@ -42,32 +42,41 @@ class FontsToolHelper {
print(error.description)
Fonts.exit(withError: error)
}
-
- let enumerator: FileManager.DirectoryEnumerator = fileManager.enumerator(atPath: inputFolder)!
-
+
+ let enumerator: FileManager.DirectoryEnumerator = fileManager.enumerator(atPath: inputFolder)! // swiftlint:disable:this force_unwrapping
+
// Filters font files
- let fontsFileNames: [String] = (enumerator.allObjects as! [String])
+ let fontsFileNames: [String] = (enumerator.allObjects as! [String]) // swiftlint:disable:this force_cast
.filter {
if $0.hasSuffix(".ttf") || $0.hasSuffix(".otf") {
return true
}
return false
}
-
+
return fontsFileNames
}
-
- private static func getFontName(atPath path: String) -> String {
- //print("fc-scan --format %{postscriptname} \(path)")
+
+ private static func getFontName(atPath path: String) -> FontName {
+ // print("fc-scan --format %{postscriptname} \(path)")
// Get real font name
let task = Shell.shell(["fc-scan", "--format", "%{postscriptname}", path])
-
- guard let fontName = task.output, task.terminationStatus == 0 else {
+
+ guard let postscriptName = task.output, task.terminationStatus == 0 else {
let error = FontsToolError.fcScan(path, task.terminationStatus, task.output)
print(error.description)
Fonts.exit(withError: error)
}
-
- return fontName
+
+ let pathURL = URL(fileURLWithPath: path)
+ let filename = pathURL
+ .deletingPathExtension()
+ .lastPathComponent
+
+ return FontName(
+ postscriptName: postscriptName,
+ filename: filename,
+ fileExtension: pathURL.pathExtension
+ )
}
}
diff --git a/Sources/ResgenSwift/Fonts/Generator/FontPlistGenerator.swift b/Sources/ResgenSwift/Fonts/Generator/FontPlistGenerator.swift
index 37259c3..2ce8722 100644
--- a/Sources/ResgenSwift/Fonts/Generator/FontPlistGenerator.swift
+++ b/Sources/ResgenSwift/Fonts/Generator/FontPlistGenerator.swift
@@ -8,37 +8,44 @@
import Foundation
import ToolCore
-class FontPlistGenerator {
+enum FontPlistGenerator {
+
static func generatePlistUIAppsFontContent(for fonts: [FontName], infoPlistPaths: [String]) -> String {
let fontsToAddToPlist = fonts
.compactMap { $0 }
-
+
// Update each plist
infoPlistPaths.forEach { infoPlist in
// Remove UIAppFonts value
- Shell.shell(launchPath: "/usr/libexec/PlistBuddy",
- ["-c", "delete :UIAppFonts", infoPlist])
-
+ Shell.shell(
+ launchPath: "/usr/libexec/PlistBuddy",
+ ["-c", "delete :UIAppFonts", infoPlist]
+ )
+
// Add UIAppFonts empty array
debugPrint("Will PlistBuddy -c add :UIAppFonts array \(infoPlist)")
- Shell.shell(launchPath: "/usr/libexec/PlistBuddy",
- ["-c", "add :UIAppFonts array", infoPlist])
+ Shell.shell(
+ launchPath: "/usr/libexec/PlistBuddy",
+ ["-c", "add :UIAppFonts array", infoPlist]
+ )
// Fill array with fonts
fontsToAddToPlist
- .forEach {
- Shell.shell(launchPath: "/usr/libexec/PlistBuddy",
- ["-c", "add :UIAppFonts: string \($0)", infoPlist])
+ .forEach { fontName in
+ Shell.shell(
+ launchPath: "/usr/libexec/PlistBuddy",
+ ["-c", "add :UIAppFonts: string \(fontName.filename).\(fontName.fileExtension)", infoPlist]
+ )
}
}
var plistData = "UIAppFonts\n\t\n"
fontsToAddToPlist
- .forEach {
- plistData += "\t\t\($0)\n"
+ .forEach { fontName in
+ plistData += "\t\t\(fontName.filename).\(fontName.fileExtension)\n"
}
plistData += "\t"
-
+
return plistData
}
}
diff --git a/Sources/ResgenSwift/Fonts/Generator/FontToolContentGenerator.swift b/Sources/ResgenSwift/Fonts/Generator/FontToolContentGenerator.swift
index 6d9d228..b0cd2bf 100644
--- a/Sources/ResgenSwift/Fonts/Generator/FontToolContentGenerator.swift
+++ b/Sources/ResgenSwift/Fonts/Generator/FontToolContentGenerator.swift
@@ -8,45 +8,51 @@
import Foundation
import ToolCore
-class FontExtensionGenerator {
+enum FontExtensionGenerator {
- private static func getFontNameEnum(fontsNames: [String]) -> String {
+ private static func getFontNameEnum(fontsNames: [FontName]) -> String {
var enumDefinition = " enum FontName: String {\n"
-
+
fontsNames.forEach {
- enumDefinition += " case \($0.fontNameSanitize) = \"\($0)\"\n"
+ enumDefinition += " case \($0.fontNameSanitize) = \"\($0.postscriptName)\"\n"
}
enumDefinition += " }\n"
-
+
return enumDefinition
}
-
- static func writeExtensionFile(fontsNames: [String],
- staticVar: Bool,
- extensionName: String,
- extensionFilePath: String,
- isSwiftUI: Bool) {
+
+ static func writeExtensionFile(
+ fontsNames: [FontName],
+ staticVar: Bool,
+ extensionName: String,
+ extensionFilePath: String,
+ isSwiftUI: Bool
+ ) {
// Create extension content
- let extensionContent = Self.getExtensionContent(fontsNames: fontsNames,
- staticVar: staticVar,
- extensionName: extensionName,
- isSwiftUI: isSwiftUI)
-
+ let extensionContent = Self.getExtensionContent(
+ fontsNames: fontsNames,
+ staticVar: staticVar,
+ extensionName: extensionName,
+ isSwiftUI: isSwiftUI
+ )
+
// Write content
let extensionFilePathURL = URL(fileURLWithPath: extensionFilePath)
do {
try extensionContent.write(to: extensionFilePathURL, atomically: false, encoding: .utf8)
- } catch let error {
+ } catch {
let error = FontsToolError.writeExtension(extensionFilePath, error.localizedDescription)
print(error.description)
Fonts.exit(withError: error)
}
}
-
- static func getExtensionContent(fontsNames: [String],
- staticVar: Bool,
- extensionName: String,
- isSwiftUI: Bool) -> String {
+
+ static func getExtensionContent(
+ fontsNames: [FontName],
+ staticVar: Bool,
+ extensionName: String,
+ isSwiftUI: Bool
+ ) -> String {
[
Self.getHeader(extensionClassname: extensionName, isSwiftUI: isSwiftUI),
Self.getFontNameEnum(fontsNames: fontsNames),
@@ -55,34 +61,34 @@ class FontExtensionGenerator {
]
.joined(separator: "\n")
}
-
+
private static func getHeader(extensionClassname: String, isSwiftUI: Bool) -> String {
"""
// Generated by ResgenSwift.\(Fonts.toolName) \(ResgenSwiftVersion)
-
+
import \(isSwiftUI ? "SwiftUI" : "UIKit")
-
+
extension \(extensionClassname) {\n
"""
}
-
+
private static func getFontMethods(fontsNames: [FontName], staticVar: Bool, isSwiftUI: Bool) -> String {
let pragma = " // MARK: - Getter"
-
+
var propertiesOrMethods: [String] = fontsNames
.unique()
.map {
$0.getProperty(isStatic: staticVar, isSwiftUI: isSwiftUI)
}
-
+
propertiesOrMethods.insert(pragma, at: 0)
return propertiesOrMethods.joined(separator: "\n\n")
}
-
+
private static func getFooter() -> String {
"""
}
-
+
"""
}
}
diff --git a/Sources/ResgenSwift/Fonts/Model/FontName.swift b/Sources/ResgenSwift/Fonts/Model/FontName.swift
index 39043f9..04f317c 100644
--- a/Sources/ResgenSwift/Fonts/Model/FontName.swift
+++ b/Sources/ResgenSwift/Fonts/Model/FontName.swift
@@ -7,13 +7,21 @@
import Foundation
-typealias FontName = String
+// swiftlint:disable no_grouping_extension
+
+struct FontName: Hashable {
+
+ let postscriptName: String
+ let filename: String
+ let fileExtension: String
+}
extension FontName {
+
var fontNameSanitize: String {
- self.removeCharacters(from: "[]+-_")
+ postscriptName.removeCharacters(from: "[]+-_")
}
-
+
func getProperty(isStatic: Bool, isSwiftUI: Bool) -> String {
if isSwiftUI {
if isStatic {
diff --git a/Sources/ResgenSwift/Fonts/Parser/FontFileParser.swift b/Sources/ResgenSwift/Fonts/Parser/FontFileParser.swift
index 61bb210..13b45ff 100644
--- a/Sources/ResgenSwift/Fonts/Parser/FontFileParser.swift
+++ b/Sources/ResgenSwift/Fonts/Parser/FontFileParser.swift
@@ -7,10 +7,13 @@
import Foundation
-class FontFileParser {
+enum FontFileParser {
+
static func parse(_ inputFile: String) -> [String] {
- let inputFileContent = try! String(contentsOfFile: inputFile,
- encoding: .utf8)
+ let inputFileContent = try! String( // swiftlint:disable:this force_try
+ contentsOfFile: inputFile,
+ encoding: .utf8
+ )
return inputFileContent.components(separatedBy: CharacterSet.newlines)
}
}
diff --git a/Sources/ResgenSwift/Generate/Extensions/StringExtensions.swift b/Sources/ResgenSwift/Generate/Extensions/StringExtensions.swift
index faa5acd..361b623 100644
--- a/Sources/ResgenSwift/Generate/Extensions/StringExtensions.swift
+++ b/Sources/ResgenSwift/Generate/Extensions/StringExtensions.swift
@@ -8,7 +8,7 @@
import Foundation
extension String {
-
+
func prependIfRelativePath(_ prependPath: String) -> String {
// If path starts with "/", it's an absolute path
if self.hasPrefix("/") {
diff --git a/Sources/ResgenSwift/Generate/Generate.swift b/Sources/ResgenSwift/Generate/Generate.swift
index a051046..9ff7967 100644
--- a/Sources/ResgenSwift/Generate/Generate.swift
+++ b/Sources/ResgenSwift/Generate/Generate.swift
@@ -5,32 +5,32 @@
// Created by Thibaut Schmitt on 30/08/2022.
//
-import ToolCore
-import Foundation
import ArgumentParser
+import Foundation
+import ToolCore
struct Generate: ParsableCommand {
-
+
// MARK: - CommandConfiguration
-
+
static var configuration = CommandConfiguration(
abstract: "A utility to generate ressources based on a configuration file",
version: ResgenSwiftVersion
)
-
+
// MARK: - Static
-
+
static let toolName = "Generate"
-
+
// MARK: - Command Options
-
+
@OptionGroup var options: GenerateOptions
-
+
// MARK: - Run
-
- public func run() throws {
+
+ func run() throws {
print("[\(Self.toolName)] Starting Resgen with configuration: \(options.configurationFile)")
-
+
// Parse
let configuration = ConfigurationFileParser.parse(options.configurationFile)
print("Found configurations :")
@@ -41,18 +41,22 @@ struct Generate: ParsableCommand {
print(" - \(configuration.strings.count) strings configuration(s)")
print(" - \(configuration.tags.count) tags configuration(s)")
print()
-
+
if let architecture = configuration.architecture {
- ArchitectureGenerator.writeArchitecture(architecture,
- projectDirectory: options.projectDirectory)
+ ArchitectureGenerator.writeArchitecture(
+ architecture,
+ projectDirectory: options.projectDirectory
+ )
}
-
+
// Execute commands
configuration.runnableConfigurations
.forEach {
let begin = Date()
- $0.run(projectDirectory: options.projectDirectory,
- force: options.forceGeneration)
+ $0.run(
+ projectDirectory: options.projectDirectory,
+ force: options.forceGeneration
+ )
print("Took: \(Date().timeIntervalSince(begin))s\n")
}
diff --git a/Sources/ResgenSwift/Generate/GenerateError.swift b/Sources/ResgenSwift/Generate/GenerateError.swift
index 03c3c16..f5791b9 100644
--- a/Sources/ResgenSwift/Generate/GenerateError.swift
+++ b/Sources/ResgenSwift/Generate/GenerateError.swift
@@ -8,26 +8,26 @@
import Foundation
enum GenerateError: Error {
+
case fileNotExists(String)
case invalidConfigurationFile(String, String)
case commandError([String], String)
case writeFile(String, String)
-
+
var description: String {
switch self {
case .fileNotExists(let filename):
return "error: [\(Generate.toolName)] File \(filename) does not exists"
-
- case .invalidConfigurationFile(let filename, let underneathErrorDescription):
+
+ case let .invalidConfigurationFile(filename, underneathErrorDescription):
return "error: [\(Generate.toolName)] File \(filename) is not a valid configuration file. Underneath error: \(underneathErrorDescription)"
-
- case .commandError(let command, let terminationStatus):
+
+ case let .commandError(command, terminationStatus):
let readableCommand = command
- .map { $0 }
.joined(separator: " ")
return "error: [\(Generate.toolName)] An error occured while running command '\(readableCommand)'. Command terminate with status code: \(terminationStatus)"
-
- case .writeFile(let filename, let info):
+
+ case let .writeFile(filename, info):
return "error: [\(Generate.toolName)] An error occured while writing file in \(filename): \(info)"
}
}
diff --git a/Sources/ResgenSwift/Generate/GenerateOptions.swift b/Sources/ResgenSwift/Generate/GenerateOptions.swift
index da3c263..e6f044d 100644
--- a/Sources/ResgenSwift/Generate/GenerateOptions.swift
+++ b/Sources/ResgenSwift/Generate/GenerateOptions.swift
@@ -5,16 +5,17 @@
// Created by Thibaut Schmitt on 30/08/2022.
//
-import Foundation
import ArgumentParser
+import Foundation
struct GenerateOptions: ParsableArguments {
+
@Flag(name: [.customShort("f"), .customShort("F")], help: "Should force generation")
var forceGeneration = false
-
+
@Argument(help: "Configuration file.", transform: { $0.replaceTiltWithHomeDirectoryPath() })
var configurationFile: String
-
+
@Option(help: "Project directory. It will be added to every relative path (path that does not start with `/`",
transform: {
if $0.last == "/" {
diff --git a/Sources/ResgenSwift/Generate/Generator/ArchitectureGenerator.swift b/Sources/ResgenSwift/Generate/Generator/ArchitectureGenerator.swift
index 7670780..5acd5e4 100644
--- a/Sources/ResgenSwift/Generate/Generator/ArchitectureGenerator.swift
+++ b/Sources/ResgenSwift/Generate/Generator/ArchitectureGenerator.swift
@@ -5,10 +5,11 @@
// Created by Thibaut Schmitt on 18/11/2022.
//
-import ToolCore
import Foundation
+import ToolCore
+
+enum ArchitectureGenerator {
-struct ArchitectureGenerator {
static func writeArchitecture(_ architecture: ConfigurationArchitecture, projectDirectory: String) {
// Create extension content
var architectureContent = [
@@ -16,21 +17,21 @@ struct ArchitectureGenerator {
architecture.getClass()
]
.joined(separator: "\n\n")
-
+
architectureContent += "\n"
-
+
let filename = "\(architecture.classname).swift"
guard let filePath = architecture.path?.prependIfRelativePath(projectDirectory) else {
let error = GenerateError.writeFile(filename, "Path of file is not defined.")
print(error.description)
Generate.exit(withError: error)
}
-
+
// Write content
let architectureFilePathURL = URL(fileURLWithPath: "\(filePath)/\(filename)")
do {
try architectureContent.write(to: architectureFilePathURL, atomically: false, encoding: .utf8)
- } catch let error {
+ } catch {
let error = GenerateError.writeFile(filename, error.localizedDescription)
print(error.description)
Generate.exit(withError: error)
diff --git a/Sources/ResgenSwift/Generate/Model/ConfigurationFile.swift b/Sources/ResgenSwift/Generate/Model/ConfigurationFile.swift
index e92f9d6..5a84654 100644
--- a/Sources/ResgenSwift/Generate/Model/ConfigurationFile.swift
+++ b/Sources/ResgenSwift/Generate/Model/ConfigurationFile.swift
@@ -8,6 +8,7 @@
import Foundation
struct ConfigurationFile: Codable, CustomDebugStringConvertible {
+
var architecture: ConfigurationArchitecture?
var analytics: [AnalyticsConfiguration]
var colors: [ColorsConfiguration]
@@ -15,12 +16,12 @@ struct ConfigurationFile: Codable, CustomDebugStringConvertible {
var images: [ImagesConfiguration]
var strings: [StringsConfiguration]
var tags: [TagsConfiguration]
-
+
var runnableConfigurations: [Runnable] {
let runnables: [[Runnable]] = [analytics, colors, fonts, images, strings, tags]
return Array(runnables.joined())
}
-
+
var debugDescription: String {
"""
\(analytics)
@@ -44,20 +45,21 @@ struct ConfigurationFile: Codable, CustomDebugStringConvertible {
}
struct ConfigurationArchitecture: Codable {
+
let property: String
let classname: String
let path: String?
- let children: [ConfigurationArchitecture]?
-
+ let children: [Self]?
+
func getProperty(isStatic: Bool) -> String {
" \(isStatic ? "static " : "")let \(property) = \(classname)()"
}
-
+
func getClass(generateStaticProperty: Bool = true) -> String {
guard children?.isEmpty == false else {
return "final class \(classname): Sendable {}"
}
-
+
let classDefinition = [
"class \(classname) {",
children?.map { $0.getProperty(isStatic: generateStaticProperty) }.joined(separator: "\n"),
@@ -65,12 +67,12 @@ struct ConfigurationArchitecture: Codable {
]
.compactMap { $0 }
.joined(separator: "\n")
-
+
return [classDefinition, "", getSubclass()]
.compactMap { $0 }
.joined(separator: "\n")
}
-
+
func getSubclass() -> String? {
guard let children else { return nil }
return children.compactMap { arch in
@@ -81,26 +83,29 @@ struct ConfigurationArchitecture: Codable {
}
struct AnalyticsConfiguration: Codable, CustomDebugStringConvertible {
+
let inputFile: String
let target: String
let extensionOutputPath: String
let extensionName: String?
let extensionSuffix: String?
private let staticMembers: Bool?
-
+
var staticMembersOptions: Bool {
- if let staticMembers = staticMembers {
+ if let staticMembers {
return staticMembers
}
return false
}
-
- internal init(inputFile: String,
- target: String,
- extensionOutputPath: String,
- extensionName: String?,
- extensionSuffix: String?,
- staticMembers: Bool?) {
+
+ internal init(
+ inputFile: String,
+ target: String,
+ extensionOutputPath: String,
+ extensionName: String?,
+ extensionSuffix: String?,
+ staticMembers: Bool?
+ ) {
self.inputFile = inputFile
self.target = target
self.extensionOutputPath = extensionOutputPath
@@ -108,7 +113,7 @@ struct AnalyticsConfiguration: Codable, CustomDebugStringConvertible {
self.extensionSuffix = extensionSuffix
self.staticMembers = staticMembers
}
-
+
var debugDescription: String {
"""
Analytics configuration:
@@ -122,6 +127,7 @@ struct AnalyticsConfiguration: Codable, CustomDebugStringConvertible {
}
struct ColorsConfiguration: Codable, CustomDebugStringConvertible {
+
let inputFile: String
let style: String
let xcassetsPath: String
@@ -130,22 +136,24 @@ struct ColorsConfiguration: Codable, CustomDebugStringConvertible {
let extensionNameUIKit: String?
let extensionSuffix: String?
private let staticMembers: Bool?
-
+
var staticMembersOptions: Bool {
- if let staticMembers = staticMembers {
+ if let staticMembers {
return staticMembers
}
return false
}
-
- internal init(inputFile: String,
- style: String,
- xcassetsPath: String,
- extensionOutputPath: String,
- extensionName: String?,
- extensionNameUIKit: String?,
- extensionSuffix: String?,
- staticMembers: Bool?) {
+
+ internal init(
+ inputFile: String,
+ style: String,
+ xcassetsPath: String,
+ extensionOutputPath: String,
+ extensionName: String?,
+ extensionNameUIKit: String?,
+ extensionSuffix: String?,
+ staticMembers: Bool?
+ ) {
self.inputFile = inputFile
self.style = style
self.xcassetsPath = xcassetsPath
@@ -155,7 +163,7 @@ struct ColorsConfiguration: Codable, CustomDebugStringConvertible {
self.extensionSuffix = extensionSuffix
self.staticMembers = staticMembers
}
-
+
var debugDescription: String {
"""
Colors configuration:
@@ -171,6 +179,7 @@ struct ColorsConfiguration: Codable, CustomDebugStringConvertible {
}
struct FontsConfiguration: Codable, CustomDebugStringConvertible {
+
let inputFile: String
let extensionOutputPath: String
let extensionName: String?
@@ -178,21 +187,23 @@ struct FontsConfiguration: Codable, CustomDebugStringConvertible {
let extensionSuffix: String?
let infoPlistPaths: String?
private let staticMembers: Bool?
-
+
var staticMembersOptions: Bool {
- if let staticMembers = staticMembers {
+ if let staticMembers {
return staticMembers
}
return false
}
-
- internal init(inputFile: String,
- extensionOutputPath: String,
- extensionName: String?,
- extensionNameUIKit: String?,
- extensionSuffix: String?,
- infoPlistPaths: String?,
- staticMembers: Bool?) {
+
+ internal init(
+ inputFile: String,
+ extensionOutputPath: String,
+ extensionName: String?,
+ extensionNameUIKit: String?,
+ extensionSuffix: String?,
+ infoPlistPaths: String?,
+ staticMembers: Bool?
+ ) {
self.inputFile = inputFile
self.extensionOutputPath = extensionOutputPath
self.extensionName = extensionName
@@ -201,7 +212,7 @@ struct FontsConfiguration: Codable, CustomDebugStringConvertible {
self.infoPlistPaths = infoPlistPaths
self.staticMembers = staticMembers
}
-
+
var debugDescription: String {
"""
Fonts configuration:
@@ -216,6 +227,7 @@ struct FontsConfiguration: Codable, CustomDebugStringConvertible {
}
struct ImagesConfiguration: Codable, CustomDebugStringConvertible {
+
let inputFile: String
let xcassetsPath: String
let extensionOutputPath: String
@@ -223,21 +235,23 @@ struct ImagesConfiguration: Codable, CustomDebugStringConvertible {
let extensionNameUIKit: String?
let extensionSuffix: String?
private let staticMembers: Bool?
-
+
var staticMembersOptions: Bool {
- if let staticMembers = staticMembers {
+ if let staticMembers {
return staticMembers
}
return false
}
-
- internal init(inputFile: String,
- xcassetsPath: String,
- extensionOutputPath: String,
- extensionName: String?,
- extensionNameUIKit: String?,
- extensionSuffix: String?,
- staticMembers: Bool?) {
+
+ internal init(
+ inputFile: String,
+ xcassetsPath: String,
+ extensionOutputPath: String,
+ extensionName: String?,
+ extensionNameUIKit: String?,
+ extensionSuffix: String?,
+ staticMembers: Bool?
+ ) {
self.inputFile = inputFile
self.xcassetsPath = xcassetsPath
self.extensionOutputPath = extensionOutputPath
@@ -246,7 +260,7 @@ struct ImagesConfiguration: Codable, CustomDebugStringConvertible {
self.extensionSuffix = extensionSuffix
self.staticMembers = staticMembers
}
-
+
var debugDescription: String {
"""
Images configuration:
@@ -261,6 +275,7 @@ struct ImagesConfiguration: Codable, CustomDebugStringConvertible {
}
struct StringsConfiguration: Codable, CustomDebugStringConvertible {
+
let inputFile: String
let outputPath: String
let langs: String
@@ -272,28 +287,30 @@ struct StringsConfiguration: Codable, CustomDebugStringConvertible {
private let xcStrings: Bool?
var staticMembersOptions: Bool {
- if let staticMembers = staticMembers {
+ if let staticMembers {
return staticMembers
}
return false
}
var xcStringsOptions: Bool {
- if let xcStrings = xcStrings {
+ if let xcStrings {
return xcStrings
}
return false
}
- internal init(inputFile: String,
- outputPath: String,
- langs: String,
- defaultLang: String,
- extensionOutputPath: String,
- extensionName: String?,
- extensionSuffix: String?,
- staticMembers: Bool?,
- xcStrings: Bool?) {
+ internal init(
+ inputFile: String,
+ outputPath: String,
+ langs: String,
+ defaultLang: String,
+ extensionOutputPath: String,
+ extensionName: String?,
+ extensionSuffix: String?,
+ staticMembers: Bool?,
+ xcStrings: Bool?
+ ) {
self.inputFile = inputFile
self.outputPath = outputPath
self.langs = langs
@@ -304,7 +321,7 @@ struct StringsConfiguration: Codable, CustomDebugStringConvertible {
self.staticMembers = staticMembers
self.xcStrings = xcStrings
}
-
+
var debugDescription: String {
"""
Strings configuration:
@@ -320,26 +337,29 @@ struct StringsConfiguration: Codable, CustomDebugStringConvertible {
}
struct TagsConfiguration: Codable, CustomDebugStringConvertible {
+
let inputFile: String
let lang: String
let extensionOutputPath: String
let extensionName: String?
let extensionSuffix: String?
private let staticMembers: Bool?
-
+
var staticMembersOptions: Bool {
- if let staticMembers = staticMembers {
+ if let staticMembers {
return staticMembers
}
return false
}
-
- internal init(inputFile: String,
- lang: String,
- extensionOutputPath: String,
- extensionName: String?,
- extensionSuffix: String?,
- staticMembers: Bool?) {
+
+ internal init(
+ inputFile: String,
+ lang: String,
+ extensionOutputPath: String,
+ extensionName: String?,
+ extensionSuffix: String?,
+ staticMembers: Bool?
+ ) {
self.inputFile = inputFile
self.lang = lang
self.extensionOutputPath = extensionOutputPath
@@ -347,7 +367,7 @@ struct TagsConfiguration: Codable, CustomDebugStringConvertible {
self.extensionSuffix = extensionSuffix
self.staticMembers = staticMembers
}
-
+
var debugDescription: String {
"""
Tags configuration:
diff --git a/Sources/ResgenSwift/Generate/Parser/ConfigurationFileParser.swift b/Sources/ResgenSwift/Generate/Parser/ConfigurationFileParser.swift
index 4b61d31..c879a97 100644
--- a/Sources/ResgenSwift/Generate/Parser/ConfigurationFileParser.swift
+++ b/Sources/ResgenSwift/Generate/Parser/ConfigurationFileParser.swift
@@ -8,14 +8,15 @@
import Foundation
import Yams
-class ConfigurationFileParser {
+enum ConfigurationFileParser {
+
static func parse(_ configurationFile: String) -> ConfigurationFile {
guard let data = FileManager().contents(atPath: configurationFile) else {
let error = GenerateError.fileNotExists(configurationFile)
print(error.description)
Generate.exit(withError: error)
}
-
+
do {
return try YAMLDecoder().decode(ConfigurationFile.self, from: data)
} catch {
diff --git a/Sources/ResgenSwift/Generate/Runnable/AnalyticsConfiguration+Runnable.swift b/Sources/ResgenSwift/Generate/Runnable/AnalyticsConfiguration+Runnable.swift
index 1689a24..ff492dc 100644
--- a/Sources/ResgenSwift/Generate/Runnable/AnalyticsConfiguration+Runnable.swift
+++ b/Sources/ResgenSwift/Generate/Runnable/AnalyticsConfiguration+Runnable.swift
@@ -8,13 +8,14 @@
import Foundation
extension AnalyticsConfiguration: Runnable {
+
func run(projectDirectory: String, force: Bool) {
var args = [String]()
-
+
if force {
args += ["-f"]
}
-
+
args += [
inputFile.prependIfRelativePath(projectDirectory),
"--target",
@@ -24,20 +25,20 @@ extension AnalyticsConfiguration: Runnable {
"--static-members",
"\(staticMembersOptions)"
]
-
- if let extensionName = extensionName {
+
+ if let extensionName {
args += [
"--extension-name",
extensionName
]
}
- if let extensionSuffix = extensionSuffix {
+ if let extensionSuffix {
args += [
"--extension-suffix",
extensionSuffix
]
}
-
+
Analytics.main(args)
}
}
diff --git a/Sources/ResgenSwift/Generate/Runnable/ColorsConfiguration+Runnable.swift b/Sources/ResgenSwift/Generate/Runnable/ColorsConfiguration+Runnable.swift
index 5c8f735..6f8bc11 100644
--- a/Sources/ResgenSwift/Generate/Runnable/ColorsConfiguration+Runnable.swift
+++ b/Sources/ResgenSwift/Generate/Runnable/ColorsConfiguration+Runnable.swift
@@ -8,18 +8,19 @@
import Foundation
extension ColorsConfiguration: Runnable {
+
func run(projectDirectory: String, force: Bool) {
let args = getArguments(projectDirectory: projectDirectory, force: force)
Colors.main(args)
}
-
+
func getArguments(projectDirectory: String, force: Bool) -> [String] {
var args = [String]()
-
+
if force {
args += ["-f"]
}
-
+
args += [
inputFile.prependIfRelativePath(projectDirectory),
"--style",
@@ -31,26 +32,26 @@ extension ColorsConfiguration: Runnable {
"--static-members",
"\(staticMembersOptions)"
]
-
- if let extensionName = extensionName {
+
+ if let extensionName {
args += [
"--extension-name",
extensionName
]
}
- if let extensionNameUIKit = extensionNameUIKit {
+ if let extensionNameUIKit {
args += [
"--extension-name-ui-kit",
extensionNameUIKit
]
}
- if let extensionSuffix = extensionSuffix {
+ if let extensionSuffix {
args += [
"--extension-suffix",
extensionSuffix
]
}
-
+
return args
}
}
diff --git a/Sources/ResgenSwift/Generate/Runnable/FontsConfiguration+Runnable.swift b/Sources/ResgenSwift/Generate/Runnable/FontsConfiguration+Runnable.swift
index a314c8e..6a9db9f 100644
--- a/Sources/ResgenSwift/Generate/Runnable/FontsConfiguration+Runnable.swift
+++ b/Sources/ResgenSwift/Generate/Runnable/FontsConfiguration+Runnable.swift
@@ -8,18 +8,19 @@
import Foundation
extension FontsConfiguration: Runnable {
+
func run(projectDirectory: String, force: Bool) {
let args = getArguments(projectDirectory: projectDirectory, force: force)
Fonts.main(args)
}
-
+
func getArguments(projectDirectory: String, force: Bool) -> [String] {
var args = [String]()
-
+
if force {
args += ["-f"]
}
-
+
args += [
inputFile.prependIfRelativePath(projectDirectory),
"--extension-output-path",
@@ -27,39 +28,39 @@ extension FontsConfiguration: Runnable {
"--static-members",
"\(staticMembersOptions)"
]
-
- if let extensionName = extensionName {
+
+ if let extensionName {
args += [
"--extension-name",
extensionName
]
}
- if let extensionNameUIKit = extensionNameUIKit {
+ if let extensionNameUIKit {
args += [
"--extension-name-ui-kit",
extensionNameUIKit
]
}
-
- if let extensionSuffix = extensionSuffix {
+
+ if let extensionSuffix {
args += [
"--extension-suffix",
extensionSuffix
]
}
-
- if let infoPlistPaths = infoPlistPaths {
+
+ if let infoPlistPaths {
let adjustedPlistPaths = infoPlistPaths
.split(separator: " ")
.map { String($0).prependIfRelativePath(projectDirectory) }
.joined(separator: " ")
-
+
args += [
"--info-plist-paths",
adjustedPlistPaths
]
}
-
+
return args
}
}
diff --git a/Sources/ResgenSwift/Generate/Runnable/ImagesConfiguration+Runnable.swift b/Sources/ResgenSwift/Generate/Runnable/ImagesConfiguration+Runnable.swift
index 42b5ef8..91d9a43 100644
--- a/Sources/ResgenSwift/Generate/Runnable/ImagesConfiguration+Runnable.swift
+++ b/Sources/ResgenSwift/Generate/Runnable/ImagesConfiguration+Runnable.swift
@@ -1,6 +1,6 @@
//
// ImagesConfiguration+Runnable.swift
-//
+//
//
// Created by Thibaut Schmitt on 30/08/2022.
//
@@ -8,18 +8,19 @@
import Foundation
extension ImagesConfiguration: Runnable {
+
func run(projectDirectory: String, force: Bool) {
let args = getArguments(projectDirectory: projectDirectory, force: force)
Images.main(args)
}
-
+
func getArguments(projectDirectory: String, force: Bool) -> [String] {
var args = [String]()
-
+
if force {
args += ["-f"] // Images has a -f and -F options
}
-
+
args += [
inputFile.prependIfRelativePath(projectDirectory),
"--xcassets-path",
@@ -29,26 +30,28 @@ extension ImagesConfiguration: Runnable {
"--static-members",
"\(staticMembersOptions)"
]
-
- if let extensionName = extensionName {
+
+ if let extensionName {
args += [
"--extension-name",
extensionName
]
}
- if let extensionNameUIKit = extensionNameUIKit {
+
+ if let extensionNameUIKit {
args += [
"--extension-name-ui-kit",
extensionNameUIKit
]
}
- if let extensionSuffix = extensionSuffix {
+
+ if let extensionSuffix {
args += [
"--extension-suffix",
extensionSuffix
]
}
-
+
return args
}
}
diff --git a/Sources/ResgenSwift/Generate/Runnable/Runnable.swift b/Sources/ResgenSwift/Generate/Runnable/Runnable.swift
index 532b52f..160b137 100644
--- a/Sources/ResgenSwift/Generate/Runnable/Runnable.swift
+++ b/Sources/ResgenSwift/Generate/Runnable/Runnable.swift
@@ -8,5 +8,6 @@
import Foundation
protocol Runnable {
+
func run(projectDirectory: String, force: Bool)
}
diff --git a/Sources/ResgenSwift/Generate/Runnable/StringsConfiguration+Runnable.swift b/Sources/ResgenSwift/Generate/Runnable/StringsConfiguration+Runnable.swift
index 7da6a37..5e094e4 100644
--- a/Sources/ResgenSwift/Generate/Runnable/StringsConfiguration+Runnable.swift
+++ b/Sources/ResgenSwift/Generate/Runnable/StringsConfiguration+Runnable.swift
@@ -8,9 +8,10 @@
import Foundation
extension StringsConfiguration: Runnable {
+
func run(projectDirectory: String, force: Bool) {
var args = [String]()
-
+
if force {
args += ["-f"]
}
@@ -30,21 +31,21 @@ extension StringsConfiguration: Runnable {
"--xc-strings",
"\(xcStringsOptions)"
]
-
- if let extensionName = extensionName {
+
+ if let extensionName {
args += [
"--extension-name",
extensionName
]
}
-
- if let extensionSuffix = extensionSuffix {
+
+ if let extensionSuffix {
args += [
"--extension-suffix",
extensionSuffix
]
}
-
+
Stringium.main(args)
}
}
diff --git a/Sources/ResgenSwift/Generate/Runnable/TagsConfiguration+Runnable.swift b/Sources/ResgenSwift/Generate/Runnable/TagsConfiguration+Runnable.swift
index 04c6648..2309ee8 100644
--- a/Sources/ResgenSwift/Generate/Runnable/TagsConfiguration+Runnable.swift
+++ b/Sources/ResgenSwift/Generate/Runnable/TagsConfiguration+Runnable.swift
@@ -8,13 +8,14 @@
import Foundation
extension TagsConfiguration: Runnable {
+
func run(projectDirectory: String, force: Bool) {
var args = [String]()
-
+
if force {
args += ["-f"]
}
-
+
args += [
inputFile.prependIfRelativePath(projectDirectory),
"--lang",
@@ -24,20 +25,20 @@ extension TagsConfiguration: Runnable {
"--static-members",
"\(staticMembersOptions)"
]
-
- if let extensionName = extensionName {
+
+ if let extensionName {
args += [
"--extension-name",
extensionName
]
}
- if let extensionSuffix = extensionSuffix {
+ if let extensionSuffix {
args += [
"--extension-suffix",
extensionSuffix
]
}
-
+
Tags.main(args)
}
}
diff --git a/Sources/ResgenSwift/Images/Extensions/FileManagerExtensions.swift b/Sources/ResgenSwift/Images/Extensions/FileManagerExtensions.swift
index bc4ccff..5b5bc5f 100644
--- a/Sources/ResgenSwift/Images/Extensions/FileManagerExtensions.swift
+++ b/Sources/ResgenSwift/Images/Extensions/FileManagerExtensions.swift
@@ -7,7 +7,10 @@
import Foundation
+// swiftlint:disable force_unwrapping
+
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 {
@@ -15,7 +18,7 @@ extension FileManager {
print(error.description)
Images.exit(withError: error)
}
-
+
for case let fileURL as URL in enumerator {
do {
let fileAttributes = try fileURL.resourceValues(forKeys: [.isRegularFileKey])
@@ -30,7 +33,7 @@ extension FileManager {
}
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 {
@@ -38,7 +41,7 @@ extension FileManager {
print(error.description)
Images.exit(withError: error)
}
-
+
for case let fileURL as URL in enumerator {
do {
let fileAttributes = try fileURL.resourceValues(forKeys: [.isDirectoryKey])
diff --git a/Sources/ResgenSwift/Images/Generator/ImageExtensionGenerator.swift b/Sources/ResgenSwift/Images/Generator/ImageExtensionGenerator.swift
index 47e760b..0676873 100644
--- a/Sources/ResgenSwift/Images/Generator/ImageExtensionGenerator.swift
+++ b/Sources/ResgenSwift/Images/Generator/ImageExtensionGenerator.swift
@@ -5,42 +5,48 @@
// Created by Thibaut Schmitt on 14/02/2022.
//
-import ToolCore
import Foundation
+import ToolCore
+
+enum ImageExtensionGenerator {
-class ImageExtensionGenerator {
-
// MARK: - UIKit
-
- static func generateExtensionFile(images: [ParsedImage],
- staticVar: Bool,
- inputFilename: String,
- extensionName: String,
- extensionFilePath: String,
- isSwiftUI: Bool) {
+
+ static func generateExtensionFile(
+ images: [ParsedImage],
+ staticVar: Bool,
+ inputFilename: String,
+ extensionName: String,
+ extensionFilePath: String,
+ isSwiftUI: Bool
+ ) {
// Create extension conten1t
- let extensionContent = Self.getExtensionContent(images: images,
- staticVar: staticVar,
- extensionName: extensionName,
- inputFilename: inputFilename,
- isSwiftUI: isSwiftUI)
-
+ let extensionContent = Self.getExtensionContent(
+ images: images,
+ staticVar: staticVar,
+ extensionName: extensionName,
+ inputFilename: inputFilename,
+ isSwiftUI: isSwiftUI
+ )
+
// Write content
let extensionFilePathURL = URL(fileURLWithPath: extensionFilePath)
do {
try extensionContent.write(to: extensionFilePathURL, atomically: false, encoding: .utf8)
- } catch let error {
+ } catch {
let error = ImagesError.writeFile(extensionFilePath, error.localizedDescription)
print(error.description)
Images.exit(withError: error)
}
}
-
- static func getExtensionContent(images: [ParsedImage],
- staticVar: Bool,
- extensionName: String,
- inputFilename: String,
- isSwiftUI: Bool) -> String {
+
+ static func getExtensionContent(
+ images: [ParsedImage],
+ staticVar: Bool,
+ extensionName: String,
+ inputFilename: String,
+ isSwiftUI: Bool
+ ) -> String {
[
Self.getHeader(inputFilename: inputFilename, extensionClassname: extensionName, isSwiftUI: isSwiftUI),
Self.getProperties(images: images, staticVar: staticVar, isSwiftUI: isSwiftUI),
@@ -48,30 +54,36 @@ class ImageExtensionGenerator {
]
.joined(separator: "\n")
}
-
- private static func getHeader(inputFilename: String,
- extensionClassname: String,
- isSwiftUI: Bool) -> String {
+
+ private static func getHeader(
+ inputFilename: String,
+ extensionClassname: String,
+ isSwiftUI: Bool
+ ) -> String {
"""
// Generated by ResgenSwift.\(Images.toolName) \(ResgenSwiftVersion)
// Images from \(inputFilename)
-
+
import \(isSwiftUI ? "SwiftUI" : "UIKit")
-
+
extension \(extensionClassname) {
"""
}
-
- private static func getProperties(images: [ParsedImage], staticVar: Bool, isSwiftUI: Bool) -> String {
+
+ private static func getProperties(
+ images: [ParsedImage],
+ staticVar: Bool,
+ isSwiftUI: Bool
+ ) -> String {
images
.map { "\n\($0.getImageProperty(isStatic: staticVar, isSwiftUI: isSwiftUI))" }
.joined(separator: "\n")
}
-
+
private static func getFooter() -> String {
"""
}
-
+
"""
}
}
diff --git a/Sources/ResgenSwift/Images/Generator/XcassetsGenerator.swift b/Sources/ResgenSwift/Images/Generator/XcassetsGenerator.swift
index 82c512c..c15663c 100644
--- a/Sources/ResgenSwift/Images/Generator/XcassetsGenerator.swift
+++ b/Sources/ResgenSwift/Images/Generator/XcassetsGenerator.swift
@@ -1,6 +1,6 @@
//
// XcassetsGenerator.swift
-//
+//
//
// Created by Thibaut Schmitt on 24/01/2022.
//
@@ -9,31 +9,34 @@ import Foundation
import ToolCore
enum OutputImageExtension: String {
+
case png
case svg
}
class XcassetsGenerator {
+ // MARK: - Properties
+
let forceGeneration: Bool
-
+
// MARK: - Init
-
+
init(forceGeneration: Bool) {
self.forceGeneration = forceGeneration
}
-
+
// MARK: - Assets generation
-
+
func generateXcassets(inputPath: String, imagesToGenerate: [ParsedImage], xcassetsPath: String) {
let fileManager = FileManager()
let svgConverter = Images.getSvgConverterPath()
let allSubFiles = fileManager.getAllRegularFileIn(directory: inputPath)
-
+
var generatedAssetsPaths = [String]()
-
+
// Generate new assets
- imagesToGenerate.forEach { parsedImage in
+ imagesToGenerate.forEach { parsedImage in // swiftlint:disable:this closure_body_length
// Get image path
let imageData: (path: String, ext: String) = {
for subfile in allSubFiles {
@@ -54,14 +57,14 @@ class XcassetsGenerator {
print(error.description)
Images.exit(withError: error)
}()
-
+
// Create imageset folder name
let imagesetName = "\(parsedImage.name).imageset"
let imagesetPath = "\(xcassetsPath)/\(imagesetName)"
-
+
// Store managed images path
generatedAssetsPaths.append(imagesetName)
-
+
// Generate output images path
let output1x = "\(imagesetPath)/\(parsedImage.name).\(OutputImageExtension.png.rawValue)"
let output2x = "\(imagesetPath)/\(parsedImage.name)@2x.\(OutputImageExtension.png.rawValue)"
@@ -79,12 +82,14 @@ class XcassetsGenerator {
print("\(parsedImage.name) -> Not regenerating")
return
}
-
+
// Create imageset folder
if fileManager.fileExists(atPath: imagesetPath) == false {
do {
- try fileManager.createDirectory(atPath: imagesetPath,
- withIntermediateDirectories: true)
+ try fileManager.createDirectory(
+ atPath: imagesetPath,
+ withIntermediateDirectories: true
+ )
} catch {
let error = ImagesError.createAssetFolder(imagesetPath)
print(error.description)
@@ -124,9 +129,7 @@ class XcassetsGenerator {
Shell.shell(command1x)
Shell.shell(command2x)
Shell.shell(command3x)
-
} else {
-
let output = "\(imagesetPath)/\(parsedImage.name).\(OutputImageExtension.svg.rawValue)"
let tempURL = URL(fileURLWithPath: output)
@@ -143,47 +146,69 @@ class XcassetsGenerator {
// convert path/to/image.png -resize 200x300 path/to/output.png
// convert path/to/image.png -resize 200x path/to/output.png
// convert path/to/image.png -resize x300 path/to/output.png
- Shell.shell(["convert", "\(imageData.path)",
- "-resize", "\(convertArguments.x1.width ?? "")x\(convertArguments.x1.height ?? "")",
- output1x])
- Shell.shell(["convert", "\(imageData.path)",
- "-resize", "\(convertArguments.x2.width ?? "")x\(convertArguments.x2.height ?? "")",
- output2x])
- Shell.shell(["convert", "\(imageData.path)",
- "-resize", "\(convertArguments.x3.width ?? "")x\(convertArguments.x3.height ?? "")",
- output3x])
+ Shell.shell(
+ [
+ "convert",
+ "\(imageData.path)",
+ "-resize",
+ "\(convertArguments.x1.width ?? "")x\(convertArguments.x1.height ?? "")",
+ output1x
+ ]
+ )
+ Shell.shell(
+ [
+ "convert",
+ "\(imageData.path)",
+ "-resize",
+ "\(convertArguments.x2.width ?? "")x\(convertArguments.x2.height ?? "")",
+ output2x
+ ]
+ )
+ Shell.shell(
+ [
+ "convert",
+ "\(imageData.path)",
+ "-resize",
+ "\(convertArguments.x3.width ?? "")x\(convertArguments.x3.height ?? "")",
+ output3x
+ ]
+ )
}
-
+
// Write Content.json
guard let imagesetContentJson = parsedImage.generateContentJson(isVector: imageData.ext == "svg") else { return }
let contentJsonFilePath = "\(imagesetPath)/Contents.json"
-
+
let contentJsonFilePathURL = URL(fileURLWithPath: contentJsonFilePath)
- try! imagesetContentJson.write(to: contentJsonFilePathURL, atomically: false, encoding: .utf8)
-
+ try! imagesetContentJson.write( // swiftlint:disable:this force_try
+ to: contentJsonFilePathURL,
+ atomically: false,
+ encoding: .utf8
+ )
+
print("\(parsedImage.name) -> Generated")
}
-
+
// Success info
let generatedAssetsCount = generatedAssetsPaths.count
print("Images generated: \(generatedAssetsCount)")
-
+
// Delete old assets
- let allImagesetName = Set(fileManager.getAllImageSetFolderIn(directory: xcassetsPath))
+ 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)")
+ try! fileManager.removeItem(atPath: "\(xcassetsPath)/\(itemToRemove)") // swiftlint:disable:this force_try
}
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")
@@ -194,14 +219,14 @@ class XcassetsGenerator {
command.append("\(height)")
}
}
-
+
// MARK: - Helpers: bypass generation
-
+
private func shouldGenerate(inputImagePath: String, xcassetImagePath: String, needToGenerateForSvg: Bool) -> Bool {
if forceGeneration || needToGenerateForSvg {
return true
}
-
+
return GeneratorChecker.isFile(inputImagePath, moreRecenThan: xcassetImagePath)
}
}
diff --git a/Sources/ResgenSwift/Images/Images.swift b/Sources/ResgenSwift/Images/Images.swift
index 4a028c4..792bbb5 100644
--- a/Sources/ResgenSwift/Images/Images.swift
+++ b/Sources/ResgenSwift/Images/Images.swift
@@ -1,123 +1,131 @@
//
// Images.swift
-//
+//
//
// Created by Thibaut Schmitt on 24/01/2022.
//
-import ToolCore
-import Foundation
import ArgumentParser
+import Foundation
+import ToolCore
struct Images: ParsableCommand {
-
+
// MARK: - CommandConfiguration
-
+
static var configuration = CommandConfiguration(
abstract: "A utility for generate images and an extension to access them easily.",
version: ResgenSwiftVersion
)
-
+
// MARK: - Static
-
+
static let toolName = "Images"
static let defaultExtensionName = "Image"
static let defaultExtensionNameUIKit = "UIImage"
-
+
// MARK: - Command Options
-
+
@OptionGroup var options: ImagesOptions
-
+
// MARK: - Run
-
+
mutating func run() {
print("[\(Self.toolName)] Starting images generation")
print("[\(Self.toolName)] Will use inputFile \(options.inputFile) to generate images in xcassets \(options.xcassetsPath)")
-
+
// Check requirements
guard checkRequirements() else { return }
-
+
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)
-
+ xcassetsGenerator.generateXcassets(
+ inputPath: inputFolder,
+ imagesToGenerate: imagesToGenerate,
+ xcassetsPath: options.xcassetsPath
+ )
+
// Generate extension
- ImageExtensionGenerator.generateExtensionFile(images: imagesToGenerate,
- staticVar: options.staticMembers,
- inputFilename: options.inputFilenameWithoutExt,
- extensionName: options.extensionName,
- extensionFilePath: options.extensionFilePath,
- isSwiftUI: true)
-
- ImageExtensionGenerator.generateExtensionFile(images: imagesToGenerate,
- staticVar: options.staticMembers,
- inputFilename: options.inputFilenameWithoutExt,
- extensionName: options.extensionNameUIKit,
- extensionFilePath: options.extensionFilePathUIKit,
- isSwiftUI: false)
-
+ ImageExtensionGenerator.generateExtensionFile(
+ images: imagesToGenerate,
+ staticVar: options.staticMembers,
+ inputFilename: options.inputFilenameWithoutExt,
+ extensionName: options.extensionName,
+ extensionFilePath: options.extensionFilePath,
+ isSwiftUI: true
+ )
+
+ ImageExtensionGenerator.generateExtensionFile(
+ images: imagesToGenerate,
+ staticVar: options.staticMembers,
+ inputFilename: options.inputFilenameWithoutExt,
+ extensionName: options.extensionNameUIKit,
+ extensionFilePath: options.extensionFilePathUIKit,
+ isSwiftUI: false
+ )
+
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 = ImagesError.fileNotExists(options.inputFile)
print(error.description)
- Images.exit(withError: error)
+ Self.exit(withError: error)
}
// RSVG-Converter
- _ = Images.getSvgConverterPath()
+ _ = Self.getSvgConverterPath()
// Extension for UIKit and SwiftUI should have different name
guard options.extensionName != options.extensionNameUIKit else {
let error = ImagesError.extensionNamesCollision(options.extensionName)
print(error.description)
- Images.exit(withError: error)
+ Self.exit(withError: error)
}
-
+
// Check if needed to regenerate
- guard GeneratorChecker.shouldGenerate(force: options.forceExecution,
- inputFilePath: options.inputFile,
- extensionFilePath: options.extensionFilePath) else {
+ guard GeneratorChecker.shouldGenerate(
+ force: options.forceExecution,
+ inputFilePath: options.inputFile,
+ extensionFilePath: options.extensionFilePath
+ ) else {
print("[\(Self.toolName)] Images are already up to date :) ")
return false
}
-
+
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)
+ return taskSvgConverter.output!.removeCharacters(from: CharacterSet.whitespacesAndNewlines) // swiftlint:disable:this force_unwrapping
}
-
+
let error = ImagesError.rsvgConvertNotFound
print(error.description)
- Images.exit(withError: error)
+ Self.exit(withError: error)
}
}
diff --git a/Sources/ResgenSwift/Images/ImagesError.swift b/Sources/ResgenSwift/Images/ImagesError.swift
index 36c98d9..d4942e7 100644
--- a/Sources/ResgenSwift/Images/ImagesError.swift
+++ b/Sources/ResgenSwift/Images/ImagesError.swift
@@ -1,6 +1,6 @@
//
// ImagesError.swift
-//
+//
//
// Created by Thibaut Schmitt on 24/01/2022.
//
@@ -8,6 +8,7 @@
import Foundation
enum ImagesError: Error {
+
case extensionNamesCollision(String)
case inputFolderNotFound(String)
case fileNotExists(String)
@@ -17,33 +18,33 @@ enum ImagesError: Error {
case writeFile(String, String)
case createAssetFolder(String)
case unknown(String)
-
+
var description: String {
switch self {
case .extensionNamesCollision(let extensionName):
return "error: [\(Fonts.toolName)] Error on extension names, extension name and SwiftUI extension name should be different (\(extensionName) is used on both)"
-
+
case .inputFolderNotFound(let inputFolder):
return "error: [\(Images.toolName)] Input folder not found: \(inputFolder)"
-
+
case .fileNotExists(let filename):
return "error: [\(Images.toolName)] File \(filename) does not exists"
-
+
case .unknownImageExtension(let filename):
return "error: [\(Images.toolName)] File \(filename) have an unhandled file extension. Cannot generate image."
-
- case .getFileAttributed(let filename, let errorDescription):
+
+ case let .getFileAttributed(filename, errorDescription):
return "error: [\(Images.toolName)] Getting file attributes of \(filename) failed with error: \(errorDescription)"
-
+
case .rsvgConvertNotFound:
return "error: [\(Images.toolName)] Can't find rsvg-convert (can be installed with 'brew remove imagemagick && brew install librsvg')"
-
- case .writeFile(let subErrorDescription, let filename):
+
+ case let .writeFile(subErrorDescription, filename):
return "error: [\(Images.toolName)] An error occured while writing content to \(filename): \(subErrorDescription)"
case .createAssetFolder(let folder):
return "error: [\(Colors.toolName)] An error occured while creating folder `\(folder)`"
-
+
case .unknown(let errorDescription):
return "error: [\(Images.toolName)] Unknown error: \(errorDescription)"
}
diff --git a/Sources/ResgenSwift/Images/ImagesOptions.swift b/Sources/ResgenSwift/Images/ImagesOptions.swift
index 2b49cfb..03554ed 100644
--- a/Sources/ResgenSwift/Images/ImagesOptions.swift
+++ b/Sources/ResgenSwift/Images/ImagesOptions.swift
@@ -1,38 +1,41 @@
//
// ImagiumOptions.swift
-//
+//
//
// Created by Thibaut Schmitt on 24/01/2022.
//
-import Foundation
import ArgumentParser
+import Foundation
+
+// swiftlint:disable no_grouping_extension
struct ImagesOptions: 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: "Tell if it will generate static properties or not")
var staticMembers: Bool = false
-
+
@Option(help: "Extension name. If not specified, it will generate an Image extension.")
var extensionName: String = Images.defaultExtensionName
-
+
@Option(help: "Extension name. If not specified, it will generate an UIImage extension.")
var extensionNameUIKit: String = Images.defaultExtensionNameUIKit
-
+
@Option(help: "Extension suffix. Ex: MyApp, it will generate {extensionName}+Image{extensionSuffix}.swift")
var extensionSuffix: String?
}
@@ -40,35 +43,35 @@ struct ImagesOptions: ParsableArguments {
// MARK: - Computed var
extension ImagesOptions {
-
+
// MARK: - SwiftUI
-
+
var extensionFileName: String {
- if let extensionSuffix = extensionSuffix {
+ if let extensionSuffix {
return "\(extensionName)+\(extensionSuffix).swift"
}
return "\(extensionName).swift"
}
-
+
var extensionFilePath: String {
"\(extensionOutputPath)/\(extensionFileName)"
}
-
+
// MARK: - UIKit
-
+
var extensionFileNameUIKit: String {
- if let extensionSuffix = extensionSuffix {
+ if let extensionSuffix {
return "\(extensionNameUIKit)+\(extensionSuffix).swift"
}
return "\(extensionNameUIKit).swift"
}
-
+
var extensionFilePathUIKit: String {
"\(extensionOutputPath)/\(extensionFileNameUIKit)"
}
-
+
// MARK: -
-
+
var inputFilenameWithoutExt: String {
URL(fileURLWithPath: inputFile)
.deletingPathExtension()
diff --git a/Sources/ResgenSwift/Images/Model/ConvertArgument.swift b/Sources/ResgenSwift/Images/Model/ConvertArgument.swift
index c66566b..1dd03b5 100644
--- a/Sources/ResgenSwift/Images/Model/ConvertArgument.swift
+++ b/Sources/ResgenSwift/Images/Model/ConvertArgument.swift
@@ -8,6 +8,7 @@
import Foundation
struct ConvertArgument {
+
let width: String?
let height: String?
}
diff --git a/Sources/ResgenSwift/Images/Model/ImageContent.swift b/Sources/ResgenSwift/Images/Model/ImageContent.swift
index fc6f37f..9982cbc 100644
--- a/Sources/ResgenSwift/Images/Model/ImageContent.swift
+++ b/Sources/ResgenSwift/Images/Model/ImageContent.swift
@@ -8,11 +8,13 @@
import Foundation
enum TemplateRenderingIntent: String, Codable {
+
case template
case original
}
struct AssetContent: Codable, Equatable {
+
let images: [AssetImageDescription]
let info: AssetInfo
let properties: AssetProperties?
@@ -27,16 +29,17 @@ struct AssetContent: Codable, Equatable {
self.properties = properties
}
- static func == (lhs: AssetContent, rhs: AssetContent) -> Bool {
+ static func == (lhs: Self, rhs: Self) -> Bool {
guard lhs.images.count == rhs.images.count else { return false }
- let lhsImages = lhs.images.sorted(by: { $0.filename < $1.filename })
- let rhsImages = rhs.images.sorted(by: { $0.filename < $1.filename })
+ let lhsImages = lhs.images.sorted { $0.filename < $1.filename }
+ let rhsImages = rhs.images.sorted { $0.filename < $1.filename }
return lhsImages == rhsImages
}
}
struct AssetImageDescription: Codable, Equatable {
+
let idiom: String
let scale: String?
let filename: String
@@ -53,11 +56,13 @@ struct AssetImageDescription: Codable, Equatable {
}
struct AssetInfo: Codable, Equatable {
+
let version: Int
let author: String
}
struct AssetProperties: Codable, Equatable {
+
let preservesVectorRepresentation: Bool
let templateRenderingIntent: TemplateRenderingIntent?
@@ -70,6 +75,7 @@ struct AssetProperties: Codable, Equatable {
}
enum CodingKeys: String, CodingKey {
+
case preservesVectorRepresentation = "preserves-vector-representation"
case templateRenderingIntent = "template-rendering-intent"
}
diff --git a/Sources/ResgenSwift/Images/Model/ParsedImage.swift b/Sources/ResgenSwift/Images/Model/ParsedImage.swift
index 7c2b3f0..49668d9 100644
--- a/Sources/ResgenSwift/Images/Model/ParsedImage.swift
+++ b/Sources/ResgenSwift/Images/Model/ParsedImage.swift
@@ -1,6 +1,6 @@
//
// ParsedImage.swift
-//
+//
//
// Created by Thibaut Schmitt on 24/01/2022.
//
@@ -8,10 +8,14 @@
import Foundation
enum ImageExtension: String {
+
case png
}
struct ParsedImage {
+
+ // MARK: - Properties
+
let name: String
let tags: String
let width: Int
@@ -33,34 +37,34 @@ struct ParsedImage {
}
// MARK: - Convert
-
- var convertArguments: (x1: ConvertArgument, x2: ConvertArgument, x3: ConvertArgument) {
+
+ var convertArguments: (x1: ConvertArgument, x2: ConvertArgument, x3: ConvertArgument) { // swiftlint:disable:this large_tuple
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
-
+
func generateContentJson(isVector: Bool) -> String? {
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
@@ -77,9 +81,8 @@ struct ParsedImage {
}
func generateImageContent(isVector: Bool) -> AssetContent {
-
if !imageExtensions.contains(.png) && isVector {
- //Generate svg
+ // Generate svg
return AssetContent(
images: [
AssetImageDescription(
@@ -97,7 +100,7 @@ struct ParsedImage {
)
)
} else {
- //Generate png
+ // Generate png
return AssetContent(
images: [
AssetImageDescription(
@@ -125,17 +128,17 @@ struct ParsedImage {
}
// MARK: - Extension property
-
+
func getImageProperty(isStatic: Bool, isSwiftUI: Bool) -> String {
if isSwiftUI {
return """
- \(isStatic ? "static ": "")var \(name): Image {
+ \(isStatic ? "static " : "")var \(name): Image {
Image("\(name)")
}
"""
}
return """
- \(isStatic ? "static ": "")var \(name): UIImage {
+ \(isStatic ? "static " : "")var \(name): UIImage {
UIImage(named: "\(name)")!
}
"""
diff --git a/Sources/ResgenSwift/Images/Model/PlatormTag.swift b/Sources/ResgenSwift/Images/Model/PlatormTag.swift
index b7fca9c..a95c574 100644
--- a/Sources/ResgenSwift/Images/Model/PlatormTag.swift
+++ b/Sources/ResgenSwift/Images/Model/PlatormTag.swift
@@ -8,6 +8,7 @@
import Foundation
enum PlatormTag: String {
+
case droid = "d"
case ios = "i"
}
diff --git a/Sources/ResgenSwift/Images/Parser/ImageFileParser.swift b/Sources/ResgenSwift/Images/Parser/ImageFileParser.swift
index eb2278b..a1c1527 100644
--- a/Sources/ResgenSwift/Images/Parser/ImageFileParser.swift
+++ b/Sources/ResgenSwift/Images/Parser/ImageFileParser.swift
@@ -7,36 +7,36 @@
import Foundation
-class ImageFileParser {
-
+enum ImageFileParser {
+
static func parse(_ inputFile: String, platform: PlatormTag) -> [ParsedImage] {
- let inputFileContent = try! String(contentsOfFile: inputFile, encoding: .utf8)
+ let inputFileContent = try! String(contentsOfFile: inputFile, encoding: .utf8) // swiftlint:disable:this force_try
let stringsByLines = inputFileContent.components(separatedBy: .newlines)
-
+
return Self.parseLines(stringsByLines, platform: platform)
}
-
+
static func parseLines(_ lines: [String], platform: PlatormTag) -> [ParsedImage] {
var imagesToGenerate = [ParsedImage]()
-
+
lines.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])!
+ return Int(splittedLine[2])! // swiftlint:disable:this force_unwrapping
}()
let height: Int = {
if splittedLine[3] == "?" {
return -1
}
- return Int(splittedLine[3])!
+ return Int(splittedLine[3])! // swiftlint:disable:this force_unwrapping
}()
var imageExtensions: [ImageExtension] = []
diff --git a/Sources/ResgenSwift/Strings/Generator/StringsFileGenerator.swift b/Sources/ResgenSwift/Strings/Generator/StringsFileGenerator.swift
index 5467dec..965e90d 100644
--- a/Sources/ResgenSwift/Strings/Generator/StringsFileGenerator.swift
+++ b/Sources/ResgenSwift/Strings/Generator/StringsFileGenerator.swift
@@ -8,23 +8,29 @@
import Foundation
import ToolCore
-class StringsFileGenerator {
+// swiftlint:disable type_body_length file_length
+
+enum StringsFileGenerator {
// MARK: - Strings Files
- static func writeStringsFiles(sections: [Section],
- langs: [String],
- defaultLang: String,
- tags: [String],
- outputPath: String,
- inputFilenameWithoutExt: String) {
+ 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)
+ stringsFilesContent[lang] = Self.generateStringsFileContent(
+ lang: lang,
+ defaultLang: defaultLang,
+ tags: tags,
+ sections: sections
+ )
}
// Write strings file content
@@ -35,7 +41,7 @@ class StringsFileGenerator {
let stringsFilePathURL = URL(fileURLWithPath: stringsFilePath)
do {
try fileContent.write(to: stringsFilePathURL, atomically: false, encoding: .utf8)
- } catch let error {
+ } catch {
let error = StringiumError.writeFile(error.localizedDescription, stringsFilePath)
print(error.description)
Stringium.exit(withError: error)
@@ -43,12 +49,14 @@ class StringsFileGenerator {
}
}
- static func writeXcStringsFiles(sections: [Section],
- langs: [String],
- defaultLang: String,
- tags: [String],
- outputPath: String,
- inputFilenameWithoutExt: String) {
+ static func writeXcStringsFiles(
+ sections: [Section],
+ langs: [String],
+ defaultLang: String,
+ tags: [String],
+ outputPath: String,
+ inputFilenameWithoutExt: String
+ ) {
let fileContent: String = Self.generateXcStringsFileContent(
langs: langs,
@@ -61,17 +69,19 @@ class StringsFileGenerator {
let stringsFilePathURL = URL(fileURLWithPath: stringsFilePath)
do {
try fileContent.write(to: stringsFilePathURL, atomically: false, encoding: .utf8)
- } catch let error {
+ } catch {
let error = StringiumError.writeFile(error.localizedDescription, stringsFilePath)
print(error.description)
Stringium.exit(withError: error)
}
}
- static func generateStringsFileContent(lang: String,
- defaultLang: String,
- tags inputTags: [String],
- sections: [Section]) -> String {
+ static func generateStringsFileContent(
+ lang: String,
+ defaultLang: String,
+ tags inputTags: [String],
+ sections: [Section]
+ ) -> String {
var stringsFileContent = """
/**
* Apple Strings File
@@ -120,11 +130,18 @@ class StringsFileGenerator {
// MARK: - XcStrings Generation
- static func generateXcStringsFileContent(langs: [String],
- defaultLang: String,
- tags inputTags: [String],
- sections: [Section]) -> String {
- let rootObject = generateRootObject(langs: langs, defaultLang: defaultLang, tags: inputTags, sections: sections)
+ static func generateXcStringsFileContent(
+ langs: [String],
+ defaultLang: String,
+ tags inputTags: [String],
+ sections: [Section]
+ ) -> String {
+ let rootObject = generateRootObject(
+ langs: langs,
+ defaultLang: defaultLang,
+ tags: inputTags,
+ sections: sections
+ )
let file = generateXcStringsFileContentFromRootObject(rootObject: rootObject)
return file
@@ -138,7 +155,6 @@ class StringsFileGenerator {
let json = try encoder.encode(rootObject)
return String(decoding: json, as: UTF8.self)
-
} catch {
debugPrint("Failed to encode: \(error)")
}
@@ -146,20 +162,22 @@ class StringsFileGenerator {
return ""
}
- static func generateRootObject(langs: [String],
- defaultLang: String,
- tags inputTags: [String],
- sections: [Section]) -> Root {
+ static func generateRootObject(
+ langs: [String],
+ defaultLang: String,
+ tags inputTags: [String],
+ sections: [Section]
+ ) -> Root {
var xcStringDefinitionTab: [XCStringDefinition] = []
- sections.forEach { section in
- // Check that at least one string will be generated
+ sections.forEach { section in // swiftlint:disable:this closure_body_length
+ // Check that at least one string will be generated
guard section.hasOneOrMoreMatchingTags(tags: inputTags) else {
return // Go to next section
}
- section.definitions.forEach { definition in
+ section.definitions.forEach { definition in // swiftlint:disable:this closure_body_length
var skipDefinition = false
var isNoTranslation = false
@@ -190,7 +208,6 @@ class StringsFileGenerator {
} else {
// Search for langs in twine
for (lang, value) in definition.translations where !value.isEmpty {
-
let localization = XCStringLocalization(
lang: lang,
content: XCStringLocalizationLangContent(
@@ -219,7 +236,7 @@ class StringsFileGenerator {
}
let xcStringContainer = XCStringDefinitionContainer(strings: xcStringDefinitionTab)
-
+
return Root(
sourceLanguage: defaultLang,
strings: xcStringContainer,
@@ -229,28 +246,32 @@ class StringsFileGenerator {
// MARK: - Extension file
- static func writeExtensionFiles(sections: [Section],
- defaultLang lang: String,
- tags: [String],
- staticVar: Bool,
- inputFilename: String,
- extensionName: String,
- extensionFilePath: String,
- extensionSuffix: String) {
+ static func writeExtensionFiles(
+ sections: [Section],
+ defaultLang lang: String,
+ tags: [String],
+ staticVar: Bool,
+ inputFilename: String,
+ extensionName: String,
+ extensionFilePath: String,
+ extensionSuffix: String
+ ) {
// Get extension content
- let extensionFileContent = Self.getExtensionContent(sections: sections,
- defaultLang: lang,
- tags: tags,
- staticVar: staticVar,
- inputFilename: inputFilename,
- extensionName: extensionName,
- extensionSuffix: extensionSuffix)
+ let extensionFileContent = Self.getExtensionContent(
+ sections: sections,
+ defaultLang: lang,
+ tags: tags,
+ staticVar: staticVar,
+ inputFilename: inputFilename,
+ extensionName: extensionName,
+ extensionSuffix: extensionSuffix
+ )
// Write content
let extensionFilePathURL = URL(fileURLWithPath: extensionFilePath)
do {
try extensionFileContent.write(to: extensionFilePathURL, atomically: false, encoding: .utf8)
- } catch let error {
+ } catch {
let error = StringiumError.writeFile(extensionFilePath, error.localizedDescription)
print(error.description)
Stringium.exit(withError: error)
@@ -259,17 +280,32 @@ class StringsFileGenerator {
// MARK: - Extension content
- static func getExtensionContent(sections: [Section],
- defaultLang lang: String,
- tags: [String],
- staticVar: Bool,
- inputFilename: String,
- extensionName: String,
- extensionSuffix: String) -> String {
+ static func getExtensionContent(
+ sections: [Section],
+ defaultLang lang: String,
+ tags: [String],
+ staticVar: Bool,
+ inputFilename: String,
+ extensionName: String,
+ extensionSuffix: String
+ ) -> String {
[
- Self.getHeader(stringsFilename: inputFilename, extensionClassname: extensionName),
- Self.getEnumKey(sections: sections, tags: tags, extensionClassname: extensionName, extensionSuffix: extensionSuffix),
- Self.getProperties(sections: sections, defaultLang: lang, tags: tags, staticVar: staticVar),
+ Self.getHeader(
+ stringsFilename: inputFilename,
+ extensionClassname: extensionName
+ ),
+ Self.getEnumKey(
+ sections: sections,
+ tags: tags,
+ extensionClassname: extensionName,
+ extensionSuffix: extensionSuffix
+ ),
+ Self.getProperties(
+ sections: sections,
+ defaultLang: lang,
+ tags: tags,
+ staticVar: staticVar
+ ),
Self.getFooter()
]
.joined(separator: "\n")
@@ -289,7 +325,12 @@ class StringsFileGenerator {
"""
}
- private static func getEnumKey(sections: [Section], tags: [String], extensionClassname: String, extensionSuffix: String) -> String {
+ private static func getEnumKey(
+ sections: [Section],
+ tags: [String],
+ extensionClassname: String,
+ extensionSuffix: String
+ ) -> String {
var enumDefinition = "\n enum Key\(extensionSuffix.uppercasedFirst()): String {\n"
// Enum
diff --git a/Sources/ResgenSwift/Strings/Generator/TagsGenerator.swift b/Sources/ResgenSwift/Strings/Generator/TagsGenerator.swift
index 81d6ccc..cba5740 100644
--- a/Sources/ResgenSwift/Strings/Generator/TagsGenerator.swift
+++ b/Sources/ResgenSwift/Strings/Generator/TagsGenerator.swift
@@ -1,71 +1,100 @@
//
// TagsGenerator.swift
-//
+//
//
// Created by Thibaut Schmitt on 10/01/2022.
//
+import CoreVideo
import Foundation
import ToolCore
-import CoreVideo
-class TagsGenerator {
- static func writeExtensionFiles(sections: [Section], lang: String, tags: [String], staticVar: Bool, extensionName: String, extensionFilePath: String) {
+enum TagsGenerator {
+
+ static func writeExtensionFiles(
+ sections: [Section],
+ lang: String,
+ tags: [String],
+ staticVar: Bool,
+ extensionName: String,
+ extensionFilePath: String
+ ) {
// Get extension content
- let extensionFileContent = Self.getExtensionContent(sections: sections,
- lang: lang,
- tags: tags,
- staticVar: staticVar,
- extensionName: extensionName)
-
+ let extensionFileContent = Self.getExtensionContent(
+ sections: sections,
+ lang: lang,
+ tags: tags,
+ staticVar: staticVar,
+ extensionName: extensionName
+ )
+
// Write content
let extensionFilePathURL = URL(fileURLWithPath: extensionFilePath)
do {
try extensionFileContent.write(to: extensionFilePathURL, atomically: false, encoding: .utf8)
- } catch let error {
+ } catch {
let error = StringiumError.writeFile(extensionFilePath, error.localizedDescription)
print(error.description)
Stringium.exit(withError: error)
}
}
-
+
// MARK: - Extension content
-
- static func getExtensionContent(sections: [Section], lang: String, tags: [String], staticVar: Bool, extensionName: String) -> String {
+
+ static func getExtensionContent(
+ sections: [Section],
+ lang: String,
+ tags: [String],
+ staticVar: Bool,
+ extensionName: String
+ ) -> String {
[
- Self.getHeader(extensionClassname: extensionName, staticVar: staticVar),
- Self.getProperties(sections: sections, lang: lang, tags: tags, staticVar: staticVar),
+ Self.getHeader(
+ extensionClassname: extensionName,
+ staticVar: staticVar
+ ),
+ Self.getProperties(
+ sections: sections,
+ lang: lang,
+ tags: tags,
+ staticVar: staticVar
+ ),
Self.getFooter()
]
.joined(separator: "\n")
}
-
+
// MARK: - Extension part
-
+
private static func getHeader(extensionClassname: String, staticVar: Bool) -> String {
"""
// Generated by ResgenSwift.Strings.\(Tags.toolName) \(ResgenSwiftVersion)
-
+
\(staticVar ? "typelias Tags = String\n\n" : "")import UIKit
-
+
extension \(extensionClassname) {
"""
}
-
- private static func getProperties(sections: [Section], lang: String, tags: [String], staticVar: Bool) -> String {
+
+ private static func getProperties(
+ sections: [Section],
+ lang: String,
+ tags: [String],
+ staticVar: Bool
+ ) -> String {
sections
.compactMap { section in
// Check that at least one string will be generated
guard section.hasOneOrMoreMatchingTags(tags: tags) else {
- return nil// Go to next section
+ return nil // Go to next section
}
-
+
var res = "\n // MARK: - \(section.name)"
section.definitions.forEach { definition in
guard definition.hasOneOrMoreMatchingTags(inputTags: tags) == true else {
return // Go to next definition
}
-
+
if staticVar {
res += "\n\n\(definition.getStaticProperty(forLang: lang))"
} else {
@@ -76,11 +105,11 @@ class TagsGenerator {
}
.joined(separator: "\n")
}
-
+
private static func getFooter() -> String {
"""
}
-
+
"""
}
}
diff --git a/Sources/ResgenSwift/Strings/Model/Definition.swift b/Sources/ResgenSwift/Strings/Model/Definition.swift
index b059383..cd7d29b 100644
--- a/Sources/ResgenSwift/Strings/Model/Definition.swift
+++ b/Sources/ResgenSwift/Strings/Model/Definition.swift
@@ -1,78 +1,93 @@
//
// Definition.swift
-//
+//
//
// Created by Thibaut Schmitt on 04/01/2022.
//
import Foundation
+// swiftlint:disable force_unwrapping
+
class Definition {
+
+ // MARK: - Properties
+
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)
}
-
+
func hasOneOrMoreMatchingTags(inputTags: [String]) -> Bool {
if Set(inputTags).isDisjoint(with: tags) {
return false
}
return true
}
-
+
// MARK: -
-
+
private func getStringParameters(input: String) -> (inputParameters: [String], translationArguments: [String])? {
var methodsParameters = [String]()
-
- let printfPlaceholderRegex = try! NSRegularExpression(pattern: "%(?:\\d+\\$)?[+-]?(?:[ 0]|'.{1})?-?\\d*(?:\\.\\d+)?[blcdeEufFgGosxX@]*")
- printfPlaceholderRegex.enumerateMatches(in: input, options: [], range: NSRange(location: 0, length: input.count)) { match, _, stop in
- guard let match = match else { return }
-
+
+ let printfPlaceholderRegex = try! NSRegularExpression( // swiftlint:disable:this force_try
+ pattern: "%(?:\\d+\\$)?[+-]?(?:[ 0]|'.{1})?-?\\d*(?:\\.\\d+)?[blcdeEufFgGosxX@]*"
+ )
+ printfPlaceholderRegex.enumerateMatches(
+ in: input,
+ options: [],
+ range: NSRange(location: 0, length: input.count)
+ ) { match, _, stop in // swiftlint:disable:this unused_closure_parameter
+ guard let match else { return }
+
if let range = Range(match.range, in: input), let last = input[range].last {
switch last {
case "d", "u":
methodsParameters.append("Int")
+
case "f", "F":
methodsParameters.append("Double")
+
case "@", "s", "c":
methodsParameters.append("String")
+
case "%":
// if you need to print %, you have to add %%
break
+
default:
break
}
}
}
-
+
if methodsParameters.isEmpty {
return nil
}
-
+
var inputParameters = [String]()
var translationArguments = [String]()
for (index, paramType) in methodsParameters.enumerated() {
@@ -80,10 +95,10 @@ class Definition {
translationArguments.append(paramName)
inputParameters.append("\(paramName): \(paramType)")
}
-
+
return (inputParameters: inputParameters, translationArguments: translationArguments)
}
-
+
private func getBaseProperty(lang: String, translation: String, isStatic: Bool, comment: String?) -> String {
"""
/// Translation in \(lang) :
@@ -91,15 +106,20 @@ class Definition {
///
/// Comment :
/// \(comment?.isEmpty == false ? comment! : "No comment")
- \(isStatic ? "static ": "")var \(name): String {
+ \(isStatic ? "static " : "")var \(name): String {
NSLocalizedString("\(name)", tableName: kStringsFileName, bundle: Bundle.main, value: "\(translation)", comment: "\(comment ?? "")")
}
"""
-
}
-
- private func getBaseMethod(lang: String, translation: String, isStatic: Bool, inputParameters: [String], translationArguments: [String], comment: String?) -> String {
+ private func getBaseMethod(
+ lang: String,
+ translation: String,
+ isStatic: Bool,
+ inputParameters: [String],
+ translationArguments: [String],
+ comment: String?
+ ) -> String {
"""
/// Translation in \(lang) :
@@ -107,12 +127,12 @@ class Definition {
///
/// Comment :
/// \(comment?.isEmpty == false ? comment! : "No comment")
- \(isStatic ? "static ": "")func \(name)(\(inputParameters.joined(separator: ", "))) -> String {
+ \(isStatic ? "static " : "")func \(name)(\(inputParameters.joined(separator: ", "))) -> String {
String(format: \(isStatic ? "Self" : "self").\(name), \(translationArguments.joined(separator: ", ")))
}
"""
}
-
+
func getNSLocalizedStringProperty(forLang lang: String) -> String {
guard let translation = translations[lang] else {
let error = StringiumError.langNotDefined(lang, name, reference != nil)
@@ -131,24 +151,26 @@ class Definition {
// Generate method
var method = ""
if let parameters = self.getStringParameters(input: translation) {
- method = getBaseMethod(lang: lang,
- translation: translation,
- isStatic: false,
- inputParameters: parameters.inputParameters,
- translationArguments: parameters.translationArguments,
- comment: self.comment)
+ method = getBaseMethod(
+ lang: lang,
+ translation: translation,
+ isStatic: false,
+ inputParameters: parameters.inputParameters,
+ translationArguments: parameters.translationArguments,
+ comment: self.comment
+ )
}
-
+
return property + method
}
-
+
func getNSLocalizedStringStaticProperty(forLang lang: String) -> String {
guard let translation = translations[lang] else {
let error = StringiumError.langNotDefined(lang, name, reference != nil)
print(error.description)
Stringium.exit(withError: error)
}
-
+
// Generate property
let property = getBaseProperty(
lang: lang,
@@ -156,23 +178,25 @@ class Definition {
isStatic: true,
comment: self.comment
)
-
+
// Generate method
var method = ""
if let parameters = self.getStringParameters(input: translation) {
- method = getBaseMethod(lang: lang,
- translation: translation,
- isStatic: true,
- inputParameters: parameters.inputParameters,
- translationArguments: parameters.translationArguments,
- comment: self.comment)
+ method = getBaseMethod(
+ lang: lang,
+ translation: translation,
+ isStatic: true,
+ inputParameters: parameters.inputParameters,
+ translationArguments: parameters.translationArguments,
+ comment: self.comment
+ )
}
-
+
return property + method
}
-
+
// MARK: - Raw strings
-
+
func getProperty(forLang lang: String) -> String {
guard let translation = translations[lang] else {
let error = StringiumError.langNotDefined(lang, name, reference != nil)
@@ -192,14 +216,14 @@ class Definition {
}
"""
}
-
+
func getStaticProperty(forLang lang: String) -> String {
guard let translation = translations[lang] else {
let error = StringiumError.langNotDefined(lang, name, reference != nil)
print(error.description)
Stringium.exit(withError: error)
}
-
+
return """
/// Translation in \(lang) :
/// \(translation)
diff --git a/Sources/ResgenSwift/Strings/Model/Section.swift b/Sources/ResgenSwift/Strings/Model/Section.swift
index 42241d2..680ac32 100644
--- a/Sources/ResgenSwift/Strings/Model/Section.swift
+++ b/Sources/ResgenSwift/Strings/Model/Section.swift
@@ -8,28 +8,35 @@
import Foundation
class Section {
+
+ // MARK: - Properties
+
let name: String // OnBoarding
var definitions = [Definition]()
-
+
+ // MARK: - Init
+
init(name: String) {
self.name = name
}
-
+
+ // MARK: - Methods
+
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 }
let allTagsSet = Set(allTags)
-
+
let intersection = Set(tags).intersection(allTagsSet)
if intersection.isEmpty {
return false
diff --git a/Sources/ResgenSwift/Strings/Model/XcString.swift b/Sources/ResgenSwift/Strings/Model/XcString.swift
index 6c784d1..dbfef43 100644
--- a/Sources/ResgenSwift/Strings/Model/XcString.swift
+++ b/Sources/ResgenSwift/Strings/Model/XcString.swift
@@ -8,25 +8,30 @@
import SwiftUI
struct DynamicKey: CodingKey {
+
var intValue: Int?
+
init?(intValue: Int) {
self.intValue = intValue
self.stringValue = "\(intValue)"
}
var stringValue: String
+
init?(stringValue: String) {
self.stringValue = stringValue
}
}
struct Root: Codable, Equatable {
+
let sourceLanguage: String
let strings: XCStringDefinitionContainer
let version: String
}
struct XCStringDefinitionContainer: Codable, Equatable {
+
let strings: [XCStringDefinition]
func encode(to encoder: Encoder) throws {
@@ -39,19 +44,19 @@ struct XCStringDefinitionContainer: Codable, Equatable {
}
}
- static func == (lhs: XCStringDefinitionContainer, rhs: XCStringDefinitionContainer) -> Bool {
- return lhs.strings.sorted(by: {
- $0.title < $1.title
- }) == rhs.strings.sorted(by: { $0.title < $1.title })
+ static func == (lhs: Self, rhs: Self) -> Bool {
+ lhs.strings.sorted { $0.title < $1.title } == rhs.strings.sorted { $0.title < $1.title }
}
}
struct XCStringDefinition: Codable, Equatable {
+
let title: String // json key -> custom encoding methods
let content: XCStringDefinitionContent
}
struct XCStringDefinitionContent: Codable, Equatable {
+
let comment: String?
let extractionState: String
var localizations: XCStringLocalizationContainer
@@ -64,6 +69,7 @@ struct XCStringDefinitionContent: Codable, Equatable {
}
struct XCStringLocalizationContainer: Codable, Equatable {
+
let localizations: [XCStringLocalization]
func encode(to encoder: Encoder) throws {
@@ -76,34 +82,39 @@ struct XCStringLocalizationContainer: Codable, Equatable {
}
}
- static func == (lhs: XCStringLocalizationContainer, rhs: XCStringLocalizationContainer) -> Bool {
- return lhs.localizations.count == rhs.localizations.count && lhs.localizations.sorted(by: { $0.lang < $1.lang }) == rhs.localizations.sorted(by: { $0.lang < $1.lang })
+ static func == (lhs: Self, rhs: Self) -> Bool {
+ lhs.localizations.count == rhs.localizations.count
+ && lhs.localizations.sorted { $0.lang < $1.lang } == rhs.localizations.sorted { $0.lang < $1.lang }
}
}
struct XCStringLocalization: Codable, Equatable {
+
let lang: String // json key -> custom encoding method
let content: XCStringLocalizationLangContent
}
struct XCStringLocalizationLangContent: Codable, Equatable {
+
let stringUnit: DefaultStringUnit
}
-//enum VarationOrStringUnit: Encodable {
-// case variations([Varation])
-// case stringUnit: (DefaultStringUnit)
-//
-// func encode(to encoder: any Encoder) throws {
-// if let varations {
-//
-// } else if let {
-//
-// }
-// }
-//}
+// enum VarationOrStringUnit: Encodable
+//
+// case variations([Varation])
+// case stringUnit: (DefaultStringUnit)
+//
+// func encode(to encoder: any Encoder) throws {
+// if let varations {
+//
+// } else if let {
+//
+// }
+// }
+// }
struct DefaultStringUnit: Codable, Equatable {
+
let state: String
let value: String
}
diff --git a/Sources/ResgenSwift/Strings/Parser/TwineFileParser.swift b/Sources/ResgenSwift/Strings/Parser/TwineFileParser.swift
index b7f6803..d23a9be 100644
--- a/Sources/ResgenSwift/Strings/Parser/TwineFileParser.swift
+++ b/Sources/ResgenSwift/Strings/Parser/TwineFileParser.swift
@@ -1,76 +1,74 @@
//
// TwineFileParser.swift
-//
+//
//
// Created by Thibaut Schmitt on 10/01/2022.
//
import Foundation
-class TwineFileParser {
+// swiftlint:disable function_body_length
+
+enum TwineFileParser {
+
static func parse(_ inputFile: String) -> [Section] {
- let inputFileContent = try! String(contentsOfFile: inputFile, encoding: .utf8)
+ let inputFileContent = try! String(contentsOfFile: inputFile, encoding: .utf8) // swiftlint:disable:this force_try
let stringsByLines = inputFileContent.components(separatedBy: .newlines)
-
+
var sections = [Section]()
-
+
// Parse file
- stringsByLines.forEach {
+ stringsByLines.forEach { // swiftlint:disable:this closure_body_length
// 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 else {
- return
- }
-
+ return
+ }
+
let rightElement: String = splitLine.dropFirst().joined(separator: "=")
-
+
// "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
@@ -83,10 +81,10 @@ class TwineFileParser {
return true
}
}
- if invalidDefinitionNames.count > 0 {
+ if invalidDefinitionNames.isEmpty == false {
print("warning: [\(Stringium.toolName)] Found \(invalidDefinitionNames.count) definition (\(invalidDefinitionNames.joined(separator: ", "))")
}
-
+
return sections
}
}
diff --git a/Sources/ResgenSwift/Strings/Stringium/Stringium.swift b/Sources/ResgenSwift/Strings/Stringium/Stringium.swift
index a27ebfe..bfb3894 100644
--- a/Sources/ResgenSwift/Strings/Stringium/Stringium.swift
+++ b/Sources/ResgenSwift/Strings/Stringium/Stringium.swift
@@ -1,114 +1,122 @@
//
// Stringium.swift
-//
+//
//
// Created by Thibaut Schmitt on 10/01/2022.
//
-import ToolCore
-import Foundation
import ArgumentParser
+import Foundation
+import ToolCore
struct Stringium: ParsableCommand {
-
+
// MARK: - Command Configuration
-
+
static var configuration = CommandConfiguration(
abstract: "Generate strings with custom scripts.",
version: ResgenSwiftVersion
)
-
+
// MARK: - Static
-
+
static let toolName = "Stringium"
static let defaultExtensionName = "String"
static let noTranslationTag: String = "notranslation"
-
+
// MARK: - Command options
-
+
@OptionGroup var options: StringiumOptions
-
+
// MARK: - Run
-
+
mutating func run() {
print("[\(Self.toolName)] Starting strings generation")
print("[\(Self.toolName)] Will use inputFile \(options.inputFile) to generate strings for \(options.langs) (default lang: \(options.defaultLang)")
-
+
// Check requirements
guard checkRequirements() else { return }
-
+
print("[\(Self.toolName)] Will generate strings")
-
+
// Parse input file
let sections = TwineFileParser.parse(options.inputFile)
-
+
// Generate strings files
print(options.xcStrings)
if !options.xcStrings {
print("[\(Self.toolName)] Will generate strings")
- StringsFileGenerator.writeStringsFiles(sections: sections,
- langs: options.langs,
- defaultLang: options.defaultLang,
- tags: options.tags,
- outputPath: options.stringsFileOutputPath,
- inputFilenameWithoutExt: options.inputFilenameWithoutExt)
+ StringsFileGenerator.writeStringsFiles(
+ sections: sections,
+ langs: options.langs,
+ defaultLang: options.defaultLang,
+ tags: options.tags,
+ outputPath: options.stringsFileOutputPath,
+ inputFilenameWithoutExt: options.inputFilenameWithoutExt
+ )
} else {
print("[\(Self.toolName)] Will generate xcStrings")
- StringsFileGenerator.writeXcStringsFiles(sections: sections,
- langs: options.langs,
- defaultLang: options.defaultLang,
- tags: options.tags,
- outputPath: options.stringsFileOutputPath,
- inputFilenameWithoutExt: options.inputFilenameWithoutExt)
+ StringsFileGenerator.writeXcStringsFiles(
+ sections: sections,
+ langs: options.langs,
+ defaultLang: options.defaultLang,
+ tags: options.tags,
+ outputPath: options.stringsFileOutputPath,
+ inputFilenameWithoutExt: options.inputFilenameWithoutExt
+ )
}
// Generate extension
- StringsFileGenerator.writeExtensionFiles(sections: sections,
- defaultLang: options.defaultLang,
- tags: options.tags,
- staticVar: options.staticMembers,
- inputFilename: options.inputFilenameWithoutExt,
- extensionName: options.extensionName,
- extensionFilePath: options.extensionFilePath,
- extensionSuffix: options.extensionSuffix)
+ StringsFileGenerator.writeExtensionFiles(
+ sections: sections,
+ defaultLang: options.defaultLang,
+ tags: options.tags,
+ staticVar: options.staticMembers,
+ inputFilename: options.inputFilenameWithoutExt,
+ extensionName: options.extensionName,
+ extensionFilePath: options.extensionFilePath,
+ extensionSuffix: options.extensionSuffix
+ )
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.description)
- Stringium.exit(withError: error)
+ Self.exit(withError: error)
}
-
+
// Langs
guard options.langs.isEmpty == false else {
let error = StringiumError.langsListEmpty
print(error.description)
- Stringium.exit(withError: error)
+ Self.exit(withError: error)
}
-
+
guard options.langs.contains(options.defaultLang) else {
let error = StringiumError.defaultLangsNotInLangs
print(error.description)
- Stringium.exit(withError: error)
+ Self.exit(withError: error)
}
-
+
// Check if needed to regenerate
- guard GeneratorChecker.shouldGenerate(force: options.forceGeneration,
- inputFilePath: options.inputFile,
- extensionFilePath: options.extensionFilePath) else {
+ guard GeneratorChecker.shouldGenerate(
+ force: options.forceGeneration,
+ inputFilePath: options.inputFile,
+ extensionFilePath: options.extensionFilePath
+ ) else {
print("[\(Self.toolName)] Strings are already up to date :) ")
return false
}
-
+
return true
}
}
diff --git a/Sources/ResgenSwift/Strings/Stringium/StringiumError.swift b/Sources/ResgenSwift/Strings/Stringium/StringiumError.swift
index ba12a82..b96341a 100644
--- a/Sources/ResgenSwift/Strings/Stringium/StringiumError.swift
+++ b/Sources/ResgenSwift/Strings/Stringium/StringiumError.swift
@@ -8,27 +8,28 @@
import Foundation
enum StringiumError: Error {
+
case fileNotExists(String)
case langsListEmpty
case defaultLangsNotInLangs
case writeFile(String, String)
case langNotDefined(String, String, Bool)
-
+
var description: 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):
+
+ case let .writeFile(subErrorDescription, filename):
return "error: [\(Stringium.toolName)] An error occured while writing content to \(filename): \(subErrorDescription)"
-
- case .langNotDefined(let lang, let definitionName, let isReference):
+
+ case let .langNotDefined(lang, definitionName, isReference):
if isReference {
return "error: [\(Stringium.toolName)] Reference are handled only by Twine. Please use it or remove reference from you strings file."
}
diff --git a/Sources/ResgenSwift/Strings/Stringium/StringiumOptions.swift b/Sources/ResgenSwift/Strings/Stringium/StringiumOptions.swift
index 7c9c7a8..8c1a05e 100644
--- a/Sources/ResgenSwift/Strings/Stringium/StringiumOptions.swift
+++ b/Sources/ResgenSwift/Strings/Stringium/StringiumOptions.swift
@@ -5,31 +5,34 @@
// Created by Thibaut Schmitt on 10/01/2022.
//
-import Foundation
import ArgumentParser
+import Foundation
+
+// swiftlint:disable no_grouping_extension
struct StringiumOptions: ParsableArguments {
+
@Flag(name: [.customShort("f"), .customShort("F")], help: "Should force generation")
var forceGeneration = false
@Argument(help: "Input files where strings are defined.", transform: { $0.replaceTiltWithHomeDirectoryPath() })
var inputFile: String
-
+
@Option(name: .customLong("output-path"), help: "Path where to strings file.", transform: { $0.replaceTiltWithHomeDirectoryPath() })
fileprivate var outputPathRaw: String
-
+
@Option(name: .customLong("langs"), help: "Langs to generate.")
fileprivate var langsRaw: String
-
+
@Option(help: "Default langs.")
var defaultLang: String
-
+
@Option(name: .customLong("tags"), help: "Tags to generate.")
fileprivate var tagsRaw: String = "ios iosonly iosOnly notranslation"
-
+
@Option(help: "Path where to generate the extension.", transform: { $0.replaceTiltWithHomeDirectoryPath() })
var extensionOutputPath: String
-
+
@Option(help: "Tell if it will generate static properties or not")
var staticMembers: Bool = false
@@ -38,7 +41,7 @@ struct StringiumOptions: ParsableArguments {
@Option(help: "Extension name. If not specified, it will generate an String extension.")
var extensionName: String = Stringium.defaultExtensionName
-
+
@Option(help: "Extension suffix: {extensionName}+{extensionSuffix}.swift")
var extensionSuffix: String
}
@@ -46,6 +49,7 @@ struct StringiumOptions: ParsableArguments {
// MARK: - Private var getter
extension StringiumOptions {
+
var stringsFileOutputPath: String {
var outputPath = outputPathRaw
if outputPath.last == "/" {
@@ -53,13 +57,13 @@ extension StringiumOptions {
}
return outputPath
}
-
+
var langs: [String] {
langsRaw
.split(separator: " ")
.map { String($0) }
}
-
+
var tags: [String] {
tagsRaw
.split(separator: " ")
@@ -70,14 +74,15 @@ extension StringiumOptions {
// MARK: - Computed var
extension StringiumOptions {
+
var extensionFileName: String {
"\(extensionName)+\(extensionSuffix).swift"
}
-
+
var extensionFilePath: String {
"\(extensionOutputPath)/\(extensionFileName)"
}
-
+
var inputFilenameWithoutExt: String {
URL(fileURLWithPath: inputFile)
.deletingPathExtension()
diff --git a/Sources/ResgenSwift/Strings/Strings.swift b/Sources/ResgenSwift/Strings/Strings.swift
index 1465d33..c19dda8 100644
--- a/Sources/ResgenSwift/Strings/Strings.swift
+++ b/Sources/ResgenSwift/Strings/Strings.swift
@@ -5,12 +5,12 @@
// Created by Thibaut Schmitt on 10/01/2022.
//
-import ToolCore
-import Foundation
import ArgumentParser
+import Foundation
+import ToolCore
struct Strings: ParsableCommand {
-
+
static var configuration = CommandConfiguration(
abstract: "A utility for generate strings.",
version: ResgenSwiftVersion,
@@ -22,8 +22,8 @@ struct Strings: ParsableCommand {
// A default subcommand, when provided, is automatically selected if a
// subcommand is not given on the command line.
- //defaultSubcommand: Twine.self
+ // defaultSubcommand: Twine.self
)
}
-//Strings.main()
+// Strings.main()
diff --git a/Sources/ResgenSwift/Strings/Tag/Tags.swift b/Sources/ResgenSwift/Strings/Tag/Tags.swift
index 26c0c4b..b1d4482 100644
--- a/Sources/ResgenSwift/Strings/Tag/Tags.swift
+++ b/Sources/ResgenSwift/Strings/Tag/Tags.swift
@@ -1,78 +1,82 @@
//
// Tag.swift
-//
+//
//
// Created by Thibaut Schmitt on 10/01/2022.
//
-import ToolCore
-import Foundation
import ArgumentParser
+import Foundation
+import ToolCore
struct Tags: ParsableCommand {
-
+
// MARK: - Command Configuration
-
+
static var configuration = CommandConfiguration(
abstract: "Generate tags extension file.",
version: ResgenSwiftVersion
)
-
+
// MARK: - Static
-
+
static let toolName = "Tags"
static let defaultExtensionName = "Tags"
static let noTranslationTag: String = "notranslation"
-
+
// MARK: - Command Options
-
+
@OptionGroup var options: TagsOptions
-
+
// MARK: - Run
-
+
mutating func run() {
print("[\(Self.toolName)] Starting tags generation")
print("[\(Self.toolName)] Will use inputFile \(options.inputFile) to generate strings for lang: \(options.lang)")
-
+
// Check requirements
guard checkRequirements() else { return }
-
+
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.staticMembers,
- extensionName: options.extensionName,
- extensionFilePath: options.extensionFilePath)
-
+ TagsGenerator.writeExtensionFiles(
+ sections: sections,
+ lang: options.lang,
+ tags: ["ios", "iosonly", Self.noTranslationTag],
+ staticVar: options.staticMembers,
+ extensionName: options.extensionName,
+ extensionFilePath: options.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.description)
Stringium.exit(withError: error)
}
-
+
// Check if needed to regenerate
- guard GeneratorChecker.shouldGenerate(force: options.forceGeneration,
- inputFilePath: options.inputFile,
- extensionFilePath: options.extensionFilePath) else {
+ guard GeneratorChecker.shouldGenerate(
+ force: options.forceGeneration,
+ inputFilePath: options.inputFile,
+ extensionFilePath: options.extensionFilePath
+ ) else {
print("[\(Self.toolName)] Tags are already up to date :) ")
return false
}
-
+
return true
}
}
diff --git a/Sources/ResgenSwift/Strings/Tag/TagsOptions.swift b/Sources/ResgenSwift/Strings/Tag/TagsOptions.swift
index e5372b9..0c6ba11 100644
--- a/Sources/ResgenSwift/Strings/Tag/TagsOptions.swift
+++ b/Sources/ResgenSwift/Strings/Tag/TagsOptions.swift
@@ -5,28 +5,31 @@
// Created by Thibaut Schmitt on 10/01/2022.
//
-import Foundation
import ArgumentParser
+import Foundation
+
+// swiftlint:disable no_grouping_extension
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: "Tell if it will generate static properties or not")
var staticMembers: Bool = false
-
+
@Option(help: "Extension name. If not specified, it will generate a Tag extension.")
var extensionName: String = Tags.defaultExtensionName
-
+
@Option(help: "Extension suffix. Ex: MyApp, it will generate {extensionName}+Tag{extensionSuffix}.swift")
var extensionSuffix: String?
}
@@ -34,13 +37,14 @@ struct TagsOptions: ParsableArguments {
// MARK: - Computed var
extension TagsOptions {
+
var extensionFileName: String {
- if let extensionSuffix = extensionSuffix {
+ if let extensionSuffix {
return "\(extensionName)+\(extensionSuffix).swift"
}
return "\(extensionName).swift"
}
-
+
var extensionFilePath: String {
"\(extensionOutputPath)/\(extensionFileName)"
}
diff --git a/Sources/ResgenSwift/Strings/Twine/Twine.swift b/Sources/ResgenSwift/Strings/Twine/Twine.swift
index 7af478d..08270dc 100644
--- a/Sources/ResgenSwift/Strings/Twine/Twine.swift
+++ b/Sources/ResgenSwift/Strings/Twine/Twine.swift
@@ -1,96 +1,117 @@
//
// Twine.swift
-//
+//
//
// Created by Thibaut Schmitt on 10/01/2022.
//
-import ToolCore
-import Foundation
import ArgumentParser
+import Foundation
+import ToolCore
struct Twine: ParsableCommand {
-
+
// MARK: - Command Configuration
-
+
static var configuration = CommandConfiguration(
abstract: "Generate strings with twine.",
version: ResgenSwiftVersion
)
-
+
// MARK: - Static
-
+
static let toolName = "Twine"
static let defaultExtensionName = "String"
- static let twineExecutable = "\(FileManager.default.homeDirectoryForCurrentUser.relativePath)/scripts/twine/twine"
-
+ static let twineExecutable: String = {
+#if os(macOS)
+ "\(FileManager.default.homeDirectoryForCurrentUser.relativePath)/scripts/twine/twine"
+#else
+ fatalError("This command should run on Mac only")
+#endif
+ }()
+
// MARK: - Command Options
-
+
@OptionGroup var options: TwineOptions
-
+
// MARK: - Run
-
+
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 options.langs {
- Shell.shell([Self.twineExecutable,
- "generate-localization-file", options.inputFile,
- "--lang", "\(lang)",
- "\(options.outputPath)/\(lang).lproj/\(options.inputFilenameWithoutExt).strings",
- "--tags=ios,iosonly,iosOnly"])
+ Shell.shell(
+ [
+ Self.twineExecutable,
+ "generate-localization-file",
+ options.inputFile,
+ "--lang",
+ "\(lang)",
+ "\(options.outputPath)/\(lang).lproj/\(options.inputFilenameWithoutExt).strings",
+ "--tags=ios,iosonly,iosOnly"
+ ]
+ )
}
-
+
// Generate extension
- Shell.shell([Self.twineExecutable,
- "generate-localization-file", options.inputFile,
- "--format", "apple-swift",
- "--lang", "\(options.defaultLang)",
- options.extensionFilePath,
- "--tags=ios,iosonly,iosOnly"])
-
+ Shell.shell(
+ [
+ Self.twineExecutable,
+ "generate-localization-file",
+ options.inputFile,
+ "--format",
+ "apple-swift",
+ "--lang",
+ "\(options.defaultLang)",
+ options.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.description)
- Twine.exit(withError: error)
+ Self.exit(withError: error)
}
-
+
// Langs
guard options.langs.isEmpty == false else {
let error = TwineError.langsListEmpty
print(error.description)
- Twine.exit(withError: error)
+ Self.exit(withError: error)
}
-
+
guard options.langs.contains(options.defaultLang) else {
let error = TwineError.defaultLangsNotInLangs
print(error.description)
- Twine.exit(withError: error)
+ Self.exit(withError: error)
}
-
+
// Check if needed to regenerate
- guard GeneratorChecker.shouldGenerate(force: options.forceGeneration,
- inputFilePath: options.inputFile,
- extensionFilePath: options.extensionFilePathGenerated) else {
+ guard GeneratorChecker.shouldGenerate(
+ force: options.forceGeneration,
+ inputFilePath: options.inputFile,
+ extensionFilePath: options.extensionFilePathGenerated
+ ) else {
print("[\(Self.toolName)] Strings are already up to date :) ")
return false
}
-
+
return true
}
}
diff --git a/Sources/ResgenSwift/Strings/Twine/TwineError.swift b/Sources/ResgenSwift/Strings/Twine/TwineError.swift
index 82ed17f..cca5e80 100644
--- a/Sources/ResgenSwift/Strings/Twine/TwineError.swift
+++ b/Sources/ResgenSwift/Strings/Twine/TwineError.swift
@@ -8,18 +8,19 @@
import Foundation
enum TwineError: Error {
+
case fileNotExists(String)
case langsListEmpty
case defaultLangsNotInLangs
-
+
var description: 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/ResgenSwift/Strings/Twine/TwineOptions.swift b/Sources/ResgenSwift/Strings/Twine/TwineOptions.swift
index 3d79aff..6b6641d 100644
--- a/Sources/ResgenSwift/Strings/Twine/TwineOptions.swift
+++ b/Sources/ResgenSwift/Strings/Twine/TwineOptions.swift
@@ -5,25 +5,28 @@
// Created by Thibaut Schmitt on 10/01/2022.
//
-import Foundation
import ArgumentParser
+import Foundation
+
+// swiftlint:disable no_grouping_extension
struct TwineOptions: 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(help: "Path where to strings file.", transform: { $0.replaceTiltWithHomeDirectoryPath() })
var outputPath: String
-
+
@Option(name: .customLong("langs"), help: "Langs to generate.")
fileprivate var langsRaw: String
-
+
@Option(help: "Default langs.")
var defaultLang: String
-
+
@Option(help: "Path where to generate the extension.", transform: { $0.replaceTiltWithHomeDirectoryPath() })
var extensionOutputPath: String
}
@@ -31,6 +34,7 @@ struct TwineOptions: ParsableArguments {
// MARK: - Private var getter
extension TwineOptions {
+
var langs: [String] {
langsRaw
.split(separator: " ")
@@ -41,16 +45,17 @@ extension TwineOptions {
// MARK: - Computed var
extension TwineOptions {
+
var inputFilenameWithoutExt: String {
URL(fileURLWithPath: inputFile)
.deletingPathExtension()
.lastPathComponent
}
-
+
var extensionFilePath: String {
"\(extensionOutputPath)/\(inputFilenameWithoutExt).swift"
}
-
+
// "R2String+" is hardcoded in Twine formatter
var extensionFilePathGenerated: String {
"\(extensionOutputPath)/R2String+\(inputFilenameWithoutExt).swift"
diff --git a/Sources/ResgenSwift/main.swift b/Sources/ResgenSwift/main.swift
index 01bd9c2..78f5fdb 100644
--- a/Sources/ResgenSwift/main.swift
+++ b/Sources/ResgenSwift/main.swift
@@ -5,12 +5,12 @@
// Created by Thibaut Schmitt on 13/12/2021.
//
-import ToolCore
-import Foundation
import ArgumentParser
+import Foundation
+import ToolCore
struct ResgenSwift: ParsableCommand {
-
+
static var configuration = CommandConfiguration(
abstract: "A utility for generate ressources.",
version: ResgenSwiftVersion,
@@ -30,7 +30,7 @@ struct ResgenSwift: ParsableCommand {
// A default subcommand, when provided, is automatically selected if a
// subcommand is not given on the command line.
- //defaultSubcommand: Twine.self
+ // defaultSubcommand: Twine.self
)
}
diff --git a/Sources/ToolCore/GeneratorChecker.swift b/Sources/ToolCore/GeneratorChecker.swift
index f96c6a8..c6dfb9c 100644
--- a/Sources/ToolCore/GeneratorChecker.swift
+++ b/Sources/ToolCore/GeneratorChecker.swift
@@ -1,43 +1,42 @@
//
// GeneratorChecker.swift
-//
+//
//
// Created by Thibaut Schmitt on 22/12/2021.
//
import Foundation
-public class GeneratorChecker {
-
+public enum GeneratorChecker {
+
/// Return `true` if inputFile is newer than extensionFile, otherwise `false`
public static func shouldGenerate(force: Bool, inputFilePath: String, extensionFilePath: String) -> Bool {
guard force == false else {
return true
}
-
+
return Self.isFile(inputFilePath, moreRecenThan: extensionFilePath)
}
-
+
public static func isFile(_ fileOne: String, moreRecenThan fileTwo: String) -> Bool {
let fileOneURL = URL(fileURLWithPath: fileOne)
let fileTwoURL = URL(fileURLWithPath: fileTwo)
-
+
let fileOneRessourceValues = try? fileOneURL.resourceValues(forKeys: [URLResourceKey.contentModificationDateKey])
let fileTwoRessourceValues = try? fileTwoURL.resourceValues(forKeys: [URLResourceKey.contentModificationDateKey])
-
+
guard let fileOneModificationDate = fileOneRessourceValues?.contentModificationDate,
let fileTwoModificationDate = fileTwoRessourceValues?.contentModificationDate else {
print("⚠️ Could not compare file modication date. ⚠️ (assume than file is newer)")
// Date not available -> assume than fileOne is newer than fileTwo
return true
}
-
+
if fileOneModificationDate >= fileTwoModificationDate {
debugPrint("File one is more recent than file two.")
return true
}
-
+
return false
}
-
}
diff --git a/Sources/ToolCore/SequenceExtensions.swift b/Sources/ToolCore/SequenceExtensions.swift
index 2608276..7a1e0b6 100644
--- a/Sources/ToolCore/SequenceExtensions.swift
+++ b/Sources/ToolCore/SequenceExtensions.swift
@@ -7,8 +7,9 @@
import Foundation
-public extension Sequence where Iterator.Element: Hashable {
- func unique() -> [Iterator.Element] {
+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/ToolCore/Shell.swift b/Sources/ToolCore/Shell.swift
index 0bcefdc..a6fd2ca 100644
--- a/Sources/ToolCore/Shell.swift
+++ b/Sources/ToolCore/Shell.swift
@@ -1,67 +1,48 @@
//
// Shell.swift
-//
+//
//
// Created by Thibaut Schmitt on 22/12/2021.
//
import Foundation
-public class Shell {
-
+public enum Shell {
+
public static var environment: [String: String] {
ProcessInfo.processInfo.environment
}
-
-// @discardableResult
-// public static func shell(launchPath: String = "/usr/bin/env", _ args: String...) -> (terminationStatus: Int32, output: String?) {
-// let task = Process()
-// task.launchPath = launchPath
-// task.arguments = args
-//
-// var currentEnv = ProcessInfo.processInfo.environment
-// for (key, value) in environment {
-// currentEnv[key] = value
-// }
-// task.environment = currentEnv
-//
-// let pipe = Pipe()
-// task.standardOutput = pipe
-// try? task.run()
-// task.waitUntilExit()
-//
-// let data = pipe.fileHandleForReading.readDataToEndOfFile()
-//
-// guard let output: String = String(data: data, encoding: .utf8) else {
-// return (terminationStatus: task.terminationStatus, output: nil)
-// }
-//
-// return (terminationStatus: task.terminationStatus, output: output)
-// }
-
+
@discardableResult
- public static func shell(launchPath: String = "/usr/bin/env", _ args: [String]) -> (terminationStatus: Int32, output: String?) {
+ public static func shell(
+ launchPath: String = "/usr/bin/env",
+ _ args: [String]
+ ) -> (terminationStatus: Int32, output: String?) {
+#if os(macOS)
let task = Process()
task.launchPath = launchPath
task.arguments = args
-
+
var currentEnv = ProcessInfo.processInfo.environment
for (key, value) in environment {
currentEnv[key] = value
}
task.environment = currentEnv
-
+
let pipe = Pipe()
task.standardOutput = pipe
task.launch()
task.waitUntilExit()
-
+
let data = pipe.fileHandleForReading.readDataToEndOfFile()
-
- guard let output: String = String(data: data, encoding: .utf8) else {
+
+ guard let output = String(data: data, encoding: .utf8) else {
return (terminationStatus: task.terminationStatus, output: nil)
}
-
+
return (terminationStatus: task.terminationStatus, output: output)
+#else
+ fatalError("Shell is only available on Mac")
+#endif
}
}
diff --git a/Sources/ToolCore/StringExtensions.swift b/Sources/ToolCore/StringExtensions.swift
index 401f7a7..3c97e07 100644
--- a/Sources/ToolCore/StringExtensions.swift
+++ b/Sources/ToolCore/StringExtensions.swift
@@ -1,83 +1,88 @@
//
// Extensions.swift
-//
+//
//
// Created by Thibaut Schmitt on 13/12/2021.
//
import Foundation
-public extension String {
- func removeCharacters(from forbiddenChars: CharacterSet) -> String {
+extension String {
+
+ public func removeCharacters(from forbiddenChars: CharacterSet) -> String {
let passed = self.unicodeScalars.filter { !forbiddenChars.contains($0) }
return String(String.UnicodeScalarView(passed))
}
-
- func removeCharacters(from: String) -> String {
- return removeCharacters(from: CharacterSet(charactersIn: from))
+
+ public func removeCharacters(from: String) -> String {
+ removeCharacters(from: CharacterSet(charactersIn: from))
}
-
- func replacingOccurrences(of: [String], with: String) -> Self {
+
+ public func replacingOccurrences(of: [String], with: String) -> Self {
var tmp = self
- for e in of {
- tmp = tmp.replacingOccurrences(of: e, with: with)
+ for ofValue in of {
+ tmp = tmp.replacingOccurrences(of: ofValue, with: with)
}
return tmp
}
-
- func removeTrailingWhitespace() -> Self {
+
+ public func removeTrailingWhitespace() -> Self {
var newString = self
-
+
while newString.last?.isWhitespace == true {
newString = String(newString.dropLast())
}
-
+
return newString
}
-
- func removeLeadingWhitespace() -> Self {
+
+ public func removeLeadingWhitespace() -> Self {
var newString = self
-
+
while newString.first?.isWhitespace == true {
newString = String(newString.dropFirst())
}
-
+
return newString
}
-
- func removeLeadingTrailingWhitespace() -> Self {
+
+ public func removeLeadingTrailingWhitespace() -> Self {
var newString = self
-
+
newString = newString.removeLeadingWhitespace()
newString = newString.removeTrailingWhitespace()
-
+
return newString
}
-
- func escapeDoubleQuote() -> Self {
+
+ public func escapeDoubleQuote() -> Self {
replacingOccurrences(of: "\"", with: "\\\"")
}
-
- func replaceTiltWithHomeDirectoryPath() -> Self {
+
+ public func replaceTiltWithHomeDirectoryPath() -> Self {
// See NSString.expandingTildeInPath
+#if os(macOS)
replacingOccurrences(of: "~", with: "\(FileManager.default.homeDirectoryForCurrentUser.relativePath)")
+#else
+ fatalError("This command should run on Mac only")
+#endif
}
-
- func colorComponent() -> (alpha: String, red: String, green: String, blue: String) {
+
+ public func colorComponent() -> (alpha: String, red: String, green: String, blue: String) { // swiftlint:disable:this large_tuple
var alpha: String = "FF"
var red: String
var green: String
var blue: String
var colorClean = self
- .replacingOccurrences(of: "#", with: "")
- .replacingOccurrences(of: "0x", with: "")
+ .replacingOccurrences(of: "#", with: "")
+ .replacingOccurrences(of: "0x", with: "")
if colorClean.count == 8 {
alpha = String(colorClean.prefix(2))
colorClean = String(colorClean.dropFirst(2))
}
-
+
red = String(colorClean.prefix(2))
colorClean = String(colorClean.dropFirst(2))
green = String(colorClean.prefix(2))
@@ -85,12 +90,12 @@ public extension String {
blue = String(colorClean.prefix(2))
return (alpha: alpha, red: red, green: green, blue: blue)
}
-
- func uppercasedFirst() -> String {
+
+ public func uppercasedFirst() -> String {
prefix(1).uppercased() + dropFirst()
}
- func replacingFirstOccurrence(of: String, with: String) -> Self {
+ public func replacingFirstOccurrence(of: String, with: String) -> Self {
if let range = self.range(of: of) {
let tmp = self.replacingOccurrences(
of: of,
diff --git a/Sources/ToolCore/Version.swift b/Sources/ToolCore/Version.swift
index b850b78..3f9b249 100644
--- a/Sources/ToolCore/Version.swift
+++ b/Sources/ToolCore/Version.swift
@@ -7,4 +7,6 @@
import Foundation
-public let ResgenSwiftVersion = "1.2"
+// swiftlint:disable prefixed_toplevel_constant identifier_name
+
+public let ResgenSwiftVersion = "2.1.0"
diff --git a/Tests/ResgenSwiftTests/Analytics/AnalyticsDefinitionTests.swift b/Tests/ResgenSwiftTests/Analytics/AnalyticsDefinitionTests.swift
index 756f79e..4ed4bbe 100644
--- a/Tests/ResgenSwiftTests/Analytics/AnalyticsDefinitionTests.swift
+++ b/Tests/ResgenSwiftTests/Analytics/AnalyticsDefinitionTests.swift
@@ -84,7 +84,7 @@ final class AnalyticsDefinitionTests: XCTestCase {
name: "Ecran un",
action: "",
category: "",
- params: []
+ params: [:]
)
}
"""
@@ -127,7 +127,7 @@ final class AnalyticsDefinitionTests: XCTestCase {
name: "Ecran un",
action: "",
category: "",
- params: []
+ params: [:]
)
}
"""
diff --git a/Tests/ResgenSwiftTests/Analytics/AnalyticsGeneratorTests.swift b/Tests/ResgenSwiftTests/Analytics/AnalyticsGeneratorTests.swift
index 7abe69f..5b6e987 100644
--- a/Tests/ResgenSwiftTests/Analytics/AnalyticsGeneratorTests.swift
+++ b/Tests/ResgenSwiftTests/Analytics/AnalyticsGeneratorTests.swift
@@ -5,6 +5,8 @@
// Created by Thibaut Schmitt on 06/09/2022.
//
+// CPD-OFF
+
import Foundation
import XCTest
import ToolCore
@@ -51,20 +53,24 @@ final class AnalyticsGeneratorTests: XCTestCase {
]
// When
- AnalyticsGenerator.targets = [TrackerType.firebase]
- let extensionContent = AnalyticsGenerator.getExtensionContent(sections: [sectionOne, sectionTwo, sectionThree],
- tags: ["ios", "iosonly"],
- staticVar: false,
- extensionName: "GenAnalytics")
+ let extensionContent = AnalyticsGenerator.getExtensionContent(
+ targets: [TrackerType.firebase],
+ sections: [sectionOne, sectionTwo, sectionThree],
+ tags: ["ios", "iosonly"],
+ staticVar: false,
+ extensionName: "GenAnalytics"
+ )
+
// Expect Analytics
let expect = """
- // Generated by ResgenSwift.Analytics 1.2
+ // Generated by ResgenSwift.Analytics \(ResgenSwiftVersion)
- import Firebase
+ import FirebaseAnalytics
// MARK: - Protocol
protocol AnalyticsManagerProtocol {
+
func logScreen(name: String, path: String)
func logEvent(
name: String,
@@ -77,9 +83,10 @@ final class AnalyticsGeneratorTests: XCTestCase {
// MARK: - Firebase
class FirebaseAnalyticsManager: AnalyticsManagerProtocol {
+
func logScreen(name: String, path: String) {
var parameters = [
- AnalyticsParameterScreenName: name
+ AnalyticsParameterScreenName: name as NSObject
]
Analytics.logEvent(
@@ -94,19 +101,25 @@ final class AnalyticsGeneratorTests: XCTestCase {
category: String,
params: [String: Any]?
) {
- var parameters: [String:Any] = [
- "action": action,
- "category": category,
+ var parameters: [String:NSObject] = [
+ "action": action as NSObject,
+ "category": category as NSObject,
]
if let supplementaryParameters = params {
- parameters.merge(supplementaryParameters) { (origin, new) -> Any in
- return origin
+ for (newKey, newValue) in supplementaryParameters {
+ if parameters.contains(where: { (key: String, value: NSObject) in
+ key == newKey
+ }) {
+ continue
+ }
+
+ parameters[newKey] = newValue as? NSObject
}
}
-
+
Analytics.logEvent(
- name,
+ name.replacingOccurrences(of: [" "], with: "_"),
parameters: parameters
)
}
@@ -115,8 +128,9 @@ final class AnalyticsGeneratorTests: XCTestCase {
// MARK: - Manager
class AnalyticsManager {
+
static var shared = AnalyticsManager()
-
+
// MARK: - Properties
var managers: [AnalyticsManagerProtocol] = []
@@ -132,15 +146,15 @@ final class AnalyticsGeneratorTests: XCTestCase {
func configure() {
managers.append(FirebaseAnalyticsManager())
}
-
+
private func logScreen(name: String, path: String) {
guard isEnabled else { return }
-
+
managers.forEach { manager in
manager.logScreen(name: name, path: path)
}
}
-
+
private func logEvent(
name: String,
action: String,
@@ -148,7 +162,7 @@ final class AnalyticsGeneratorTests: XCTestCase {
params: [String: Any]?
) {
guard isEnabled else { return }
-
+
managers.forEach { manager in
manager.logEvent(
name: name,
@@ -173,7 +187,7 @@ final class AnalyticsGeneratorTests: XCTestCase {
name: "s1 def two",
action: "",
category: "",
- params: []
+ params: [:]
)
}
@@ -216,20 +230,23 @@ final class AnalyticsGeneratorTests: XCTestCase {
]
// When
- AnalyticsGenerator.targets = [TrackerType.matomo]
- let extensionContent = AnalyticsGenerator.getExtensionContent(sections: [sectionOne, sectionTwo, sectionThree],
- tags: ["ios", "iosonly"],
- staticVar: false,
- extensionName: "GenAnalytics")
+ let extensionContent = AnalyticsGenerator.getExtensionContent(
+ targets: [TrackerType.matomo],
+ sections: [sectionOne, sectionTwo, sectionThree],
+ tags: ["ios", "iosonly"],
+ staticVar: false,
+ extensionName: "GenAnalytics"
+ )
// Expect Analytics
let expect = """
- // Generated by ResgenSwift.Analytics 1.2
+ // Generated by ResgenSwift.Analytics \(ResgenSwiftVersion)
import MatomoTracker
// MARK: - Protocol
protocol AnalyticsManagerProtocol {
+
func logScreen(name: String, path: String)
func logEvent(
name: String,
@@ -242,7 +259,7 @@ final class AnalyticsGeneratorTests: XCTestCase {
// MARK: - Matomo
class MatomoAnalyticsManager: AnalyticsManagerProtocol {
-
+
// MARK: - Properties
private var tracker: MatomoTracker
@@ -256,11 +273,11 @@ final class AnalyticsGeneratorTests: XCTestCase {
siteId: siteId,
baseURL: URL(string: url)!
)
-
+
#if DEBUG
tracker.dispatchInterval = 5
#endif
-
+
#if DEBUG
tracker.logger = DefaultLogger(minLevel: .verbose)
#endif
@@ -268,7 +285,7 @@ final class AnalyticsGeneratorTests: XCTestCase {
debugPrint("[Matomo service] Configured with content base: \\(tracker.contentBase?.absoluteString ?? "-")")
debugPrint("[Matomo service] Opt out: \\(tracker.isOptedOut)")
}
-
+
// MARK: - Methods
func logScreen(name: String, path: String) {
@@ -289,7 +306,7 @@ final class AnalyticsGeneratorTests: XCTestCase {
params: [String: Any]?
) {
guard !tracker.isOptedOut else { return }
-
+
tracker.track(
eventWithCategory: category,
action: action,
@@ -303,8 +320,9 @@ final class AnalyticsGeneratorTests: XCTestCase {
// MARK: - Manager
class AnalyticsManager {
+
static var shared = AnalyticsManager()
-
+
// MARK: - Properties
var managers: [AnalyticsManagerProtocol] = []
@@ -325,15 +343,15 @@ final class AnalyticsGeneratorTests: XCTestCase {
)
)
}
-
+
private func logScreen(name: String, path: String) {
guard isEnabled else { return }
-
+
managers.forEach { manager in
manager.logScreen(name: name, path: path)
}
}
-
+
private func logEvent(
name: String,
action: String,
@@ -341,7 +359,7 @@ final class AnalyticsGeneratorTests: XCTestCase {
params: [String: Any]?
) {
guard isEnabled else { return }
-
+
managers.forEach { manager in
manager.logEvent(
name: name,
@@ -366,7 +384,7 @@ final class AnalyticsGeneratorTests: XCTestCase {
name: "s1 def two",
action: "test",
category: "test",
- params: []
+ params: [:]
)
}
@@ -379,7 +397,7 @@ final class AnalyticsGeneratorTests: XCTestCase {
)
}
}
-
+
"""
if extensionContent != expect {
@@ -409,21 +427,25 @@ final class AnalyticsGeneratorTests: XCTestCase {
]
// When
- AnalyticsGenerator.targets = [TrackerType.matomo, TrackerType.firebase]
- let extensionContent = AnalyticsGenerator.getExtensionContent(sections: [sectionOne, sectionTwo, sectionThree],
- tags: ["ios", "iosonly"],
- staticVar: false,
- extensionName: "GenAnalytics")
+ let extensionContent = AnalyticsGenerator.getExtensionContent(
+ targets: [TrackerType.matomo, TrackerType.firebase],
+ sections: [sectionOne, sectionTwo, sectionThree],
+ tags: ["ios", "iosonly"],
+ staticVar: false,
+ extensionName: "GenAnalytics"
+ )
+
// Expect Analytics
let expect = """
- // Generated by ResgenSwift.Analytics 1.2
+ // Generated by ResgenSwift.Analytics \(ResgenSwiftVersion)
import MatomoTracker
- import Firebase
+ import FirebaseAnalytics
// MARK: - Protocol
protocol AnalyticsManagerProtocol {
+
func logScreen(name: String, path: String)
func logEvent(
name: String,
@@ -436,7 +458,7 @@ final class AnalyticsGeneratorTests: XCTestCase {
// MARK: - Matomo
class MatomoAnalyticsManager: AnalyticsManagerProtocol {
-
+
// MARK: - Properties
private var tracker: MatomoTracker
@@ -450,11 +472,11 @@ final class AnalyticsGeneratorTests: XCTestCase {
siteId: siteId,
baseURL: URL(string: url)!
)
-
+
#if DEBUG
tracker.dispatchInterval = 5
#endif
-
+
#if DEBUG
tracker.logger = DefaultLogger(minLevel: .verbose)
#endif
@@ -462,13 +484,13 @@ final class AnalyticsGeneratorTests: XCTestCase {
debugPrint("[Matomo service] Configured with content base: \\(tracker.contentBase?.absoluteString ?? "-")")
debugPrint("[Matomo service] Opt out: \\(tracker.isOptedOut)")
}
-
+
// MARK: - Methods
func logScreen(name: String, path: String) {
guard !tracker.isOptedOut else { return }
guard let trackerUrl = tracker.contentBase?.absoluteString else { return }
-
+
let urlString = URL(string: "\\(trackerUrl)" + "/" + "\\(path)" + "iOS")
tracker.track(
view: [name],
@@ -483,7 +505,7 @@ final class AnalyticsGeneratorTests: XCTestCase {
params: [String: Any]?
) {
guard !tracker.isOptedOut else { return }
-
+
tracker.track(
eventWithCategory: category,
action: action,
@@ -493,13 +515,13 @@ final class AnalyticsGeneratorTests: XCTestCase {
)
}
}
-
+
// MARK: - Firebase
class FirebaseAnalyticsManager: AnalyticsManagerProtocol {
func logScreen(name: String, path: String) {
var parameters = [
- AnalyticsParameterScreenName: name
+ AnalyticsParameterScreenName: name as NSObject
]
Analytics.logEvent(
@@ -514,19 +536,25 @@ final class AnalyticsGeneratorTests: XCTestCase {
category: String,
params: [String: Any]?
) {
- var parameters: [String:Any] = [
- "action": action,
- "category": category,
+ var parameters: [String:NSObject] = [
+ "action": action as NSObject,
+ "category": category as NSObject,
]
if let supplementaryParameters = params {
- parameters.merge(supplementaryParameters) { (origin, new) -> Any in
- return origin
+ for (newKey, newValue) in supplementaryParameters {
+ if parameters.contains(where: { (key: String, value: NSObject) in
+ key == newKey
+ }) {
+ continue
+ }
+
+ parameters[newKey] = newValue as? NSObject
}
}
-
+
Analytics.logEvent(
- name,
+ name.replacingOccurrences(of: [" "], with: "_"),
parameters: parameters
)
}
@@ -535,8 +563,9 @@ final class AnalyticsGeneratorTests: XCTestCase {
// MARK: - Manager
class AnalyticsManager {
+
static var shared = AnalyticsManager()
-
+
// MARK: - Properties
var managers: [AnalyticsManagerProtocol] = []
@@ -558,7 +587,7 @@ final class AnalyticsGeneratorTests: XCTestCase {
)
managers.append(FirebaseAnalyticsManager())
}
-
+
private func logScreen(name: String, path: String) {
guard isEnabled else { return }
@@ -566,7 +595,7 @@ final class AnalyticsGeneratorTests: XCTestCase {
manager.logScreen(name: name, path: path)
}
}
-
+
private func logEvent(
name: String,
action: String,
@@ -574,7 +603,7 @@ final class AnalyticsGeneratorTests: XCTestCase {
params: [String: Any]?
) {
guard isEnabled else { return }
-
+
managers.forEach { manager in
manager.logEvent(
name: name,
@@ -599,7 +628,7 @@ final class AnalyticsGeneratorTests: XCTestCase {
name: "s1 def two",
action: "test",
category: "test",
- params: []
+ params: [:]
)
}
@@ -612,7 +641,7 @@ final class AnalyticsGeneratorTests: XCTestCase {
)
}
}
-
+
"""
if extensionContent != expect {
@@ -621,3 +650,5 @@ final class AnalyticsGeneratorTests: XCTestCase {
XCTAssertEqual(extensionContent.adaptForXCTest(), expect.adaptForXCTest())
}
}
+
+// CPD-ON
diff --git a/Tests/ResgenSwiftTests/Fonts/FontExtensionGeneratorTests.swift b/Tests/ResgenSwiftTests/Fonts/FontExtensionGeneratorTests.swift
index 2093f09..5c2ec98 100644
--- a/Tests/ResgenSwiftTests/Fonts/FontExtensionGeneratorTests.swift
+++ b/Tests/ResgenSwiftTests/Fonts/FontExtensionGeneratorTests.swift
@@ -16,8 +16,8 @@ final class FontExtensionGeneratorTests: XCTestCase {
func test_uiKit_GeneratedExtensionContent() {
// Given
let fontNames: [FontName] = [
- "CircularStd-Regular",
- "CircularStd-Bold"
+ FontName(postscriptName: "CircularStd-Regular", filename: "CircularStd-Regular", fileExtension: "ttf"),
+ FontName(postscriptName: "CircularStd-Bold", filename: "CircularStd-Bold", fileExtension: "ttf")
]
// When
@@ -59,8 +59,8 @@ final class FontExtensionGeneratorTests: XCTestCase {
func test_swiftUI_GeneratedExtensionContent() {
// Given
let fontNames: [FontName] = [
- "CircularStd-Regular",
- "CircularStd-Bold"
+ FontName(postscriptName: "CircularStd-Regular", filename: "CircularStd-Regular", fileExtension: "ttf"),
+ FontName(postscriptName: "CircularStd-Bold", filename: "CircularStd-Bold", fileExtension: "ttf")
]
// When
diff --git a/Tests/ResgenSwiftTests/Fonts/FontNameTests.swift b/Tests/ResgenSwiftTests/Fonts/FontNameTests.swift
index 80f3883..0acc48c 100644
--- a/Tests/ResgenSwiftTests/Fonts/FontNameTests.swift
+++ b/Tests/ResgenSwiftTests/Fonts/FontNameTests.swift
@@ -14,8 +14,12 @@ final class FontNameTests: XCTestCase {
func test_uiKit_GeneratedProperty_noForbiddenCharacter() {
// Given
- let fontName: FontName = "CircularStdBold"
-
+ let fontName = FontName(
+ postscriptName: "CircularStdBold",
+ filename: "CircularStd-Bold",
+ fileExtension: "ttf"
+ )
+
// When
let property = fontName.getProperty(isStatic: true, isSwiftUI: false)
@@ -31,7 +35,11 @@ final class FontNameTests: XCTestCase {
func test_uiKit_GeneratedProperty_withForbiddenCharacter() {
// Given
- let fontName: FontName = "[Circular_Std+Bold-Underline]"
+ let fontName = FontName(
+ postscriptName: "[Circular_Std+Bold-Underline]",
+ filename: "Circular_Std+Bold-Underline",
+ fileExtension: "ttf"
+ )
// When
let property = fontName.getProperty(isStatic: true, isSwiftUI: false)
@@ -48,8 +56,12 @@ final class FontNameTests: XCTestCase {
func test_uiKit_GeneratedMethod_noForbiddenCharacter() {
// Given
- let fontName: FontName = "CircularStdBold"
-
+ let fontName = FontName(
+ postscriptName: "CircularStdBold",
+ filename: "CircularStd-Bold",
+ fileExtension: "ttf"
+ )
+
// When
let property = fontName.getProperty(isStatic: false, isSwiftUI: false)
@@ -65,8 +77,12 @@ final class FontNameTests: XCTestCase {
func test_uiKit_GeneratedMethod_withForbiddenCharacter() {
// Given
- let fontName: FontName = "[Circular_Std+Bold-Underline]"
-
+ let fontName = FontName(
+ postscriptName: "[Circular_Std+Bold-Underline]",
+ filename: "Circular_Std+Bold-Underline",
+ fileExtension: "ttf"
+ )
+
// When
let property = fontName.getProperty(isStatic: false, isSwiftUI: false)
@@ -82,8 +98,12 @@ final class FontNameTests: XCTestCase {
func test_swiftUI_GeneratedProperty_noForbiddenCharacter() {
// Given
- let fontName: FontName = "CircularStdBold"
-
+ let fontName = FontName(
+ postscriptName: "CircularStdBold",
+ filename: "CircularStd-Bold",
+ fileExtension: "ttf"
+ )
+
// When
let property = fontName.getProperty(isStatic: true, isSwiftUI: true)
@@ -99,8 +119,12 @@ final class FontNameTests: XCTestCase {
func test_swiftUI_GeneratedProperty_withForbiddenCharacter() {
// Given
- let fontName: FontName = "[Circular_Std+Bold-Underline]"
-
+ let fontName = FontName(
+ postscriptName: "[Circular_Std+Bold-Underline]",
+ filename: "Circular_Std+Bold-Underline",
+ fileExtension: "ttf"
+ )
+
// When
let property = fontName.getProperty(isStatic: true, isSwiftUI: true)
@@ -116,8 +140,12 @@ final class FontNameTests: XCTestCase {
func test_swiftUI_GeneratedMethod_noForbiddenCharacter() {
// Given
- let fontName: FontName = "CircularStdBold"
-
+ let fontName = FontName(
+ postscriptName: "CircularStdBold",
+ filename: "CircularStd-Bold",
+ fileExtension: "ttf"
+ )
+
// When
let property = fontName.getProperty(isStatic: false, isSwiftUI: true)
@@ -133,8 +161,12 @@ final class FontNameTests: XCTestCase {
func test_swiftUI_GeneratedMethod_withForbiddenCharacter() {
// Given
- let fontName: FontName = "[Circular_Std+Bold-Underline]"
-
+ let fontName = FontName(
+ postscriptName: "[Circular_Std+Bold-Underline]",
+ filename: "Circular_Std+Bold-Underline",
+ fileExtension: "ttf"
+ )
+
// When
let property = fontName.getProperty(isStatic: false, isSwiftUI: true)
diff --git a/Tests/ResgenSwiftTests/Fonts/FontPlistGeneratorTests.swift b/Tests/ResgenSwiftTests/Fonts/FontPlistGeneratorTests.swift
index 8c86ca5..a09236a 100644
--- a/Tests/ResgenSwiftTests/Fonts/FontPlistGeneratorTests.swift
+++ b/Tests/ResgenSwiftTests/Fonts/FontPlistGeneratorTests.swift
@@ -14,8 +14,8 @@ final class FontPlistGeneratorTests: XCTestCase {
func testGeneratedPlist() {
// Given
let fontNames: [FontName] = [
- "CircularStd-Regular",
- "CircularStd-Bold"
+ FontName(postscriptName: "CircularStd-Regular", filename: "CircularStd-Regular", fileExtension: "ttf"),
+ FontName(postscriptName: "CircularStd-Bold", filename: "CircularStd-Bold", fileExtension: "ttf")
]
// When
@@ -25,8 +25,8 @@ final class FontPlistGeneratorTests: XCTestCase {
let expect = """
UIAppFonts
- CircularStd-Regular
- CircularStd-Bold
+ CircularStd-Regular.ttf
+ CircularStd-Bold.ttf
"""
diff --git a/Tests/ResgenSwiftTests/ResgenSwiftTests.swift b/Tests/ResgenSwiftTests/ResgenSwiftTests.swift
index 508d37b..54e4dcc 100644
--- a/Tests/ResgenSwiftTests/ResgenSwiftTests.swift
+++ b/Tests/ResgenSwiftTests/ResgenSwiftTests.swift
@@ -14,9 +14,8 @@ final class ResgenCLITests: XCTestCase {
return
}
- // Mac Catalyst won't have `Process`, but it is supported for executables.
- #if !targetEnvironment(macCatalyst)
-
+ // Process available on Mac only
+ #if os(macOS)
let fooBinary = productsDirectory.appendingPathComponent("ResgenSwift")
let process = Process()
diff --git a/Tests/ResgenSwiftTests/Strings/DefinitionTests.swift b/Tests/ResgenSwiftTests/Strings/DefinitionTests.swift
index c25b3ff..bed6795 100644
--- a/Tests/ResgenSwiftTests/Strings/DefinitionTests.swift
+++ b/Tests/ResgenSwiftTests/Strings/DefinitionTests.swift
@@ -5,6 +5,8 @@
// Created by Thibaut Schmitt on 06/09/2022.
//
+// CPD-OFF
+
import Foundation
import XCTest
@@ -872,3 +874,5 @@ final class DefinitionTests: XCTestCase {
XCTAssertEqual(propertyEnUs.adaptForXCTest(), expectEnUs.adaptForXCTest())
}
}
+
+// CPD-ON
diff --git a/Tests/ResgenSwiftTests/Strings/StringFileGeneratorTests/Expectation/StringsFileGenerator+OldStringsFile.swift b/Tests/ResgenSwiftTests/Strings/StringFileGeneratorTests/Expectation/StringsFileGenerator+OldStringsFile.swift
new file mode 100644
index 0000000..524c2e6
--- /dev/null
+++ b/Tests/ResgenSwiftTests/Strings/StringFileGeneratorTests/Expectation/StringsFileGenerator+OldStringsFile.swift
@@ -0,0 +1,51 @@
+//
+// StringsFileGenerator+OldStringsFile.swift
+// ResgenSwift
+//
+// Created by Thibaut Schmitt on 30/04/2025.
+//
+
+import ToolCore
+
+extension StringsFileGeneratorTests {
+
+ static let appleStringsFileExpectationFr = """
+ /**
+ * Apple Strings File
+ * Generated by ResgenSwift \(ResgenSwiftVersion)
+ * Language: fr
+ */
+
+ /********** section_one **********/
+
+ "s1_def_one" = "Section Un - Definition Un";
+
+ "s1_def_two" = "Section Un - Definition Deux";
+
+ /********** section_two **********/
+
+ "s2_def_one" = "Section Deux - Definition Un";
+
+ "s2_def_two" = "Section Deux - Definition Deux";
+ """
+
+ static let appleStringsFileExpectationEn = """
+ /**
+ * Apple Strings File
+ * Generated by ResgenSwift \(ResgenSwiftVersion)
+ * Language: en
+ */
+
+ /********** section_one **********/
+
+ "s1_def_one" = "Section One - Definition One";
+
+ "s1_def_two" = "Section One - Definition Two";
+
+ /********** section_two **********/
+
+ "s2_def_one" = "Section Two - Definition One";
+
+ "s2_def_two" = "Section Deux - Definition Deux";
+ """
+}
diff --git a/Tests/ResgenSwiftTests/Strings/StringFileGeneratorTests/Expectation/StringsFileGenerator+R2ExtensionsExpectation.swift b/Tests/ResgenSwiftTests/Strings/StringFileGeneratorTests/Expectation/StringsFileGenerator+R2ExtensionsExpectation.swift
new file mode 100644
index 0000000..54b5bcf
--- /dev/null
+++ b/Tests/ResgenSwiftTests/Strings/StringFileGeneratorTests/Expectation/StringsFileGenerator+R2ExtensionsExpectation.swift
@@ -0,0 +1,91 @@
+//
+// StringsFileGenerator+R2ExtensionsExpectation.swift
+// ResgenSwift
+//
+// Created by Thibaut Schmitt on 30/04/2025.
+//
+
+@testable import ResgenSwift
+import ToolCore
+
+extension StringsFileGeneratorTests {
+
+ static func getExtensionContentExpectation(
+ staticVar: Bool,
+ s1DefOneFr: String = "Section Un - Definition Un",
+ s1DefOneComment: String = "",
+ s1DefTwoFr: String = "Section Un - Definition Deux",
+ s1DefTwoComment: String = "",
+ s2DefOneFr: String = "Section Deux - Definition Un",
+ s2DefOneComment: String = "",
+ s2DefTwoFr: String = "Section Deux - Definition Deux",
+ s2DefTwoComment: String = "",
+ ) -> String {
+ """
+ // Generated by ResgenSwift.Strings.Stringium \(ResgenSwiftVersion)
+
+ import UIKit
+
+ fileprivate let kStringsFileName = "myInputFilename"
+
+ extension GenStrings {
+
+ enum KeyStrings: String {
+ case s1_def_one = "s1_def_one"
+ case s1_def_two = "s1_def_two"
+ case s2_def_one = "s2_def_one"
+ case s2_def_two = "s2_def_two"
+
+ var keyPath: KeyPath {
+ switch self {
+ case .s1_def_one: return \\GenStrings.s1_def_one
+ case .s1_def_two: return \\GenStrings.s1_def_two
+ case .s2_def_one: return \\GenStrings.s2_def_one
+ case .s2_def_two: return \\GenStrings.s2_def_two
+ }
+ }
+ }
+
+ // MARK: - section_one
+
+ /// Translation in fr :
+ /// \(s1DefOneFr)
+ ///
+ /// Comment :
+ /// \(s1DefOneComment.isEmpty ? "No comment" : s1DefOneComment)
+ \(staticVar ? "static " : "")var s1_def_one: String {
+ NSLocalizedString("s1_def_one", tableName: kStringsFileName, bundle: Bundle.main, value: "Section Un - Definition Un", comment: "\(s1DefOneComment)")
+ }
+
+ /// Translation in fr :
+ /// \(s1DefTwoFr)
+ ///
+ /// Comment :
+ /// \(s1DefTwoComment.isEmpty ? "No comment" : s1DefTwoComment)
+ \(staticVar ? "static " : "")var s1_def_two: String {
+ NSLocalizedString("s1_def_two", tableName: kStringsFileName, bundle: Bundle.main, value: "Section Un - Definition Deux", comment: "\(s1DefTwoComment)")
+ }
+
+ // MARK: - section_two
+
+ /// Translation in fr :
+ /// \(s2DefOneFr)
+ ///
+ /// Comment :
+ /// \(s2DefOneComment.isEmpty ? "No comment" : s2DefOneComment)
+ \(staticVar ? "static " : "")var s2_def_one: String {
+ NSLocalizedString("s2_def_one", tableName: kStringsFileName, bundle: Bundle.main, value: "Section Deux - Definition Un", comment: "\(s2DefOneComment)")
+ }
+
+ /// Translation in fr :
+ /// \(s2DefTwoFr)
+ ///
+ /// Comment :
+ /// \(s2DefTwoComment.isEmpty ? "No comment" : s2DefTwoComment)
+ \(staticVar ? "static " : "")var s2_def_two: String {
+ NSLocalizedString("s2_def_two", tableName: kStringsFileName, bundle: Bundle.main, value: "Section Deux - Definition Deux", comment: "\(s2DefTwoComment)")
+ }
+ }
+ """
+ }
+}
diff --git a/Tests/ResgenSwiftTests/Strings/StringFileGeneratorTests/Expectation/StringsFileGenerator+XCStringsExpectation.swift b/Tests/ResgenSwiftTests/Strings/StringFileGeneratorTests/Expectation/StringsFileGenerator+XCStringsExpectation.swift
new file mode 100644
index 0000000..ede3a59
--- /dev/null
+++ b/Tests/ResgenSwiftTests/Strings/StringFileGeneratorTests/Expectation/StringsFileGenerator+XCStringsExpectation.swift
@@ -0,0 +1,129 @@
+//
+// StringsFileGenerator+XCStringsExpectation.swift
+// ResgenSwift
+//
+// Created by Thibaut Schmitt on 30/04/2025.
+//
+
+@testable import ResgenSwift
+
+extension XCStringDefinition {
+
+ enum Mock {
+
+ static func getDefinitionSectionOne(
+ defOneFr: String = "Section Un - Definition Un",
+ defOneEn: String = "Section One - Definition One",
+ defOneComment: String? = nil,
+ defTwoFr: String = "Section Un - Definition Deux",
+ defTwoEn: String = "Section One - Definition Two",
+ defTwoComment: String? = nil
+ ) -> [XCStringDefinition] {
+ [
+ XCStringDefinition(
+ title: "s1_def_one",
+ content: XCStringDefinitionContent(
+ comment: defOneComment,
+ extractionState: "manual",
+ localizations: XCStringLocalizationContainer(
+ localizations: getXCStringLocalization(
+ defFr: defOneFr,
+ defEn: defOneEn
+ )
+ )
+ )
+ ),
+ XCStringDefinition(
+ title: "s1_def_two",
+ content: XCStringDefinitionContent(
+ comment: defTwoComment,
+ extractionState: "manual",
+ localizations: XCStringLocalizationContainer(
+ localizations: getXCStringLocalization(
+ defFr: defTwoFr,
+ defEn: defTwoEn
+ )
+ )
+ )
+ )
+ ]
+ }
+
+ static func getDefinitionSectionTwo(
+ defOneFr: String = "Section Deux - Definition Un",
+ defOneEn: String = "Section Two - Definition One",
+ defOneComment: String? = nil,
+ defTwoFr: String = "Section Deux - Definition Deux",
+ defTwoEn: String? = nil,
+ defTwoComment: String? = nil
+ ) -> [XCStringDefinition] {
+ [
+ XCStringDefinition(
+ title: "s2_def_one",
+ content: XCStringDefinitionContent(
+ comment: defOneComment,
+ extractionState: "manual",
+ localizations: XCStringLocalizationContainer(
+ localizations: getXCStringLocalization(
+ defFr: defOneFr,
+ defEn: defOneEn
+ )
+ )
+ )
+ ),
+ XCStringDefinition(
+ title: "s2_def_two",
+ content: XCStringDefinitionContent(
+ comment: defTwoComment,
+ extractionState: "manual",
+ localizations: XCStringLocalizationContainer(
+ localizations: getXCStringLocalization(
+ defFr: defTwoFr,
+ defEn: defTwoEn ?? ""
+ )
+ )
+ )
+ )
+ ]
+ }
+
+ // MARK: - Private methods
+
+ private static func getXCStringLocalization(
+ defFr: String,
+ defEn: String
+ ) -> [XCStringLocalization] {
+ var localizations = [XCStringLocalization]()
+
+ if defFr.isEmpty == false {
+ localizations.append(
+ XCStringLocalization(
+ lang: "fr",
+ content: XCStringLocalizationLangContent(
+ stringUnit: DefaultStringUnit(
+ state: "translated",
+ value: defFr
+ )
+ )
+ )
+ )
+ }
+
+ if defEn.isEmpty == false {
+ localizations.append(
+ XCStringLocalization(
+ lang: "en",
+ content: XCStringLocalizationLangContent(
+ stringUnit: DefaultStringUnit(
+ state: "translated",
+ value: defEn
+ )
+ )
+ )
+ )
+ }
+
+ return localizations
+ }
+ }
+}
diff --git a/Tests/ResgenSwiftTests/Strings/StringFileGeneratorTests/Mock/Section+Mock.swift b/Tests/ResgenSwiftTests/Strings/StringFileGeneratorTests/Mock/Section+Mock.swift
new file mode 100644
index 0000000..417b3d3
--- /dev/null
+++ b/Tests/ResgenSwiftTests/Strings/StringFileGeneratorTests/Mock/Section+Mock.swift
@@ -0,0 +1,95 @@
+//
+// Section+Mock.swift
+// ResgenSwift
+//
+// Created by Thibaut Schmitt on 30/04/2025.
+//
+
+@testable import ResgenSwift
+
+extension Section {
+
+ enum Mock {
+
+ static func getSectionOne(
+ defOneFr: String = "Section Un - Definition Un",
+ defOneEn: String = "Section One - Definition One",
+ defOneComment: String? = nil,
+ defOneTag: [String] = ["ios","iosonly"],
+ defTwoFr: String = "Section Un - Definition Deux",
+ defTwoEn: String = "Section One - Definition Two",
+ defTwoComment: String? = nil,
+ defTwoTag: [String] = ["ios","iosonly"]
+ ) -> Section {
+ let section = Section(name: "section_one")
+ section.definitions = [
+ Self.getDefinition(
+ name: "s1_def_one",
+ translations: ["fr": defOneFr,
+ "en": defOneEn],
+ tags: defOneTag,
+ comment: defOneComment
+ ),
+ Self.getDefinition(
+ name: "s1_def_two",
+ translations: ["fr": defTwoFr,
+ "en": defTwoEn],
+ tags: defTwoTag,
+ comment: defTwoComment
+ )
+ ]
+ return section
+ }
+
+ static func getSectionTwo(
+ defOneFr: String = "Section Deux - Definition Un",
+ defOneEn: String = "Section Two - Definition One",
+ defOneComment: String? = nil,
+ defOneTag: [String] = ["ios","iosonly"],
+ defTwoFr: String = "Section Deux - Definition Deux",
+ defTwoEn: String? = nil,
+ defTwoComment: String? = nil,
+ defTwoTag: [String] = ["notranslation"]
+ ) -> Section {
+ let section = Section(name: "section_two")
+ let defTwoTranslations: [String: String] = {
+ var translations = ["fr": "Section Deux - Definition Deux"]
+ if let defTwoEn {
+ translations["en"] = defTwoEn
+ }
+ return translations
+ }()
+ section.definitions = [
+ Self.getDefinition(
+ name: "s2_def_one",
+ translations: ["fr": defOneFr,
+ "en": defOneEn],
+ tags: defOneTag,
+ comment: defOneComment
+ ),
+ Self.getDefinition(
+ name: "s2_def_two",
+ translations: defTwoTranslations,
+ tags: defTwoTag,
+ comment: defTwoComment
+ )
+ ]
+ return section
+ }
+
+ // MARK: - Private methods
+
+ private static func getDefinition(
+ name: String,
+ translations: [String: String],
+ tags: [String],
+ comment: String? = nil
+ ) -> Definition {
+ let definition = Definition(name: name)
+ definition.tags = tags
+ definition.translations = translations
+ definition.comment = comment
+ return definition
+ }
+ }
+}
diff --git a/Tests/ResgenSwiftTests/Strings/StringFileGeneratorTests/StringsFileGeneratorTests.swift b/Tests/ResgenSwiftTests/Strings/StringFileGeneratorTests/StringsFileGeneratorTests.swift
new file mode 100644
index 0000000..0efb6d9
--- /dev/null
+++ b/Tests/ResgenSwiftTests/Strings/StringFileGeneratorTests/StringsFileGeneratorTests.swift
@@ -0,0 +1,491 @@
+//
+// StringsFileGeneratorTests.swift
+//
+//
+// Created by Thibaut Schmitt on 06/09/2022.
+//
+
+import Foundation
+import XCTest
+import ToolCore
+
+@testable import ResgenSwift
+
+final class StringsFileGeneratorTests: XCTestCase {
+
+ // MARK: - Strings File Content
+
+ func testGenerateStringsFileContent() {
+ // Given
+ let sectionOne = Section.Mock.getSectionOne()
+ let sectionTwo = Section.Mock.getSectionTwo()
+
+ // When - CPD-OFF
+ let stringsFileContentFr = StringsFileGenerator.generateStringsFileContent(
+ lang: "fr",
+ defaultLang: "fr",
+ tags: ["ios", "iosonly", "notranslation"],
+ sections: [sectionOne, sectionTwo]
+ )
+ let stringsFileContentEn = StringsFileGenerator.generateStringsFileContent(
+ lang: "en",
+ defaultLang: "fr",
+ tags: ["ios", "iosonly", "notranslation"],
+ sections: [sectionOne, sectionTwo]
+ )
+ // CPD-ON
+
+ // Expect
+ XCTAssertEqual(
+ stringsFileContentFr.adaptForXCTest(),
+ Self.appleStringsFileExpectationFr.adaptForXCTest()
+ )
+ XCTAssertEqual(
+ stringsFileContentEn.adaptForXCTest(),
+ Self.appleStringsFileExpectationEn.adaptForXCTest()
+ )
+ }
+
+ func testGenerateStringsFileContentWithComment() {
+ // Given
+ let sectionOne = Section.Mock.getSectionOne(
+ defOneComment: "This is a comment",
+ defTwoComment: "This is a comment"
+ )
+ let sectionTwo = Section.Mock.getSectionTwo(
+ defOneComment: "This is a comment",
+ defTwoComment: "This is a comment"
+ )
+
+ // When - CPD-OFF
+ let stringsFileContentFr = StringsFileGenerator.generateStringsFileContent(
+ lang: "fr",
+ defaultLang: "fr",
+ tags: ["ios", "iosonly", "notranslation"],
+ sections: [sectionOne, sectionTwo]
+ )
+ let stringsFileContentEn = StringsFileGenerator.generateStringsFileContent(
+ lang: "en",
+ defaultLang: "fr",
+ tags: ["ios", "iosonly", "notranslation"],
+ sections: [sectionOne, sectionTwo]
+ )
+ // CPD-ON
+
+ // Expect
+ XCTAssertEqual(
+ stringsFileContentFr.adaptForXCTest(),
+ Self.appleStringsFileExpectationFr.adaptForXCTest()
+ )
+ XCTAssertEqual(
+ stringsFileContentEn.adaptForXCTest(),
+ Self.appleStringsFileExpectationEn.adaptForXCTest()
+ )
+ }
+
+ // MARK: - XcString File Content
+
+ func testGenerateXcStringsRootObject() {
+ // Given
+ let sectionOne = Section.Mock.getSectionOne()
+ let sectionTwo = Section.Mock.getSectionTwo(
+ defTwoEn: "Section Two - Definition Two"
+ )
+
+ // When
+ let rootObject = StringsFileGenerator.generateRootObject(
+ langs: ["fr", "en"],
+ defaultLang: "en",
+ tags: ["ios", "iosonly", "notranslation"],
+ sections: [sectionOne, sectionTwo]
+ )
+
+ // Expect
+ let expect =
+ Root(
+ sourceLanguage: "en",
+ strings: XCStringDefinitionContainer(
+ strings: [
+ XCStringDefinition(
+ title: "s1_def_one",
+ content: XCStringDefinitionContent(
+ extractionState: "manual",
+ localizations: XCStringLocalizationContainer(
+ localizations: [
+ XCStringLocalization(
+ lang: "en",
+ content: XCStringLocalizationLangContent(
+ stringUnit: DefaultStringUnit(
+ state: "translated",
+ value: "Section One - Definition One"
+ )
+ )
+ ),
+ XCStringLocalization(
+ lang: "fr",
+ content: XCStringLocalizationLangContent(
+ stringUnit: DefaultStringUnit(
+ state: "translated",
+ value: "Section Un - Definition Un"
+ )
+ )
+ )
+ ]
+ )
+ )
+ ),
+ XCStringDefinition(
+ title: "s1_def_two",
+ content: XCStringDefinitionContent(
+ extractionState: "manual",
+ localizations: XCStringLocalizationContainer(
+ localizations: [
+ XCStringLocalization(
+ lang: "en",
+ content: XCStringLocalizationLangContent(
+ stringUnit: DefaultStringUnit(
+ state: "translated",
+ value: "Section One - Definition Two"
+ )
+ )
+ ),
+ XCStringLocalization(
+ lang: "fr",
+ content: XCStringLocalizationLangContent(
+ stringUnit: DefaultStringUnit(
+ state: "translated",
+ value: "Section Un - Definition Deux"
+ )
+ )
+ )
+ ]
+ )
+ )
+ ),
+ XCStringDefinition(
+ title: "s2_def_one",
+ content: XCStringDefinitionContent(
+ extractionState: "manual",
+ localizations: XCStringLocalizationContainer(
+ localizations: [
+ XCStringLocalization(
+ lang: "en",
+ content: XCStringLocalizationLangContent(
+ stringUnit: DefaultStringUnit(
+ state: "translated",
+ value: "Section Two - Definition One"
+ )
+ )
+ ),
+ XCStringLocalization(
+ lang: "fr",
+ content: XCStringLocalizationLangContent(
+ stringUnit: DefaultStringUnit(
+ state: "translated",
+ value: "Section Deux - Definition Un"
+ )
+ )
+ )
+ ]
+ )
+ )
+ ),
+ XCStringDefinition(
+ title: "s2_def_two",
+ content: XCStringDefinitionContent(
+ extractionState: "manual",
+ localizations: XCStringLocalizationContainer(
+ localizations: [
+ XCStringLocalization(
+ lang: "en",
+ content: XCStringLocalizationLangContent(
+ stringUnit: DefaultStringUnit(
+ state: "translated",
+ value: "Section Two - Definition Two"
+ )
+ )
+ ),
+ XCStringLocalization(
+ lang: "fr",
+ content: XCStringLocalizationLangContent(
+ stringUnit: DefaultStringUnit(
+ state: "translated",
+ value: "Section Two - Definition Two"
+ )
+ )
+ )
+ ]
+ )
+ )
+ )
+ ]
+ ),
+ version: "1.0"
+ )
+
+ XCTAssertEqual(rootObject, expect)
+ }
+
+ func testGenerateXcStringsRootObjectWithEmptyTranslations() {
+ // Given
+ let sectionOne = Section.Mock.getSectionOne(
+ defOneFr: "",
+ defTwoFr: ""
+ )
+ let sectionTwo = Section.Mock.getSectionTwo(
+ defOneFr: "",
+ defTwoFr: "",
+ defTwoEn: "Section Two - Definition Two"
+ )
+ // By default Section2.s2_def_two has a fr value => remove it
+ sectionTwo.definitions
+ .first { def in
+ def.name == "s2_def_two"
+ }?
+ .translations.removeValue(forKey: "fr")
+
+ // When
+ let rootObject = StringsFileGenerator.generateRootObject(
+ langs: ["fr", "en"],
+ defaultLang: "en",
+ tags: ["ios", "iosonly", "notranslation"],
+ sections: [sectionOne, sectionTwo]
+ )
+
+ // Expect
+ let expect =
+ Root(
+ sourceLanguage: "en",
+ strings: XCStringDefinitionContainer(
+ strings: [
+ XCStringDefinition.Mock.getDefinitionSectionOne(
+ defOneFr: "",
+ defTwoFr: "",
+ ),
+ XCStringDefinition.Mock.getDefinitionSectionTwo(
+ defOneFr: "",
+ defTwoFr: "Section Two - Definition Two",
+ defTwoEn: "Section Two - Definition Two",
+ )
+ ]
+ .flatMap { $0 }
+ ),
+ version: "1.0"
+ )
+
+ XCTAssertEqual(rootObject, expect)
+ }
+
+ func testGenerateXcStringsRootObjectWithNoTranslations() {
+ // Given
+ let sectionOne = Section.Mock.getSectionOne(
+ defOneFr: "",
+ defOneEn: "",
+ defTwoFr: "",
+ defTwoEn : ""
+ )
+ let sectionTwo = Section.Mock.getSectionTwo(
+ defOneFr: "",
+ defOneEn: "",
+ defTwoFr: "",
+ defTwoEn : ""
+ )
+
+ // When
+ let rootObject = StringsFileGenerator.generateRootObject(
+ langs: ["fr", "en"],
+ defaultLang: "en",
+ tags: ["ios", "iosonly", "notranslation"],
+ sections: [sectionOne, sectionTwo]
+ )
+
+ // Expect
+ let expect =
+ Root(
+ sourceLanguage: "en",
+ strings: XCStringDefinitionContainer(
+ strings: [
+ XCStringDefinition.Mock.getDefinitionSectionOne(
+ defOneFr: "",
+ defOneEn: "",
+ defTwoFr: "",
+ defTwoEn : ""
+ ),
+ XCStringDefinition.Mock.getDefinitionSectionTwo(
+ defOneFr: "",
+ defOneEn: "",
+ defTwoFr: "",
+ defTwoEn : ""
+ )
+ ]
+ .flatMap { $0 }
+ ),
+ version: "1.0"
+ )
+
+ XCTAssertEqual(rootObject, expect)
+ }
+
+ func testGenerateXcStringsRootObjectWithComments() {
+ // Given
+ let sectionOne = Section.Mock.getSectionOne(
+ defOneComment: "Comment 1",
+ defTwoComment: "Comment 2"
+ )
+ let sectionTwo = Section.Mock.getSectionTwo()
+
+ // When
+ let rootObject = StringsFileGenerator.generateRootObject(
+ langs: ["fr", "en"],
+ defaultLang: "fr",
+ tags: ["ios", "iosonly", "notranslation"],
+ sections: [sectionOne, sectionTwo]
+ )
+
+ // Expect
+ let expect =
+ Root(
+ sourceLanguage: "fr",
+ strings: XCStringDefinitionContainer(
+ strings: [
+ XCStringDefinition.Mock.getDefinitionSectionOne(
+ defOneComment: "Comment 1",
+ defTwoComment: "Comment 2"
+ ),
+ XCStringDefinition.Mock.getDefinitionSectionTwo(
+ // SourceLanguage is frn en definition should the same as fr
+ defTwoEn: "Section Deux - Definition Deux"
+ )
+ ]
+ .flatMap { $0 }
+ ),
+ version: "1.0"
+ )
+
+ XCTAssertEqual(rootObject, expect)
+ }
+
+ // MARK: - Extension Content
+
+ func testGeneratedExtensionContent() {
+ // Given
+ let sectionOne = Section.Mock.getSectionOne()
+ let sectionTwo = Section.Mock.getSectionTwo()
+
+ // When
+ let extensionContent = StringsFileGenerator.getExtensionContent(sections: [sectionOne, sectionTwo],
+ defaultLang: "fr",
+ tags: ["ios", "iosonly", "notranslation"],
+ staticVar: false,
+ inputFilename: "myInputFilename",
+ extensionName: "GenStrings",
+ extensionSuffix: "strings")
+
+ // Expect
+ let expect = Self.getExtensionContentExpectation(
+ staticVar: false
+ )
+
+ if extensionContent != expect {
+ print(prettyFirstDifferenceBetweenStrings(s1: extensionContent, s2: expect))
+ }
+ XCTAssertEqual(extensionContent.adaptForXCTest(), expect.adaptForXCTest())
+ }
+
+ func testGeneratedExtensionContentWithComment() {
+ // Given
+ let sectionOne = Section.Mock.getSectionOne(
+ defOneComment: "This is a comment",
+ defTwoComment: "This is a comment"
+ )
+ let sectionTwo = Section.Mock.getSectionTwo(
+ defOneComment: "This is a comment",
+ defTwoComment: "This is a comment"
+ )
+
+ // When
+ let extensionContent = StringsFileGenerator.getExtensionContent(sections: [sectionOne, sectionTwo],
+ defaultLang: "fr",
+ tags: ["ios", "iosonly", "notranslation"],
+ staticVar: false,
+ inputFilename: "myInputFilename",
+ extensionName: "GenStrings",
+ extensionSuffix: "strings")
+
+ // Expect
+ let expect = Self.getExtensionContentExpectation(
+ staticVar: false,
+ s1DefOneComment: "This is a comment",
+ s1DefTwoComment: "This is a comment",
+ s2DefOneComment: "This is a comment",
+ s2DefTwoComment: "This is a comment",
+ )
+
+ if extensionContent != expect {
+ print(prettyFirstDifferenceBetweenStrings(s1: extensionContent, s2: expect))
+ }
+ XCTAssertEqual(extensionContent.adaptForXCTest(), expect.adaptForXCTest())
+ }
+
+ // MARK: - Extension Content Static
+ func testGeneratedExtensionContentWithStaticVar() {
+ // Given
+ let sectionOne = Section.Mock.getSectionOne()
+ let sectionTwo = Section.Mock.getSectionTwo()
+
+ // When
+ let extensionContent = StringsFileGenerator.getExtensionContent(sections: [sectionOne, sectionTwo],
+ defaultLang: "fr",
+ tags: ["ios", "iosonly", "notranslation"],
+ staticVar: true,
+ inputFilename: "myInputFilename",
+ extensionName: "GenStrings",
+ extensionSuffix: "strings")
+
+ // Expect
+ let expect = Self.getExtensionContentExpectation(
+ staticVar: true
+ )
+
+ if extensionContent != expect {
+ print(prettyFirstDifferenceBetweenStrings(s1: extensionContent, s2: expect))
+ }
+ XCTAssertEqual(extensionContent.adaptForXCTest(), expect.adaptForXCTest())
+ }
+
+ func testGeneratedExtensionContentWithStaticVarWithComment() {
+ // Given
+ let sectionOne = Section.Mock.getSectionOne(
+ defOneComment: "This is a comment",
+ defTwoComment: "This is a comment"
+ )
+ let sectionTwo = Section.Mock.getSectionTwo(
+ defOneComment: "This is a comment",
+ defTwoComment: "This is a comment"
+ )
+
+ // When
+ let extensionContent = StringsFileGenerator.getExtensionContent(sections: [sectionOne, sectionTwo],
+ defaultLang: "fr",
+ tags: ["ios", "iosonly", "notranslation"],
+ staticVar: true,
+ inputFilename: "myInputFilename",
+ extensionName: "GenStrings",
+ extensionSuffix: "strings")
+
+ // Expect
+ let expect = Self.getExtensionContentExpectation(
+ staticVar: true,
+ s1DefOneComment: "This is a comment",
+ s1DefTwoComment: "This is a comment",
+ s2DefOneComment: "This is a comment",
+ s2DefTwoComment: "This is a comment",
+ )
+
+ if extensionContent != expect {
+ print(prettyFirstDifferenceBetweenStrings(s1: extensionContent, s2: expect))
+ }
+ XCTAssertEqual(extensionContent.adaptForXCTest(), expect.adaptForXCTest())
+ }
+}
+
diff --git a/Tests/ResgenSwiftTests/Strings/StringsFileGeneratorTests.swift b/Tests/ResgenSwiftTests/Strings/StringsFileGeneratorTests.swift
deleted file mode 100644
index dec6627..0000000
--- a/Tests/ResgenSwiftTests/Strings/StringsFileGeneratorTests.swift
+++ /dev/null
@@ -1,1348 +0,0 @@
-//
-// StringsFileGeneratorTests.swift
-//
-//
-// Created by Thibaut Schmitt on 06/09/2022.
-//
-
-import Foundation
-import XCTest
-import ToolCore
-
-@testable import ResgenSwift
-
-final class StringsFileGeneratorTests: XCTestCase {
-
- private func getDefinition(name: String, translations: [String: String], tags: [String], comment: String? = nil) -> Definition {
- let definition = Definition(name: name)
- definition.tags = tags
- definition.translations = translations
- definition.comment = comment
- return definition
- }
-
- // MARK: - Strings File Content
-
- func testGenerateStringsFileContent() {
- // Given
- let sectionOne = Section(name: "section_one")
- sectionOne.definitions = [
- getDefinition(name: "s1_def_one",
- translations: ["fr": "Section Un - Definition Un",
- "en": "Section One - Definition One"],
- tags: ["ios","iosonly"]),
- getDefinition(name: "s1_def_two",
- translations: ["fr": "Section Un - Definition Deux",
- "en": "Section One - Definition Two"],
- tags: ["ios","iosonly"])
- ]
-
- let sectionTwo = Section(name: "section_two")
- sectionTwo.definitions = [
- getDefinition(name: "s2_def_one",
- translations: ["fr": "Section Deux - Definition Un",
- "en": "Section Two - Definition One"],
- tags: ["ios","iosonly"]),
- getDefinition(name: "s2_def_two",
- translations: ["fr": "Section Deux - Definition Deux"],
- tags: ["notranslation"])
- ]
-
- // When
- let stringsFileContentFr = StringsFileGenerator.generateStringsFileContent(lang: "fr",
- defaultLang: "fr",
- tags: ["ios", "iosonly", "notranslation"],
- sections: [sectionOne, sectionTwo])
- let stringsFileContentEn = StringsFileGenerator.generateStringsFileContent(lang: "en",
- defaultLang: "fr",
- tags: ["ios", "iosonly", "notranslation"],
- sections: [sectionOne, sectionTwo])
-
- // Expect
- let expectFr = """
- /**
- * Apple Strings File
- * Generated by ResgenSwift \(ResgenSwiftVersion)
- * Language: fr
- */
-
- /********** section_one **********/
-
- "s1_def_one" = "Section Un - Definition Un";
-
- "s1_def_two" = "Section Un - Definition Deux";
-
- /********** section_two **********/
-
- "s2_def_one" = "Section Deux - Definition Un";
-
- "s2_def_two" = "Section Deux - Definition Deux";
- """
-
- let expectEn = """
- /**
- * Apple Strings File
- * Generated by ResgenSwift \(ResgenSwiftVersion)
- * Language: en
- */
-
- /********** section_one **********/
-
- "s1_def_one" = "Section One - Definition One";
-
- "s1_def_two" = "Section One - Definition Two";
-
- /********** section_two **********/
-
- "s2_def_one" = "Section Two - Definition One";
-
- "s2_def_two" = "Section Deux - Definition Deux";
- """
-
- XCTAssertEqual(stringsFileContentFr.adaptForXCTest(), expectFr.adaptForXCTest())
- XCTAssertEqual(stringsFileContentEn.adaptForXCTest(), expectEn.adaptForXCTest())
- }
-
- func testGenerateStringsFileContentWithComment() {
- // Given
- let sectionOne = Section(name: "section_one")
- sectionOne.definitions = [
- getDefinition(name: "s1_def_one",
- translations: ["fr": "Section Un - Definition Un",
- "en": "Section One - Definition One"],
- tags: ["ios","iosonly"],
- comment: "This is a comment"),
- getDefinition(name: "s1_def_two",
- translations: ["fr": "Section Un - Definition Deux",
- "en": "Section One - Definition Two"],
- tags: ["ios","iosonly"],
- comment: "This is a comment")
- ]
-
- let sectionTwo = Section(name: "section_two")
- sectionTwo.definitions = [
- getDefinition(name: "s2_def_one",
- translations: ["fr": "Section Deux - Definition Un",
- "en": "Section Two - Definition One"],
- tags: ["ios","iosonly"],
- comment: "This is a comment"),
- getDefinition(name: "s2_def_two",
- translations: ["fr": "Section Deux - Definition Deux"],
- tags: ["notranslation"],
- comment: "This is a comment")
- ]
-
- // When
- let stringsFileContentFr = StringsFileGenerator.generateStringsFileContent(lang: "fr",
- defaultLang: "fr",
- tags: ["ios", "iosonly", "notranslation"],
- sections: [sectionOne, sectionTwo])
- let stringsFileContentEn = StringsFileGenerator.generateStringsFileContent(lang: "en",
- defaultLang: "fr",
- tags: ["ios", "iosonly", "notranslation"],
- sections: [sectionOne, sectionTwo])
-
- // Expect
- let expectFr = """
- /**
- * Apple Strings File
- * Generated by ResgenSwift \(ResgenSwiftVersion)
- * Language: fr
- */
-
- /********** section_one **********/
-
- "s1_def_one" = "Section Un - Definition Un";
-
- "s1_def_two" = "Section Un - Definition Deux";
-
- /********** section_two **********/
-
- "s2_def_one" = "Section Deux - Definition Un";
-
- "s2_def_two" = "Section Deux - Definition Deux";
- """
-
- let expectEn = """
- /**
- * Apple Strings File
- * Generated by ResgenSwift \(ResgenSwiftVersion)
- * Language: en
- */
-
- /********** section_one **********/
-
- "s1_def_one" = "Section One - Definition One";
-
- "s1_def_two" = "Section One - Definition Two";
-
- /********** section_two **********/
-
- "s2_def_one" = "Section Two - Definition One";
-
- "s2_def_two" = "Section Deux - Definition Deux";
- """
-
- XCTAssertEqual(stringsFileContentFr.adaptForXCTest(), expectFr.adaptForXCTest())
- XCTAssertEqual(stringsFileContentEn.adaptForXCTest(), expectEn.adaptForXCTest())
- }
-
- // MARK: - XcString File Content
-
- func testGenerateXcStringsRootObject() {
- // Given
-
- // [[section_one]]
- // [s1_def_one]
- // fr = Section Un - Definition Un
- // en = Section One - Definition One
- // tags = ios,iosonly
- // comments =
- // [s1_def_two]
- // fr = Section Un - Definition Deux
- // en = Section One - Definition Two
- // tags = ios,iosonly
- // comments =
- //
- // [[section_two]
- // [s2_def_one]
- // fr = Section Deux - Definition Un
- // en = Section Two - Definition One
- // tags = ios,iosonly
- // comments =
- // [s2_def_two]
- // fr = Section Deux - Definition deux
- // en = Section Two - Definition Two
- // tags = notranslation
- // comments =
-
-
- let sectionOne = Section(name: "section_one")
- sectionOne.definitions = [
- getDefinition(name: "s1_def_one",
- translations: ["fr": "Section Un - Definition Un",
- "en": "Section One - Definition One"],
- tags: ["ios","iosonly"]),
- getDefinition(name: "s1_def_two",
- translations: ["fr": "Section Un - Definition Deux",
- "en": "Section One - Definition Two"],
- tags: ["ios","iosonly"])
- ]
-
- let sectionTwo = Section(name: "section_two")
- sectionTwo.definitions = [
- getDefinition(name: "s2_def_one",
- translations: ["fr": "Section Deux - Definition Un",
- "en": "Section Two - Definition One"],
- tags: ["ios","iosonly"]),
- getDefinition(name: "s2_def_two",
- translations: ["fr": "Section Deux - Definition Deux",
- "en": "Section Two - Definition Two"],
- tags: ["notranslation"])
- ]
-
- // When
- let rootObject = StringsFileGenerator.generateRootObject(
- langs: ["fr", "en"],
- defaultLang: "en",
- tags: ["ios", "iosonly", "notranslation"],
- sections: [sectionOne, sectionTwo]
- )
-
- // Expect
- let expect =
- Root(
- sourceLanguage: "en",
- strings: XCStringDefinitionContainer(
- strings: [
- XCStringDefinition(
- title: "s1_def_one",
- content: XCStringDefinitionContent(
- extractionState: "manual",
- localizations: XCStringLocalizationContainer(
- localizations: [
- XCStringLocalization(
- lang: "en",
- content: XCStringLocalizationLangContent(
- stringUnit: DefaultStringUnit(
- state: "translated",
- value: "Section One - Definition One"
- )
- )
- ),
- XCStringLocalization(
- lang: "fr",
- content: XCStringLocalizationLangContent(
- stringUnit: DefaultStringUnit(
- state: "translated",
- value: "Section Un - Definition Un"
- )
- )
- )
- ]
- )
- )
- ),
- XCStringDefinition(
- title: "s1_def_two",
- content: XCStringDefinitionContent(
- extractionState: "manual",
- localizations: XCStringLocalizationContainer(
- localizations: [
- XCStringLocalization(
- lang: "en",
- content: XCStringLocalizationLangContent(
- stringUnit: DefaultStringUnit(
- state: "translated",
- value: "Section One - Definition Two"
- )
- )
- ),
- XCStringLocalization(
- lang: "fr",
- content: XCStringLocalizationLangContent(
- stringUnit: DefaultStringUnit(
- state: "translated",
- value: "Section Un - Definition Deux"
- )
- )
- )
- ]
- )
- )
- ),
- XCStringDefinition(
- title: "s2_def_one",
- content: XCStringDefinitionContent(
- extractionState: "manual",
- localizations: XCStringLocalizationContainer(
- localizations: [
- XCStringLocalization(
- lang: "en",
- content: XCStringLocalizationLangContent(
- stringUnit: DefaultStringUnit(
- state: "translated",
- value: "Section Two - Definition One"
- )
- )
- ),
- XCStringLocalization(
- lang: "fr",
- content: XCStringLocalizationLangContent(
- stringUnit: DefaultStringUnit(
- state: "translated",
- value: "Section Deux - Definition Un"
- )
- )
- )
- ]
- )
- )
- ),
- XCStringDefinition(
- title: "s2_def_two",
- content: XCStringDefinitionContent(
- extractionState: "manual",
- localizations: XCStringLocalizationContainer(
- localizations: [
- XCStringLocalization(
- lang: "en",
- content: XCStringLocalizationLangContent(
- stringUnit: DefaultStringUnit(
- state: "translated",
- value: "Section Two - Definition Two"
- )
- )
- ),
- XCStringLocalization(
- lang: "fr",
- content: XCStringLocalizationLangContent(
- stringUnit: DefaultStringUnit(
- state: "translated",
- value: "Section Two - Definition Two"
- )
- )
- )
- ]
- )
- )
- )
- ]
- ),
- version: "1.0"
- )
-
- XCTAssertEqual(rootObject, expect)
- }
-
- func testGenerateXcStringsRootObjectWithEmptyTranslations() {
- // Given
- let sectionOne = Section(name: "section_one")
- sectionOne.definitions = [
- getDefinition(name: "s1_def_one",
- translations: ["fr": "",
- "en": "Section One - Definition One"],
- tags: ["ios","iosonly"]),
- getDefinition(name: "s1_def_two",
- translations: ["fr": "",
- "en": "Section One - Definition Two"],
- tags: ["ios","iosonly"])
- ]
-
- let sectionTwo = Section(name: "section_two")
- sectionTwo.definitions = [
- getDefinition(name: "s2_def_one",
- translations: ["fr": "",
- "en": "Section Two - Definition One"],
- tags: ["ios","iosonly"]),
- getDefinition(name: "s2_def_two",
- translations: ["en": "Section Two - Definition Two"],
- tags: ["notranslation"])
- ]
-
- // When
- let rootObject = StringsFileGenerator.generateRootObject(
- langs: ["fr", "en"],
- defaultLang: "en",
- tags: ["ios", "iosonly", "notranslation"],
- sections: [sectionOne, sectionTwo]
- )
-
- // [[section_one]]
- // [s1_def_one]
- // fr =
- // en = Section One - Definition One
- // tags = ios,iosonly
- // comments =
- // [s1_def_two]
- // fr =
- // en = Section One - Definition Two
- // tags = ios,iosonly
- // comments =
- //
- // [[section_two]
- // [s2_def_one]
- // fr =
- // en = Section Two - Definition One
- // tags = ios,iosonly
- // comments =
- // [s2_def_two]
- // en = Section Two - Definition Two
- // tags = notranslation
- // comments =
-
- // Expect
- let expect =
- Root(
- sourceLanguage: "en",
- strings: XCStringDefinitionContainer(
- strings: [
- XCStringDefinition(
- title: "s1_def_one",
- content: XCStringDefinitionContent(
- extractionState: "manual",
- localizations: XCStringLocalizationContainer(
- localizations: [
- XCStringLocalization(
- lang: "en",
- content: XCStringLocalizationLangContent(
- stringUnit: DefaultStringUnit(
- state: "translated",
- value: "Section One - Definition One"
- )
- )
- )
- ]
- )
- )
- ),
- XCStringDefinition(
- title: "s1_def_two",
- content: XCStringDefinitionContent(
- extractionState: "manual",
- localizations: XCStringLocalizationContainer(
- localizations: [
- XCStringLocalization(
- lang: "en",
- content: XCStringLocalizationLangContent(
- stringUnit: DefaultStringUnit(
- state: "translated",
- value: "Section One - Definition Two"
- )
- )
- )
- ]
- )
- )
- ),
- XCStringDefinition(
- title: "s2_def_one",
- content: XCStringDefinitionContent(
- extractionState: "manual",
- localizations: XCStringLocalizationContainer(
- localizations: [
- XCStringLocalization(
- lang: "en",
- content: XCStringLocalizationLangContent(
- stringUnit: DefaultStringUnit(
- state: "translated",
- value: "Section Two - Definition One"
- )
- )
- )
- ]
- )
- )
- ),
- XCStringDefinition(
- title: "s2_def_two",
- content: XCStringDefinitionContent(
- extractionState: "manual",
- localizations: XCStringLocalizationContainer(
- localizations: [
- XCStringLocalization(
- lang: "en",
- content: XCStringLocalizationLangContent(
- stringUnit: DefaultStringUnit(
- state: "translated",
- value: "Section Two - Definition Two"
- )
- )
- ),
- XCStringLocalization(
- lang: "fr",
- content: XCStringLocalizationLangContent(
- stringUnit: DefaultStringUnit(
- state: "translated",
- value: "Section Two - Definition Two"
- )
- )
- )
- ]
- )
- )
- )
- ]
- ),
- version: "1.0"
- )
-
- XCTAssertEqual(rootObject, expect)
- }
-
- func testGenerateXcStringsRootObjectWithNoTranslations() {
- // Given
- let sectionOne = Section(name: "section_one")
- sectionOne.definitions = [
- getDefinition(name: "s1_def_one",
- translations: ["fr": "",
- "en": ""],
- tags: ["ios","iosonly"]),
- getDefinition(name: "s1_def_two",
- translations: ["fr": "",
- "en": ""],
- tags: ["ios","iosonly"])
- ]
-
- let sectionTwo = Section(name: "section_two")
- sectionTwo.definitions = [
- getDefinition(name: "s2_def_one",
- translations: ["fr": "",
- "en": ""],
- tags: ["ios","iosonly"]),
- getDefinition(name: "s2_def_two",
- translations: ["fr": "",
- "en": ""],
- tags: ["notranslation"])
- ]
-
- // When
- let rootObject = StringsFileGenerator.generateRootObject(
- langs: ["fr", "en"],
- defaultLang: "en",
- tags: ["ios", "iosonly", "notranslation"],
- sections: [sectionOne, sectionTwo]
- )
-
- // [[section_one]]
- // [s1_def_one]
- // fr =
- // en =
- // tags = ios,iosonly
- // comments =
- // [s1_def_two]
- // fr =
- // en =
- // tags = ios,iosonly
- // comments =
- //
- // [[section_two]
- // [s2_def_one]
- // fr =
- // en =
- // tags = ios,iosonly
- // comments =
- // [s2_def_two]
- // fr =
- // en =
- // tags = ios,iosonly
- // comments =
-
- // Expect
- let expect =
- Root(
- sourceLanguage: "en",
- strings: XCStringDefinitionContainer(
- strings: [
- XCStringDefinition(
- title: "s1_def_one",
- content: XCStringDefinitionContent(
- extractionState: "manual",
- localizations: XCStringLocalizationContainer(
- localizations: []
- )
- )
- ),
- XCStringDefinition(
- title: "s1_def_two",
- content: XCStringDefinitionContent(
- extractionState: "manual",
- localizations: XCStringLocalizationContainer(
- localizations: []
- )
- )
- ),
- XCStringDefinition(
- title: "s2_def_one",
- content: XCStringDefinitionContent(
- extractionState: "manual",
- localizations: XCStringLocalizationContainer(
- localizations: []
- )
- )
- ),
- XCStringDefinition(
- title: "s2_def_two",
- content: XCStringDefinitionContent(
- extractionState: "manual",
- localizations: XCStringLocalizationContainer(
- localizations: []
- )
- )
- )
- ]
- ),
- version: "1.0"
- )
-
- XCTAssertEqual(rootObject, expect)
- }
-
- func testGenerateXcStringsRootObjectWithComments() {
- // Given
-
- // [[section_one]]
- // [s1_def_one]
- // fr = Section Un - Definition Un
- // en = Section One - Definition One
- // tags = ios,iosonly
- // comments = Comment 1
- // [s1_def_two]
- // fr = Section Un - Definition Deux
- // en = Section One - Definition Two
- // tags = ios,iosonly
- // comments = Comment 2
- //
- // [[section_two]
- // [s2_def_one]
- // fr = Section Deux - Definition Un
- // en = Section Two - Definition One
- // tags = ios,iosonly
- // comments =
- // [s2_def_two]
- // fr = Section Deux - Definition deux
- // en = Section Two - Definition Two
- // tags = notranslation
- // comments =
-
-
- let sectionOne = Section(name: "section_one")
- sectionOne.definitions = [
- getDefinition(name: "s1_def_one",
- translations: ["fr": "Section Un - Definition Un",
- "en": "Section One - Definition One"],
- tags: ["ios","iosonly"],
- comment: "Comment 1"),
- getDefinition(name: "s1_def_two",
- translations: ["fr": "Section Un - Definition Deux",
- "en": "Section One - Definition Two"],
- tags: ["ios","iosonly"],
- comment: "Comment 2")
- ]
-
- let sectionTwo = Section(name: "section_two")
- sectionTwo.definitions = [
- getDefinition(name: "s2_def_one",
- translations: ["fr": "Section Deux - Definition Un",
- "en": "Section Two - Definition One"],
- tags: ["ios","iosonly"]),
- getDefinition(name: "s2_def_two",
- translations: ["fr": "Section Deux - Definition Deux"],
- tags: ["notranslation"])
- ]
-
- // When
- let rootObject = StringsFileGenerator.generateRootObject(
- langs: ["fr", "en"],
- defaultLang: "fr",
- tags: ["ios", "iosonly", "notranslation"],
- sections: [sectionOne, sectionTwo]
- )
-
- // Expect
- let expect =
- Root(
- sourceLanguage: "fr",
- strings: XCStringDefinitionContainer(
- strings: [
- XCStringDefinition(
- title: "s1_def_one",
- content: XCStringDefinitionContent(
- comment: "Comment 1",
- extractionState: "manual",
- localizations: XCStringLocalizationContainer(
- localizations: [
- XCStringLocalization(
- lang: "en",
- content: XCStringLocalizationLangContent(
- stringUnit: DefaultStringUnit(
- state: "translated",
- value: "Section One - Definition One"
- )
- )
- ),
- XCStringLocalization(
- lang: "fr",
- content: XCStringLocalizationLangContent(
- stringUnit: DefaultStringUnit(
- state: "translated",
- value: "Section Un - Definition Un"
- )
- )
- )
- ]
- )
- )
- ),
- XCStringDefinition(
- title: "s1_def_two",
- content: XCStringDefinitionContent(
- comment: "Comment 2",
- extractionState: "manual",
- localizations: XCStringLocalizationContainer(
- localizations: [
- XCStringLocalization(
- lang: "en",
- content: XCStringLocalizationLangContent(
- stringUnit: DefaultStringUnit(
- state: "translated",
- value: "Section One - Definition Two"
- )
- )
- ),
- XCStringLocalization(
- lang: "fr",
- content: XCStringLocalizationLangContent(
- stringUnit: DefaultStringUnit(
- state: "translated",
- value: "Section Un - Definition Deux"
- )
- )
- )
- ]
- )
- )
- ),
- XCStringDefinition(
- title: "s2_def_one",
- content: XCStringDefinitionContent(
- extractionState: "manual",
- localizations: XCStringLocalizationContainer(
- localizations: [
- XCStringLocalization(
- lang: "en",
- content: XCStringLocalizationLangContent(
- stringUnit: DefaultStringUnit(
- state: "translated",
- value: "Section Two - Definition One"
- )
- )
- ),
- XCStringLocalization(
- lang: "fr",
- content: XCStringLocalizationLangContent(
- stringUnit: DefaultStringUnit(
- state: "translated",
- value: "Section Deux - Definition Un"
- )
- )
- )
- ]
- )
- )
- ),
- XCStringDefinition(
- title: "s2_def_two",
- content: XCStringDefinitionContent(
- extractionState: "manual",
- localizations: XCStringLocalizationContainer(
- localizations: [
- XCStringLocalization(
- lang: "en",
- content: XCStringLocalizationLangContent(
- stringUnit: DefaultStringUnit(
- state: "translated",
- value: "Section Deux - Definition Deux"
- )
- )
- ),
- XCStringLocalization(
- lang: "fr",
- content: XCStringLocalizationLangContent(
- stringUnit: DefaultStringUnit(
- state: "translated",
- value: "Section Deux - Definition Deux"
- )
- )
- )
- ]
- )
- )
- )
- ]
- ),
- version: "1.0"
- )
-
- // """
- // {
- // "sourceLanguage" : "en",
- // "strings" : {
- // "s1_def_one" : {
- // "extractionState" : "manual",
- // "localizations" : {
- // "en" : {
- // "stringUnit" : {
- // "state" : "translated",
- // "value" : "Section One - Definition One"
- // }
- // },
- // "fr" : {
- // "stringUnit" : {
- // "state" : "translated",
- // "value" : "Section Un - Definition Un"
- // }
- // }
- // }
- // },
- // "s1_def_two" : {
- // "extractionState" : "manual",
- // "localizations" : {
- // "en" : {
- // "stringUnit" : {
- // "state" : "translated",
- // "value" : "Section One - Definition Two"
- // }
- // },
- // "fr" : {
- // "stringUnit" : {
- // "state" : "translated",
- // "value" : "Section Un - Definition Deux"
- // }
- // }
- // }
- // },
- // "s2_def_one" : {
- // "extractionState" : "manual",
- // "localizations" : {
- // "en" : {
- // "stringUnit" : {
- // "state" : "translated",
- // "value" : "Section Two - Definition One"
- // }
- // },
- // "fr" : {
- // "stringUnit" : {
- // "state" : "translated",
- // "value" : "Section Deux - Definition Une"
- // }
- // }
- // }
- // },
- // "s2_def_two" : {
- // "extractionState" : "manual",
- // "localizations" : {
- // "en" : {
- // "stringUnit" : {
- // "state" : "translated",
- // "value" : "Section Two - Definition Two"
- // }
- // },
- // "fr" : {
- // "stringUnit" : {
- // "state" : "translated",
- // "value" : "Section Deux - Definition Deux"
- // }
- // }
- // }
- // }
- // },
- // "version" : "1.0"
- // }
- // """
- XCTAssertEqual(rootObject, expect)
- }
-
- // MARK: - Extension Content
- func testGeneratedExtensionContent() {
- // Given
- let sectionOne = Section(name: "section_one")
- sectionOne.definitions = [
- getDefinition(name: "s1_def_one",
- translations: ["fr": "Section Un - Definition Un",
- "en": "Section One - Definition One"],
- tags: ["ios","iosonly"]),
- getDefinition(name: "s1_def_two",
- translations: ["fr": "Section Un - Definition Deux",
- "en": "Section One - Definition Two"],
- tags: ["ios","iosonly"])
- ]
-
- let sectionTwo = Section(name: "section_two")
- sectionTwo.definitions = [
- getDefinition(name: "s2_def_one",
- translations: ["fr": "Section Deux - Definition Un",
- "en": "Section Two - Definition One"],
- tags: ["ios","iosonly"]),
- getDefinition(name: "s2_def_two",
- translations: ["fr": "Section Deux - Definition Deux"],
- tags: ["notranslation"])
- ]
-
- // When
- let extensionContent = StringsFileGenerator.getExtensionContent(sections: [sectionOne, sectionTwo],
- defaultLang: "fr",
- tags: ["ios", "iosonly", "notranslation"],
- staticVar: false,
- inputFilename: "myInputFilename",
- extensionName: "GenStrings",
- extensionSuffix: "strings")
-
- // Expect
- let expect = """
- // Generated by ResgenSwift.Strings.Stringium \(ResgenSwiftVersion)
-
- import UIKit
-
- fileprivate let kStringsFileName = "myInputFilename"
-
- extension GenStrings {
-
- enum KeyStrings: String {
- case s1_def_one = "s1_def_one"
- case s1_def_two = "s1_def_two"
- case s2_def_one = "s2_def_one"
- case s2_def_two = "s2_def_two"
-
- var keyPath: KeyPath {
- switch self {
- case .s1_def_one: return \\GenStrings.s1_def_one
- case .s1_def_two: return \\GenStrings.s1_def_two
- case .s2_def_one: return \\GenStrings.s2_def_one
- case .s2_def_two: return \\GenStrings.s2_def_two
- }
- }
- }
-
- // MARK: - section_one
-
- /// Translation in fr :
- /// Section Un - Definition Un
- ///
- /// Comment :
- /// No comment
- var s1_def_one: String {
- NSLocalizedString("s1_def_one", tableName: kStringsFileName, bundle: Bundle.main, value: "Section Un - Definition Un", comment: "")
- }
-
- /// Translation in fr :
- /// Section Un - Definition Deux
- ///
- /// Comment :
- /// No comment
- var s1_def_two: String {
- NSLocalizedString("s1_def_two", tableName: kStringsFileName, bundle: Bundle.main, value: "Section Un - Definition Deux", comment: "")
- }
-
- // MARK: - section_two
-
- /// Translation in fr :
- /// Section Deux - Definition Un
- ///
- /// Comment :
- /// No comment
- var s2_def_one: String {
- NSLocalizedString("s2_def_one", tableName: kStringsFileName, bundle: Bundle.main, value: "Section Deux - Definition Un", comment: "")
- }
-
- /// Translation in fr :
- /// Section Deux - Definition Deux
- ///
- /// Comment :
- /// No comment
- var s2_def_two: String {
- NSLocalizedString("s2_def_two", tableName: kStringsFileName, bundle: Bundle.main, value: "Section Deux - Definition Deux", comment: "")
- }
- }
- """
-
- if extensionContent != expect {
- print(prettyFirstDifferenceBetweenStrings(s1: extensionContent, s2: expect))
- }
- XCTAssertEqual(extensionContent.adaptForXCTest(), expect.adaptForXCTest())
- }
-
- func testGeneratedExtensionContentWithComment() {
- // Given
- let sectionOne = Section(name: "section_one")
- sectionOne.definitions = [
- getDefinition(name: "s1_def_one",
- translations: ["fr": "Section Un - Definition Un",
- "en": "Section One - Definition One"],
- tags: ["ios","iosonly"],
- comment: "This is a comment"),
- getDefinition(name: "s1_def_two",
- translations: ["fr": "Section Un - Definition Deux",
- "en": "Section One - Definition Two"],
- tags: ["ios","iosonly"],
- comment: "This is a comment")
- ]
-
- let sectionTwo = Section(name: "section_two")
- sectionTwo.definitions = [
- getDefinition(name: "s2_def_one",
- translations: ["fr": "Section Deux - Definition Un",
- "en": "Section Two - Definition One"],
- tags: ["ios","iosonly"],
- comment: "This is a comment"),
- getDefinition(name: "s2_def_two",
- translations: ["fr": "Section Deux - Definition Deux"],
- tags: ["notranslation"],
- comment: "This is a comment")
- ]
-
- // When
- let extensionContent = StringsFileGenerator.getExtensionContent(sections: [sectionOne, sectionTwo],
- defaultLang: "fr",
- tags: ["ios", "iosonly", "notranslation"],
- staticVar: false,
- inputFilename: "myInputFilename",
- extensionName: "GenStrings",
- extensionSuffix: "strings")
-
- // Expect
- let expect = """
- // Generated by ResgenSwift.Strings.Stringium \(ResgenSwiftVersion)
-
- import UIKit
-
- fileprivate let kStringsFileName = "myInputFilename"
-
- extension GenStrings {
-
- enum KeyStrings: String {
- case s1_def_one = "s1_def_one"
- case s1_def_two = "s1_def_two"
- case s2_def_one = "s2_def_one"
- case s2_def_two = "s2_def_two"
-
- var keyPath: KeyPath {
- switch self {
- case .s1_def_one: return \\GenStrings.s1_def_one
- case .s1_def_two: return \\GenStrings.s1_def_two
- case .s2_def_one: return \\GenStrings.s2_def_one
- case .s2_def_two: return \\GenStrings.s2_def_two
- }
- }
- }
-
- // MARK: - section_one
-
- /// Translation in fr :
- /// Section Un - Definition Un
- ///
- /// Comment :
- /// This is a comment
- var s1_def_one: String {
- NSLocalizedString("s1_def_one", tableName: kStringsFileName, bundle: Bundle.main, value: "Section Un - Definition Un", comment: "This is a comment")
- }
-
- /// Translation in fr :
- /// Section Un - Definition Deux
- ///
- /// Comment :
- /// This is a comment
- var s1_def_two: String {
- NSLocalizedString("s1_def_two", tableName: kStringsFileName, bundle: Bundle.main, value: "Section Un - Definition Deux", comment: "This is a comment")
- }
-
- // MARK: - section_two
-
- /// Translation in fr :
- /// Section Deux - Definition Un
- ///
- /// Comment :
- /// This is a comment
- var s2_def_one: String {
- NSLocalizedString("s2_def_one", tableName: kStringsFileName, bundle: Bundle.main, value: "Section Deux - Definition Un", comment: "This is a comment")
- }
-
- /// Translation in fr :
- /// Section Deux - Definition Deux
- ///
- /// Comment :
- /// This is a comment
- var s2_def_two: String {
- NSLocalizedString("s2_def_two", tableName: kStringsFileName, bundle: Bundle.main, value: "Section Deux - Definition Deux", comment: "This is a comment")
- }
- }
- """
-
- if extensionContent != expect {
- print(prettyFirstDifferenceBetweenStrings(s1: extensionContent, s2: expect))
- }
- XCTAssertEqual(extensionContent.adaptForXCTest(), expect.adaptForXCTest())
- }
-
- // MARK: - Extension Content Static
- func testGeneratedExtensionContentWithStaticVar() {
- // Given
- let sectionOne = Section(name: "section_one")
- sectionOne.definitions = [
- getDefinition(name: "s1_def_one",
- translations: ["fr": "Section Un - Definition Un",
- "en": "Section One - Definition One"],
- tags: ["ios","iosonly"]),
- getDefinition(name: "s1_def_two",
- translations: ["fr": "Section Un - Definition Deux",
- "en": "Section One - Definition Two"],
- tags: ["ios","iosonly"])
- ]
-
- let sectionTwo = Section(name: "section_two")
- sectionTwo.definitions = [
- getDefinition(name: "s2_def_one",
- translations: ["fr": "Section Deux - Definition Un",
- "en": "Section Two - Definition One"],
- tags: ["ios","iosonly"]),
- getDefinition(name: "s2_def_two",
- translations: ["fr": "Section Deux - Definition Deux"],
- tags: ["notranslation"])
- ]
-
- // When
- let extensionContent = StringsFileGenerator.getExtensionContent(sections: [sectionOne, sectionTwo],
- defaultLang: "fr",
- tags: ["ios", "iosonly", "notranslation"],
- staticVar: true,
- inputFilename: "myInputFilename",
- extensionName: "GenStrings",
- extensionSuffix: "strings")
-
- // Expect
- let expect = """
- // Generated by ResgenSwift.Strings.Stringium \(ResgenSwiftVersion)
-
- import UIKit
-
- fileprivate let kStringsFileName = "myInputFilename"
-
- extension GenStrings {
-
- enum KeyStrings: String {
- case s1_def_one = "s1_def_one"
- case s1_def_two = "s1_def_two"
- case s2_def_one = "s2_def_one"
- case s2_def_two = "s2_def_two"
-
- var keyPath: KeyPath {
- switch self {
- case .s1_def_one: return \\GenStrings.s1_def_one
- case .s1_def_two: return \\GenStrings.s1_def_two
- case .s2_def_one: return \\GenStrings.s2_def_one
- case .s2_def_two: return \\GenStrings.s2_def_two
- }
- }
- }
-
- // MARK: - section_one
-
- /// Translation in fr :
- /// Section Un - Definition Un
- ///
- /// Comment :
- /// No comment
- static var s1_def_one: String {
- NSLocalizedString("s1_def_one", tableName: kStringsFileName, bundle: Bundle.main, value: "Section Un - Definition Un", comment: "")
- }
-
- /// Translation in fr :
- /// Section Un - Definition Deux
- ///
- /// Comment :
- /// No comment
- static var s1_def_two: String {
- NSLocalizedString("s1_def_two", tableName: kStringsFileName, bundle: Bundle.main, value: "Section Un - Definition Deux", comment: "")
- }
-
- // MARK: - section_two
-
- /// Translation in fr :
- /// Section Deux - Definition Un
- ///
- /// Comment :
- /// No comment
- static var s2_def_one: String {
- NSLocalizedString("s2_def_one", tableName: kStringsFileName, bundle: Bundle.main, value: "Section Deux - Definition Un", comment: "")
- }
-
- /// Translation in fr :
- /// Section Deux - Definition Deux
- ///
- /// Comment :
- /// No comment
- static var s2_def_two: String {
- NSLocalizedString("s2_def_two", tableName: kStringsFileName, bundle: Bundle.main, value: "Section Deux - Definition Deux", comment: "")
- }
- }
- """
-
- if extensionContent != expect {
- print(prettyFirstDifferenceBetweenStrings(s1: extensionContent, s2: expect))
- }
- XCTAssertEqual(extensionContent.adaptForXCTest(), expect.adaptForXCTest())
- }
-
- func testGeneratedExtensionContentWithStaticVarWithComment() {
- // Given
- let sectionOne = Section(name: "section_one")
- sectionOne.definitions = [
- getDefinition(name: "s1_def_one",
- translations: ["fr": "Section Un - Definition Un",
- "en": "Section One - Definition One"],
- tags: ["ios","iosonly"],
- comment: "This is a comment"),
- getDefinition(name: "s1_def_two",
- translations: ["fr": "Section Un - Definition Deux",
- "en": "Section One - Definition Two"],
- tags: ["ios","iosonly"],
- comment: "This is a comment")
- ]
-
- let sectionTwo = Section(name: "section_two")
- sectionTwo.definitions = [
- getDefinition(name: "s2_def_one",
- translations: ["fr": "Section Deux - Definition Un",
- "en": "Section Two - Definition One"],
- tags: ["ios","iosonly"],
- comment: "This is a comment"),
- getDefinition(name: "s2_def_two",
- translations: ["fr": "Section Deux - Definition Deux"],
- tags: ["notranslation"],
- comment: "This is a comment")
- ]
-
- // When
- let extensionContent = StringsFileGenerator.getExtensionContent(sections: [sectionOne, sectionTwo],
- defaultLang: "fr",
- tags: ["ios", "iosonly", "notranslation"],
- staticVar: true,
- inputFilename: "myInputFilename",
- extensionName: "GenStrings",
- extensionSuffix: "strings")
-
- // Expect
- let expect = """
- // Generated by ResgenSwift.Strings.Stringium \(ResgenSwiftVersion)
-
- import UIKit
-
- fileprivate let kStringsFileName = "myInputFilename"
-
- extension GenStrings {
-
- enum KeyStrings: String {
- case s1_def_one = "s1_def_one"
- case s1_def_two = "s1_def_two"
- case s2_def_one = "s2_def_one"
- case s2_def_two = "s2_def_two"
-
- var keyPath: KeyPath {
- switch self {
- case .s1_def_one: return \\GenStrings.s1_def_one
- case .s1_def_two: return \\GenStrings.s1_def_two
- case .s2_def_one: return \\GenStrings.s2_def_one
- case .s2_def_two: return \\GenStrings.s2_def_two
- }
- }
- }
-
- // MARK: - section_one
-
- /// Translation in fr :
- /// Section Un - Definition Un
- ///
- /// Comment :
- /// This is a comment
- static var s1_def_one: String {
- NSLocalizedString("s1_def_one", tableName: kStringsFileName, bundle: Bundle.main, value: "Section Un - Definition Un", comment: "This is a comment")
- }
-
- /// Translation in fr :
- /// Section Un - Definition Deux
- ///
- /// Comment :
- /// This is a comment
- static var s1_def_two: String {
- NSLocalizedString("s1_def_two", tableName: kStringsFileName, bundle: Bundle.main, value: "Section Un - Definition Deux", comment: "This is a comment")
- }
-
- // MARK: - section_two
-
- /// Translation in fr :
- /// Section Deux - Definition Un
- ///
- /// Comment :
- /// This is a comment
- static var s2_def_one: String {
- NSLocalizedString("s2_def_one", tableName: kStringsFileName, bundle: Bundle.main, value: "Section Deux - Definition Un", comment: "This is a comment")
- }
-
- /// Translation in fr :
- /// Section Deux - Definition Deux
- ///
- /// Comment :
- /// This is a comment
- static var s2_def_two: String {
- NSLocalizedString("s2_def_two", tableName: kStringsFileName, bundle: Bundle.main, value: "Section Deux - Definition Deux", comment: "This is a comment")
- }
- }
- """
-
- if extensionContent != expect {
- print(prettyFirstDifferenceBetweenStrings(s1: extensionContent, s2: expect))
- }
- XCTAssertEqual(extensionContent.adaptForXCTest(), expect.adaptForXCTest())
- }
-}
-
diff --git a/Tests/ResgenSwiftTests/Strings/TagsGeneratorTests.swift b/Tests/ResgenSwiftTests/Strings/TagsGeneratorTests.swift
index 561c70c..af34e68 100644
--- a/Tests/ResgenSwiftTests/Strings/TagsGeneratorTests.swift
+++ b/Tests/ResgenSwiftTests/Strings/TagsGeneratorTests.swift
@@ -57,12 +57,18 @@ final class TagsGeneratorTests: XCTestCase {
/// Translation in ium :
/// Some translation
+ ///
+ /// Comment :
+ /// No comment
var s1_def_one: String {
"Some translation"
}
/// Translation in ium :
/// Some translation
+ ///
+ /// Comment :
+ /// No comment
var s1_def_two: String {
"Some translation"
}
@@ -71,6 +77,9 @@ final class TagsGeneratorTests: XCTestCase {
/// Translation in ium :
/// Some translation
+ ///
+ /// Comment :
+ /// No comment
var s2_def_one: String {
"Some translation"
}