Add SwiftLint HARD rules
	
		
			
	
		
	
	
		
	
		
			Some checks failed
		
		
	
	
		
			
				
	
				gitea-openium/resgen.swift/pipeline/head There was a failure building this commit
				
			
		
		
	
	
				
					
				
			
		
			Some checks failed
		
		
	
	gitea-openium/resgen.swift/pipeline/head There was a failure building this commit
				
			This commit is contained in:
		
							
								
								
									
										317
									
								
								.swiftlint.yml
									
									
									
									
									
								
							
							
						
						
									
										317
									
								
								.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 | ||||
|  | ||||
|    | ||||
|   | ||||
| @@ -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" | ||||
|       } | ||||
|     }, | ||||
|     { | ||||
|   | ||||
| @@ -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)], | ||||
|     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 | ||||
|   | ||||
| @@ -5,9 +5,9 @@ | ||||
| //  Created by Loris Perret on 08/12/2023. | ||||
| // | ||||
|  | ||||
| import ToolCore | ||||
| import Foundation | ||||
| import ArgumentParser | ||||
| import Foundation | ||||
| import ToolCore | ||||
|  | ||||
| struct Analytics: ParsableCommand { | ||||
|  | ||||
| @@ -42,15 +42,17 @@ struct Analytics: ParsableCommand { | ||||
|         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") | ||||
|     } | ||||
| @@ -64,19 +66,21 @@ struct Analytics: ParsableCommand { | ||||
|         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 | ||||
|         } | ||||
|   | ||||
| @@ -8,6 +8,7 @@ | ||||
| import Foundation | ||||
|  | ||||
| enum AnalyticsError: Error { | ||||
|  | ||||
|     case noValidTracker(String) | ||||
|     case fileNotExists(String) | ||||
|     case missingElement(String) | ||||
| @@ -32,7 +33,7 @@ enum AnalyticsError: Error { | ||||
|         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)" | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -5,10 +5,13 @@ | ||||
| //  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 | ||||
|  | ||||
| @@ -34,8 +37,9 @@ 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" | ||||
|   | ||||
| @@ -5,34 +5,50 @@ | ||||
| //  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) | ||||
| @@ -41,38 +57,57 @@ class AnalyticsGenerator { | ||||
|  | ||||
|     // 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()) | ||||
|         \(getImport(targets: targets)) | ||||
|  | ||||
|         \(Self.getAnalyticsProtocol()) | ||||
|         \(getAnalyticsProtocol(targets: targets)) | ||||
|         // MARK: - Manager | ||||
|  | ||||
|         class AnalyticsManager { | ||||
|  | ||||
|             static var shared = AnalyticsManager() | ||||
|  | ||||
|             // MARK: - Properties | ||||
|  | ||||
|             var managers: [AnalyticsManagerProtocol] = [] | ||||
|  | ||||
|             \(Self.getEnabledContent()) | ||||
|             \(getEnabledContent()) | ||||
|  | ||||
|             \(Self.getAnalyticsProperties()) | ||||
|             \(getAnalyticsProperties(targets: targets)) | ||||
|  | ||||
|             \(Self.getPrivateLogFunction()) | ||||
|             \(getPrivateLogFunction()) | ||||
|         """ | ||||
|     } | ||||
|  | ||||
| @@ -88,7 +123,7 @@ class AnalyticsGenerator { | ||||
|         """ | ||||
|     } | ||||
|  | ||||
|     private static func getImport() -> String { | ||||
|     private static func getImport(targets: [TrackerType]) -> String { | ||||
|         var result: [String] = [] | ||||
|  | ||||
|         if targets.contains(TrackerType.matomo) { | ||||
| @@ -131,7 +166,7 @@ class AnalyticsGenerator { | ||||
|         """ | ||||
|     } | ||||
|  | ||||
|     private static func getAnalyticsProperties() -> String { | ||||
|     private static func getAnalyticsProperties(targets: [TrackerType]) -> String { | ||||
|         var header = "" | ||||
|         var content: [String] = [] | ||||
|         let footer = "    }" | ||||
| @@ -164,11 +199,12 @@ 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, | ||||
| @@ -193,7 +229,11 @@ class AnalyticsGenerator { | ||||
|         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 | ||||
|   | ||||
| @@ -11,10 +11,10 @@ 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") | ||||
|     } | ||||
|   | ||||
| @@ -11,11 +11,11 @@ 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") | ||||
|     } | ||||
|   | ||||
| @@ -8,6 +8,9 @@ | ||||
| import Foundation | ||||
|  | ||||
| class AnalyticsCategory { | ||||
|  | ||||
|     // MARK: - Properties | ||||
|  | ||||
|     let id: String // OnBoarding | ||||
|     var definitions = [AnalyticsDefinition]() | ||||
|  | ||||
|   | ||||
| @@ -9,6 +9,9 @@ import Foundation | ||||
| import ToolCore | ||||
|  | ||||
| class AnalyticsDefinition { | ||||
|  | ||||
|     // MARK: - Properties | ||||
|  | ||||
|     let id: String | ||||
|     var name: String | ||||
|     var path: String = "" | ||||
|   | ||||
| @@ -8,16 +8,19 @@ | ||||
| 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 | ||||
| @@ -28,6 +31,7 @@ struct AnalyticsDefinitionScreenDTO: Codable { | ||||
| } | ||||
|  | ||||
| struct AnalyticsDefinitionEventDTO: Codable { | ||||
|  | ||||
|     var id: String | ||||
|     var name: String | ||||
|     var tags: String | ||||
| @@ -39,6 +43,7 @@ struct AnalyticsDefinitionEventDTO: Codable { | ||||
| } | ||||
|  | ||||
| struct AnalyticsParameterDTO: Codable { | ||||
|  | ||||
|     var name: String | ||||
|     var type: String | ||||
|     var replaceIn: String? | ||||
|   | ||||
| @@ -8,6 +8,9 @@ | ||||
| import Foundation | ||||
|  | ||||
| class AnalyticsParameter { | ||||
|  | ||||
|     // MARK: - Properties | ||||
|  | ||||
|     var name: String | ||||
|     var type: String | ||||
|     var replaceIn: [String] = [] | ||||
|   | ||||
| @@ -10,6 +10,7 @@ import Foundation | ||||
| extension AnalyticsDefinition { | ||||
|  | ||||
|     enum TagType { | ||||
|  | ||||
|         case screen | ||||
|         case event | ||||
|     } | ||||
|   | ||||
| @@ -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" | ||||
|         } | ||||
|   | ||||
| @@ -9,10 +9,48 @@ 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) | ||||
| @@ -29,10 +67,9 @@ 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 | ||||
| @@ -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, | ||||
| @@ -110,9 +147,9 @@ class AnalyticsFileParser { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     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 | ||||
|             } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -5,15 +5,15 @@ | ||||
| //  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 | ||||
|     ) | ||||
| @@ -31,7 +31,7 @@ struct Colors: ParsableCommand { | ||||
|  | ||||
|     // MARK: - Run | ||||
|  | ||||
|     public func run() throws { | ||||
|     func run() throws { | ||||
|         print("[\(Self.toolName)] Starting colors generation") | ||||
|  | ||||
|         // Check requirements | ||||
| @@ -43,28 +43,36 @@ struct Colors: ParsableCommand { | ||||
|         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") | ||||
|     } | ||||
| @@ -78,27 +86,29 @@ struct Colors: ParsableCommand { | ||||
|         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 | ||||
|         } | ||||
| @@ -118,7 +128,7 @@ struct Colors: ParsableCommand { | ||||
|             } catch { | ||||
|                 let error = ColorsToolError.deleteExistingColors("\(options.xcassetsPath)/Colors") | ||||
|                 print(error.description) | ||||
|                 Colors.exit(withError: error) | ||||
|                 Self.exit(withError: error) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -8,6 +8,7 @@ | ||||
| import Foundation | ||||
|  | ||||
| enum ColorsToolError: Error { | ||||
|  | ||||
|     case extensionNamesCollision(String) | ||||
|     case badFormat(String) | ||||
|     case writeAsset(String) | ||||
| @@ -31,13 +32,13 @@ enum ColorsToolError: Error { | ||||
|         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): | ||||
|   | ||||
| @@ -5,10 +5,13 @@ | ||||
| //  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 | ||||
|  | ||||
| @@ -44,7 +47,7 @@ extension ColorsToolOptions { | ||||
|     // MARK: - SwiftUI | ||||
|  | ||||
|     var extensionFileName: String { | ||||
|         if let extensionSuffix = extensionSuffix { | ||||
|         if let extensionSuffix { | ||||
|             return "\(extensionName)+\(extensionSuffix).swift" | ||||
|         } | ||||
|         return "\(extensionName).swift" | ||||
| @@ -57,7 +60,7 @@ extension ColorsToolOptions { | ||||
|     // MARK: - UIKit | ||||
|  | ||||
|     var extensionFileNameUIKit: String { | ||||
|         if let extensionSuffix = extensionSuffix { | ||||
|         if let extensionSuffix { | ||||
|             return "\(extensionNameUIKit)+\(extensionSuffix).swift" | ||||
|         } | ||||
|         return "\(extensionNameUIKit).swift" | ||||
|   | ||||
| @@ -15,32 +15,38 @@ struct ColorExtensionGenerator { | ||||
|  | ||||
|     // 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), | ||||
| @@ -66,9 +72,11 @@ struct ColorExtensionGenerator { | ||||
|         """ | ||||
|     } | ||||
|  | ||||
|     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) | ||||
|         } | ||||
|   | ||||
| @@ -8,7 +8,7 @@ | ||||
| import Foundation | ||||
| import ToolCore | ||||
|  | ||||
| struct ColorXcassetHelper { | ||||
| enum ColorXcassetHelper { | ||||
|  | ||||
|     static func generateXcassetColors(colors: [ParsedColor], to xcassetsPath: String) { | ||||
|         colors.forEach { | ||||
| @@ -25,8 +25,10 @@ struct ColorXcassetHelper { | ||||
|         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) | ||||
| @@ -38,7 +40,7 @@ struct ColorXcassetHelper { | ||||
|         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) | ||||
|   | ||||
| @@ -5,10 +5,11 @@ | ||||
| //  Created by Thibaut Schmitt on 29/08/2022. | ||||
| // | ||||
|  | ||||
| import Foundation | ||||
| import ArgumentParser | ||||
| import Foundation | ||||
|  | ||||
| enum ColorStyle: String, Decodable, ExpressibleByArgument { | ||||
|  | ||||
|     case light | ||||
|     case all | ||||
|  | ||||
|   | ||||
| @@ -8,6 +8,7 @@ | ||||
| import Foundation | ||||
|  | ||||
| struct ParsedColor { | ||||
|  | ||||
|     let name: String | ||||
|     let light: String | ||||
|     let dark: String | ||||
|   | ||||
| @@ -7,10 +7,11 @@ | ||||
|  | ||||
| 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 | ||||
| @@ -20,9 +21,9 @@ class ColorFileParser { | ||||
|     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() | ||||
|   | ||||
| @@ -5,10 +5,13 @@ | ||||
| //  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 | ||||
|  | ||||
|   | ||||
| @@ -5,9 +5,9 @@ | ||||
| //  Created by Thibaut Schmitt on 13/12/2021. | ||||
| // | ||||
|  | ||||
| import ToolCore | ||||
| import Foundation | ||||
| import ArgumentParser | ||||
| import Foundation | ||||
| import ToolCore | ||||
|  | ||||
| struct Fonts: ParsableCommand { | ||||
|  | ||||
| @@ -30,7 +30,7 @@ struct Fonts: ParsableCommand { | ||||
|  | ||||
|     // 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") | ||||
|  | ||||
| @@ -84,20 +84,22 @@ struct Fonts: ParsableCommand { | ||||
|         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 | ||||
|         } | ||||
|   | ||||
| @@ -8,6 +8,7 @@ | ||||
| import Foundation | ||||
|  | ||||
| enum FontsToolError: Error { | ||||
|  | ||||
|     case extensionNamesCollision(String) | ||||
|     case fcScan(String, Int32, String?) | ||||
|     case inputFolderNotFound(String) | ||||
| @@ -19,7 +20,7 @@ enum FontsToolError: Error { | ||||
|         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): | ||||
| @@ -28,7 +29,7 @@ enum FontsToolError: Error { | ||||
|         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)" | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -8,7 +8,7 @@ | ||||
| import Foundation | ||||
| import ToolCore | ||||
|  | ||||
| class FontsToolHelper { | ||||
| enum FontsToolHelper { | ||||
|  | ||||
|     static func getFontPostScriptName(for fonts: [String], inputFolder: String) -> [FontName] { | ||||
|         let fontsFilenames = Self.getFontsFilenames(fromInputFolder: inputFolder) | ||||
| @@ -43,10 +43,10 @@ class FontsToolHelper { | ||||
|             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 | ||||
| @@ -58,7 +58,7 @@ class FontsToolHelper { | ||||
|     } | ||||
|  | ||||
|     private static func getFontName(atPath path: String) -> FontName { | ||||
|         //print("fc-scan --format %{postscriptname} \(path)") | ||||
|         // print("fc-scan --format %{postscriptname} \(path)") | ||||
|         // Get real font name | ||||
|         let task = Shell.shell(["fc-scan", "--format", "%{postscriptname}", path]) | ||||
|  | ||||
|   | ||||
| @@ -8,7 +8,8 @@ | ||||
| import Foundation | ||||
| import ToolCore | ||||
|  | ||||
| class FontPlistGenerator { | ||||
| enum FontPlistGenerator { | ||||
|  | ||||
|     static func generatePlistUIAppsFontContent(for fonts: [FontName], infoPlistPaths: [String]) -> String { | ||||
|         let fontsToAddToPlist = fonts | ||||
|             .compactMap { $0 } | ||||
| @@ -16,19 +17,25 @@ class FontPlistGenerator { | ||||
|         // 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 { fontName in | ||||
|                     Shell.shell(launchPath: "/usr/libexec/PlistBuddy", | ||||
|                                 ["-c", "add :UIAppFonts: string \(fontName.filename).\(fontName.fileExtension)", infoPlist]) | ||||
|                     Shell.shell( | ||||
|                         launchPath: "/usr/libexec/PlistBuddy", | ||||
|                         ["-c", "add :UIAppFonts: string \(fontName.filename).\(fontName.fileExtension)", infoPlist] | ||||
|                     ) | ||||
|                 } | ||||
|         } | ||||
|  | ||||
|   | ||||
| @@ -8,7 +8,7 @@ | ||||
| import Foundation | ||||
| import ToolCore | ||||
|  | ||||
| class FontExtensionGenerator { | ||||
| enum FontExtensionGenerator { | ||||
|  | ||||
|     private static func getFontNameEnum(fontsNames: [FontName]) -> String { | ||||
|         var enumDefinition = "    enum FontName: String {\n" | ||||
| @@ -21,32 +21,38 @@ class FontExtensionGenerator { | ||||
|         return enumDefinition | ||||
|     } | ||||
|  | ||||
|     static func writeExtensionFile(fontsNames: [FontName], | ||||
|                                    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: [FontName], | ||||
|                                     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), | ||||
|   | ||||
| @@ -7,7 +7,7 @@ | ||||
|  | ||||
| import Foundation | ||||
|  | ||||
| //typealias FontName = String | ||||
| // swiftlint:disable no_grouping_extension | ||||
|  | ||||
| struct FontName: Hashable { | ||||
|  | ||||
| @@ -17,6 +17,7 @@ struct FontName: Hashable { | ||||
| } | ||||
|  | ||||
| extension FontName { | ||||
|  | ||||
|     var fontNameSanitize: String { | ||||
|         postscriptName.removeCharacters(from: "[]+-_") | ||||
|     } | ||||
|   | ||||
| @@ -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) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -5,9 +5,9 @@ | ||||
| //  Created by Thibaut Schmitt on 30/08/2022. | ||||
| // | ||||
|  | ||||
| import ToolCore | ||||
| import Foundation | ||||
| import ArgumentParser | ||||
| import Foundation | ||||
| import ToolCore | ||||
|  | ||||
| struct Generate: ParsableCommand { | ||||
|  | ||||
| @@ -28,7 +28,7 @@ struct Generate: ParsableCommand { | ||||
|  | ||||
|     // MARK: - Run | ||||
|  | ||||
|     public func run() throws { | ||||
|     func run() throws { | ||||
|         print("[\(Self.toolName)] Starting Resgen with configuration: \(options.configurationFile)") | ||||
|  | ||||
|         // Parse | ||||
| @@ -43,16 +43,20 @@ struct Generate: ParsableCommand { | ||||
|         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") | ||||
|             } | ||||
|  | ||||
|   | ||||
| @@ -8,6 +8,7 @@ | ||||
| import Foundation | ||||
|  | ||||
| enum GenerateError: Error { | ||||
|  | ||||
|     case fileNotExists(String) | ||||
|     case invalidConfigurationFile(String, String) | ||||
|     case commandError([String], String) | ||||
| @@ -18,16 +19,15 @@ enum GenerateError: Error { | ||||
|         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)" | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -5,10 +5,11 @@ | ||||
| //  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 | ||||
|  | ||||
|   | ||||
| @@ -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 = [ | ||||
| @@ -30,7 +31,7 @@ struct ArchitectureGenerator { | ||||
|         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) | ||||
|   | ||||
| @@ -8,6 +8,7 @@ | ||||
| import Foundation | ||||
|  | ||||
| struct ConfigurationFile: Codable, CustomDebugStringConvertible { | ||||
|  | ||||
|     var architecture: ConfigurationArchitecture? | ||||
|     var analytics: [AnalyticsConfiguration] | ||||
|     var colors: [ColorsConfiguration] | ||||
| @@ -44,10 +45,11 @@ 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)()" | ||||
| @@ -81,6 +83,7 @@ struct ConfigurationArchitecture: Codable { | ||||
| } | ||||
|  | ||||
| struct AnalyticsConfiguration: Codable, CustomDebugStringConvertible { | ||||
|  | ||||
|     let inputFile: String | ||||
|     let target: String | ||||
|     let extensionOutputPath: String | ||||
| @@ -89,18 +92,20 @@ struct AnalyticsConfiguration: Codable, CustomDebugStringConvertible { | ||||
|     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 | ||||
| @@ -122,6 +127,7 @@ struct AnalyticsConfiguration: Codable, CustomDebugStringConvertible { | ||||
| } | ||||
|  | ||||
| struct ColorsConfiguration: Codable, CustomDebugStringConvertible { | ||||
|  | ||||
|     let inputFile: String | ||||
|     let style: String | ||||
|     let xcassetsPath: String | ||||
| @@ -132,20 +138,22 @@ struct ColorsConfiguration: Codable, CustomDebugStringConvertible { | ||||
|     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 | ||||
| @@ -171,6 +179,7 @@ struct ColorsConfiguration: Codable, CustomDebugStringConvertible { | ||||
| } | ||||
|  | ||||
| struct FontsConfiguration: Codable, CustomDebugStringConvertible { | ||||
|  | ||||
|     let inputFile: String | ||||
|     let extensionOutputPath: String | ||||
|     let extensionName: String? | ||||
| @@ -180,19 +189,21 @@ struct FontsConfiguration: Codable, CustomDebugStringConvertible { | ||||
|     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 | ||||
| @@ -216,6 +227,7 @@ struct FontsConfiguration: Codable, CustomDebugStringConvertible { | ||||
| } | ||||
|  | ||||
| struct ImagesConfiguration: Codable, CustomDebugStringConvertible { | ||||
|  | ||||
|     let inputFile: String | ||||
|     let xcassetsPath: String | ||||
|     let extensionOutputPath: String | ||||
| @@ -225,19 +237,21 @@ struct ImagesConfiguration: Codable, CustomDebugStringConvertible { | ||||
|     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 | ||||
| @@ -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 | ||||
| @@ -320,6 +337,7 @@ struct StringsConfiguration: Codable, CustomDebugStringConvertible { | ||||
| } | ||||
|  | ||||
| struct TagsConfiguration: Codable, CustomDebugStringConvertible { | ||||
|  | ||||
|     let inputFile: String | ||||
|     let lang: String | ||||
|     let extensionOutputPath: String | ||||
| @@ -328,18 +346,20 @@ struct TagsConfiguration: Codable, CustomDebugStringConvertible { | ||||
|     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 | ||||
|   | ||||
| @@ -8,7 +8,8 @@ | ||||
| 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) | ||||
|   | ||||
| @@ -8,6 +8,7 @@ | ||||
| import Foundation | ||||
|  | ||||
| extension AnalyticsConfiguration: Runnable { | ||||
|  | ||||
|     func run(projectDirectory: String, force: Bool) { | ||||
|         var args = [String]() | ||||
|  | ||||
| @@ -25,13 +26,13 @@ extension AnalyticsConfiguration: Runnable { | ||||
|             "\(staticMembersOptions)" | ||||
|         ] | ||||
|  | ||||
|         if let extensionName = extensionName { | ||||
|         if let extensionName { | ||||
|             args += [ | ||||
|                 "--extension-name", | ||||
|                 extensionName | ||||
|             ] | ||||
|         } | ||||
|         if let extensionSuffix = extensionSuffix { | ||||
|         if let extensionSuffix { | ||||
|             args += [ | ||||
|                 "--extension-suffix", | ||||
|                 extensionSuffix | ||||
|   | ||||
| @@ -8,6 +8,7 @@ | ||||
| import Foundation | ||||
|  | ||||
| extension ColorsConfiguration: Runnable { | ||||
|  | ||||
|     func run(projectDirectory: String, force: Bool) { | ||||
|         let args = getArguments(projectDirectory: projectDirectory, force: force) | ||||
|         Colors.main(args) | ||||
| @@ -32,19 +33,19 @@ extension ColorsConfiguration: Runnable { | ||||
|             "\(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 | ||||
|   | ||||
| @@ -8,6 +8,7 @@ | ||||
| import Foundation | ||||
|  | ||||
| extension FontsConfiguration: Runnable { | ||||
|  | ||||
|     func run(projectDirectory: String, force: Bool) { | ||||
|         let args = getArguments(projectDirectory: projectDirectory, force: force) | ||||
|         Fonts.main(args) | ||||
| @@ -28,27 +29,27 @@ extension FontsConfiguration: Runnable { | ||||
|             "\(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) } | ||||
|   | ||||
| @@ -8,6 +8,7 @@ | ||||
| import Foundation | ||||
|  | ||||
| extension ImagesConfiguration: Runnable { | ||||
|  | ||||
|     func run(projectDirectory: String, force: Bool) { | ||||
|         let args = getArguments(projectDirectory: projectDirectory, force: force) | ||||
|         Images.main(args) | ||||
| @@ -30,19 +31,21 @@ extension ImagesConfiguration: Runnable { | ||||
|             "\(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 | ||||
|   | ||||
| @@ -8,5 +8,6 @@ | ||||
| import Foundation | ||||
|  | ||||
| protocol Runnable { | ||||
|  | ||||
|     func run(projectDirectory: String, force: Bool) | ||||
| } | ||||
|   | ||||
| @@ -8,6 +8,7 @@ | ||||
| import Foundation | ||||
|  | ||||
| extension StringsConfiguration: Runnable { | ||||
|  | ||||
|     func run(projectDirectory: String, force: Bool) { | ||||
|         var args = [String]() | ||||
|  | ||||
| @@ -31,14 +32,14 @@ extension StringsConfiguration: Runnable { | ||||
|             "\(xcStringsOptions)" | ||||
|         ] | ||||
|  | ||||
|         if let extensionName = extensionName { | ||||
|         if let extensionName { | ||||
|             args += [ | ||||
|                 "--extension-name", | ||||
|                 extensionName | ||||
|             ] | ||||
|         } | ||||
|  | ||||
|         if let extensionSuffix = extensionSuffix { | ||||
|         if let extensionSuffix { | ||||
|             args += [ | ||||
|                 "--extension-suffix", | ||||
|                 extensionSuffix | ||||
|   | ||||
| @@ -8,6 +8,7 @@ | ||||
| import Foundation | ||||
|  | ||||
| extension TagsConfiguration: Runnable { | ||||
|  | ||||
|     func run(projectDirectory: String, force: Bool) { | ||||
|         var args = [String]() | ||||
|  | ||||
| @@ -25,13 +26,13 @@ extension TagsConfiguration: Runnable { | ||||
|             "\(staticMembersOptions)" | ||||
|         ] | ||||
|  | ||||
|         if let extensionName = extensionName { | ||||
|         if let extensionName { | ||||
|             args += [ | ||||
|                 "--extension-name", | ||||
|                 extensionName | ||||
|             ] | ||||
|         } | ||||
|         if let extensionSuffix = extensionSuffix { | ||||
|         if let extensionSuffix { | ||||
|             args += [ | ||||
|                 "--extension-suffix", | ||||
|                 extensionSuffix | ||||
|   | ||||
| @@ -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 { | ||||
|   | ||||
| @@ -5,42 +5,48 @@ | ||||
| //  Created by Thibaut Schmitt on 14/02/2022. | ||||
| // | ||||
|  | ||||
| import ToolCore | ||||
| import Foundation | ||||
| import ToolCore | ||||
|  | ||||
| class ImageExtensionGenerator { | ||||
| enum 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), | ||||
| @@ -49,9 +55,11 @@ 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) | ||||
| @@ -62,7 +70,11 @@ class ImageExtensionGenerator { | ||||
|         """ | ||||
|     } | ||||
|  | ||||
|     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") | ||||
|   | ||||
| @@ -9,12 +9,15 @@ import Foundation | ||||
| import ToolCore | ||||
|  | ||||
| enum OutputImageExtension: String { | ||||
|  | ||||
|     case png | ||||
|     case svg | ||||
| } | ||||
|  | ||||
| class XcassetsGenerator { | ||||
|  | ||||
|     // MARK: - Properties | ||||
|  | ||||
|     let forceGeneration: Bool | ||||
|  | ||||
|     // MARK: - Init | ||||
| @@ -33,7 +36,7 @@ class XcassetsGenerator { | ||||
|         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 { | ||||
| @@ -83,8 +86,10 @@ class XcassetsGenerator { | ||||
|             // 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,15 +146,33 @@ 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 | ||||
| @@ -159,7 +180,11 @@ class XcassetsGenerator { | ||||
|             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") | ||||
|         } | ||||
| @@ -177,7 +202,7 @@ class XcassetsGenerator { | ||||
|         } | ||||
|  | ||||
|         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") | ||||
|     } | ||||
|   | ||||
| @@ -5,9 +5,9 @@ | ||||
| //  Created by Thibaut Schmitt on 24/01/2022. | ||||
| // | ||||
|  | ||||
| import ToolCore | ||||
| import Foundation | ||||
| import ArgumentParser | ||||
| import Foundation | ||||
| import ToolCore | ||||
|  | ||||
| struct Images: ParsableCommand { | ||||
|  | ||||
| @@ -48,24 +48,30 @@ struct Images: ParsableCommand { | ||||
|             .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.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.extensionNameUIKit, | ||||
|             extensionFilePath: options.extensionFilePathUIKit, | ||||
|             isSwiftUI: false | ||||
|         ) | ||||
|  | ||||
|         print("[\(Self.toolName)] Images generated") | ||||
|     } | ||||
| @@ -83,23 +89,25 @@ struct Images: ParsableCommand { | ||||
|         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 | ||||
|         } | ||||
| @@ -113,11 +121,11 @@ struct Images: ParsableCommand { | ||||
|     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) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -8,6 +8,7 @@ | ||||
| import Foundation | ||||
|  | ||||
| enum ImagesError: Error { | ||||
|  | ||||
|     case extensionNamesCollision(String) | ||||
|     case inputFolderNotFound(String) | ||||
|     case fileNotExists(String) | ||||
| @@ -32,13 +33,13 @@ enum ImagesError: Error { | ||||
|         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): | ||||
|   | ||||
| @@ -5,10 +5,13 @@ | ||||
| //  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 | ||||
|  | ||||
| @@ -44,7 +47,7 @@ extension ImagesOptions { | ||||
|     // MARK: - SwiftUI | ||||
|  | ||||
|     var extensionFileName: String { | ||||
|         if let extensionSuffix = extensionSuffix { | ||||
|         if let extensionSuffix { | ||||
|             return "\(extensionName)+\(extensionSuffix).swift" | ||||
|         } | ||||
|         return "\(extensionName).swift" | ||||
| @@ -57,7 +60,7 @@ extension ImagesOptions { | ||||
|     // MARK: - UIKit | ||||
|  | ||||
|     var extensionFileNameUIKit: String { | ||||
|         if let extensionSuffix = extensionSuffix { | ||||
|         if let extensionSuffix { | ||||
|             return "\(extensionNameUIKit)+\(extensionSuffix).swift" | ||||
|         } | ||||
|         return "\(extensionNameUIKit).swift" | ||||
|   | ||||
| @@ -8,6 +8,7 @@ | ||||
| import Foundation | ||||
|  | ||||
| struct ConvertArgument { | ||||
|  | ||||
|     let width: String? | ||||
|     let height: String? | ||||
| } | ||||
|   | ||||
| @@ -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" | ||||
|     } | ||||
|   | ||||
| @@ -8,10 +8,14 @@ | ||||
| import Foundation | ||||
|  | ||||
| enum ImageExtension: String { | ||||
|  | ||||
|     case png | ||||
| } | ||||
|  | ||||
| struct ParsedImage { | ||||
|  | ||||
|     // MARK: - Properties | ||||
|  | ||||
|     let name: String | ||||
|     let tags: String | ||||
|     let width: Int | ||||
| @@ -34,7 +38,7 @@ 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 = "" | ||||
| @@ -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( | ||||
| @@ -129,13 +132,13 @@ struct ParsedImage { | ||||
|     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)")! | ||||
|             } | ||||
|         """ | ||||
|   | ||||
| @@ -8,6 +8,7 @@ | ||||
| import Foundation | ||||
|  | ||||
| enum PlatormTag: String { | ||||
|  | ||||
|     case droid = "d" | ||||
|     case ios = "i" | ||||
| } | ||||
|   | ||||
| @@ -7,10 +7,10 @@ | ||||
|  | ||||
| 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) | ||||
| @@ -30,13 +30,13 @@ class ImageFileParser { | ||||
|                 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] = [] | ||||
|   | ||||
| @@ -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( | ||||
| @@ -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 | ||||
|   | ||||
| @@ -5,24 +5,34 @@ | ||||
| //  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) | ||||
| @@ -31,10 +41,24 @@ class TagsGenerator { | ||||
|  | ||||
|     // 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") | ||||
| @@ -52,12 +76,17 @@ class TagsGenerator { | ||||
|         """ | ||||
|     } | ||||
|  | ||||
|     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)" | ||||
|   | ||||
| @@ -7,7 +7,12 @@ | ||||
|  | ||||
| import Foundation | ||||
|  | ||||
| // swiftlint:disable force_unwrapping | ||||
|  | ||||
| class Definition { | ||||
|  | ||||
|     // MARK: - Properties | ||||
|  | ||||
|     let name: String | ||||
|     var tags = [String]() | ||||
|     var comment: String? | ||||
| @@ -48,21 +53,31 @@ class Definition { | ||||
|     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 | ||||
|                 } | ||||
| @@ -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,7 +127,7 @@ 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: ", "))) | ||||
|             } | ||||
|         """ | ||||
| @@ -131,12 +151,14 @@ 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 | ||||
| @@ -160,12 +182,14 @@ class Definition { | ||||
|         // 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 | ||||
|   | ||||
| @@ -8,13 +8,20 @@ | ||||
| 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 | ||||
|   | ||||
| @@ -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) | ||||
| // enum VarationOrStringUnit: Encodable | ||||
| //  | ||||
| //    func encode(to encoder: any Encoder) throws { | ||||
| //        if let varations { | ||||
| //     case variations([Varation]) | ||||
| //     case stringUnit: (DefaultStringUnit) | ||||
| //  | ||||
| //        } else if let  { | ||||
| //     func encode(to encoder: any Encoder) throws { | ||||
| //         if let varations { | ||||
| //  | ||||
| //        } | ||||
| //    } | ||||
| //} | ||||
| //         } else if let  { | ||||
| //  | ||||
| //         } | ||||
| //     } | ||||
| // } | ||||
|  | ||||
| struct DefaultStringUnit: Codable, Equatable { | ||||
|  | ||||
|     let state: String | ||||
|     let value: String | ||||
| } | ||||
|   | ||||
| @@ -7,15 +7,18 @@ | ||||
|  | ||||
| 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) | ||||
| @@ -37,8 +40,8 @@ class TwineFileParser { | ||||
|  | ||||
|                 guard let lastDefinition = sections.last?.definitions.last, | ||||
|                       let leftElement = splitLine.first else { | ||||
|                           return | ||||
|                       } | ||||
|                     return | ||||
|                 } | ||||
|  | ||||
|                 let rightElement: String = splitLine.dropFirst().joined(separator: "=") | ||||
|  | ||||
| @@ -62,11 +65,6 @@ class TwineFileParser { | ||||
|  | ||||
|                 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 | ||||
|                     //} | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| @@ -83,7 +81,7 @@ class TwineFileParser { | ||||
|                     return true | ||||
|                 } | ||||
|         } | ||||
|         if invalidDefinitionNames.count > 0 { | ||||
|         if invalidDefinitionNames.isEmpty == false { | ||||
|             print("warning: [\(Stringium.toolName)] Found \(invalidDefinitionNames.count) definition (\(invalidDefinitionNames.joined(separator: ", "))") | ||||
|         } | ||||
|  | ||||
|   | ||||
| @@ -5,9 +5,9 @@ | ||||
| //  Created by Thibaut Schmitt on 10/01/2022. | ||||
| // | ||||
|  | ||||
| import ToolCore | ||||
| import Foundation | ||||
| import ArgumentParser | ||||
| import Foundation | ||||
| import ToolCore | ||||
|  | ||||
| struct Stringium: ParsableCommand { | ||||
|  | ||||
| @@ -47,31 +47,37 @@ struct Stringium: ParsableCommand { | ||||
|         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") | ||||
|     } | ||||
| @@ -85,26 +91,28 @@ struct Stringium: ParsableCommand { | ||||
|         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 | ||||
|         } | ||||
|   | ||||
| @@ -8,6 +8,7 @@ | ||||
| import Foundation | ||||
|  | ||||
| enum StringiumError: Error { | ||||
|  | ||||
|     case fileNotExists(String) | ||||
|     case langsListEmpty | ||||
|     case defaultLangsNotInLangs | ||||
| @@ -25,10 +26,10 @@ enum StringiumError: Error { | ||||
|         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." | ||||
|             } | ||||
|   | ||||
| @@ -5,10 +5,13 @@ | ||||
| //  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 | ||||
|  | ||||
| @@ -46,6 +49,7 @@ struct StringiumOptions: ParsableArguments { | ||||
| // MARK: - Private var getter | ||||
|  | ||||
| extension StringiumOptions { | ||||
|  | ||||
|     var stringsFileOutputPath: String { | ||||
|         var outputPath = outputPathRaw | ||||
|         if outputPath.last == "/" { | ||||
| @@ -70,6 +74,7 @@ extension StringiumOptions { | ||||
| // MARK: - Computed var | ||||
|  | ||||
| extension StringiumOptions { | ||||
|  | ||||
|     var extensionFileName: String { | ||||
|         "\(extensionName)+\(extensionSuffix).swift" | ||||
|     } | ||||
|   | ||||
| @@ -5,9 +5,9 @@ | ||||
| //  Created by Thibaut Schmitt on 10/01/2022. | ||||
| // | ||||
|  | ||||
| import ToolCore | ||||
| import Foundation | ||||
| import ArgumentParser | ||||
| import Foundation | ||||
| import ToolCore | ||||
|  | ||||
| struct Strings: ParsableCommand { | ||||
|  | ||||
| @@ -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() | ||||
|   | ||||
| @@ -5,9 +5,9 @@ | ||||
| //  Created by Thibaut Schmitt on 10/01/2022. | ||||
| // | ||||
|  | ||||
| import ToolCore | ||||
| import Foundation | ||||
| import ArgumentParser | ||||
| import Foundation | ||||
| import ToolCore | ||||
|  | ||||
| struct Tags: ParsableCommand { | ||||
|  | ||||
| @@ -43,12 +43,14 @@ struct Tags: ParsableCommand { | ||||
|         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") | ||||
|     } | ||||
| @@ -66,9 +68,11 @@ struct Tags: ParsableCommand { | ||||
|         } | ||||
|  | ||||
|         // 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 | ||||
|         } | ||||
|   | ||||
| @@ -5,10 +5,13 @@ | ||||
| //  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 | ||||
|  | ||||
| @@ -34,8 +37,9 @@ 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" | ||||
|   | ||||
| @@ -5,9 +5,9 @@ | ||||
| //  Created by Thibaut Schmitt on 10/01/2022. | ||||
| // | ||||
|  | ||||
| import ToolCore | ||||
| import Foundation | ||||
| import ArgumentParser | ||||
| import Foundation | ||||
| import ToolCore | ||||
|  | ||||
| struct Twine: ParsableCommand { | ||||
|  | ||||
| @@ -23,11 +23,11 @@ struct Twine: ParsableCommand { | ||||
|     static let toolName = "Twine" | ||||
|     static let defaultExtensionName = "String" | ||||
|     static let twineExecutable: String = { | ||||
|         #if os(macOS) | ||||
| #if os(macOS) | ||||
|         "\(FileManager.default.homeDirectoryForCurrentUser.relativePath)/scripts/twine/twine" | ||||
|         #else | ||||
| #else | ||||
|         fatalError("This command should run on Mac only") | ||||
|         #endif | ||||
| #endif | ||||
|     }() | ||||
|  | ||||
|     // MARK: - Command Options | ||||
| @@ -46,20 +46,33 @@ struct Twine: ParsableCommand { | ||||
|  | ||||
|         // 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") | ||||
|     } | ||||
| @@ -73,26 +86,28 @@ struct Twine: ParsableCommand { | ||||
|         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 | ||||
|         } | ||||
|   | ||||
| @@ -8,6 +8,7 @@ | ||||
| import Foundation | ||||
|  | ||||
| enum TwineError: Error { | ||||
|  | ||||
|     case fileNotExists(String) | ||||
|     case langsListEmpty | ||||
|     case defaultLangsNotInLangs | ||||
|   | ||||
| @@ -5,10 +5,13 @@ | ||||
| //  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 | ||||
|  | ||||
| @@ -31,6 +34,7 @@ struct TwineOptions: ParsableArguments { | ||||
| // MARK: - Private var getter | ||||
|  | ||||
| extension TwineOptions { | ||||
|  | ||||
|     var langs: [String] { | ||||
|         langsRaw | ||||
|             .split(separator: " ") | ||||
| @@ -41,6 +45,7 @@ extension TwineOptions { | ||||
| // MARK: - Computed var | ||||
|  | ||||
| extension TwineOptions { | ||||
|  | ||||
|     var inputFilenameWithoutExt: String { | ||||
|         URL(fileURLWithPath: inputFile) | ||||
|             .deletingPathExtension() | ||||
|   | ||||
| @@ -5,9 +5,9 @@ | ||||
| //  Created by Thibaut Schmitt on 13/12/2021. | ||||
| // | ||||
|  | ||||
| import ToolCore | ||||
| import Foundation | ||||
| import ArgumentParser | ||||
| import Foundation | ||||
| import ToolCore | ||||
|  | ||||
| struct ResgenSwift: ParsableCommand { | ||||
|  | ||||
| @@ -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 | ||||
|     ) | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -7,7 +7,7 @@ | ||||
|  | ||||
| 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 { | ||||
| @@ -39,5 +39,4 @@ public class GeneratorChecker { | ||||
|  | ||||
|         return false | ||||
|     } | ||||
|      | ||||
| } | ||||
|   | ||||
| @@ -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 } | ||||
|     } | ||||
|   | ||||
| @@ -7,7 +7,7 @@ | ||||
|  | ||||
| import Foundation | ||||
|  | ||||
| public class Shell { | ||||
| public enum Shell { | ||||
|  | ||||
|     public static var environment: [String: String] { | ||||
|         ProcessInfo.processInfo.environment | ||||
| @@ -18,7 +18,7 @@ public class Shell { | ||||
|         launchPath: String = "/usr/bin/env", | ||||
|         _ args: [String] | ||||
|     ) -> (terminationStatus: Int32, output: String?) { | ||||
|         #if os(macOS) | ||||
| #if os(macOS) | ||||
|         let task = Process() | ||||
|         task.launchPath = launchPath | ||||
|         task.arguments = args | ||||
| @@ -36,13 +36,13 @@ public class Shell { | ||||
|  | ||||
|         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 | ||||
| #else | ||||
|         fatalError("Shell is only available on Mac") | ||||
|         #endif | ||||
| #endif | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -7,25 +7,26 @@ | ||||
|  | ||||
| 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 { | ||||
| @@ -35,7 +36,7 @@ public extension String { | ||||
|         return newString | ||||
|     } | ||||
|  | ||||
|     func removeLeadingWhitespace() -> Self { | ||||
|     public func removeLeadingWhitespace() -> Self { | ||||
|         var newString = self | ||||
|  | ||||
|         while newString.first?.isWhitespace == true { | ||||
| @@ -45,7 +46,7 @@ public extension String { | ||||
|         return newString | ||||
|     } | ||||
|  | ||||
|     func removeLeadingTrailingWhitespace() -> Self { | ||||
|     public func removeLeadingTrailingWhitespace() -> Self { | ||||
|         var newString = self | ||||
|  | ||||
|         newString = newString.removeLeadingWhitespace() | ||||
| @@ -54,28 +55,28 @@ public extension String { | ||||
|         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) | ||||
| #if os(macOS) | ||||
|         replacingOccurrences(of: "~", with: "\(FileManager.default.homeDirectoryForCurrentUser.relativePath)") | ||||
|         #else | ||||
| #else | ||||
|         fatalError("This command should run on Mac only") | ||||
|         #endif | ||||
| #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)) | ||||
| @@ -90,11 +91,11 @@ public extension String { | ||||
|         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, | ||||
|   | ||||
| @@ -7,4 +7,6 @@ | ||||
|  | ||||
| import Foundation | ||||
|  | ||||
| // swiftlint:disable prefixed_toplevel_constant identifier_name | ||||
|  | ||||
| public let ResgenSwiftVersion = "2.1.0" | ||||
|   | ||||
| @@ -51,11 +51,14 @@ 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 \(ResgenSwiftVersion) | ||||
| @@ -65,6 +68,7 @@ final class AnalyticsGeneratorTests: XCTestCase { | ||||
|         // MARK: - Protocol | ||||
|  | ||||
|         protocol AnalyticsManagerProtocol { | ||||
|  | ||||
|             func logScreen(name: String, path: String) | ||||
|             func logEvent( | ||||
|                 name: String, | ||||
| @@ -77,6 +81,7 @@ final class AnalyticsGeneratorTests: XCTestCase { | ||||
|         // MARK: - Firebase | ||||
|  | ||||
|         class FirebaseAnalyticsManager: AnalyticsManagerProtocol { | ||||
|  | ||||
|             func logScreen(name: String, path: String) { | ||||
|                 var parameters = [ | ||||
|                     AnalyticsParameterScreenName: name as NSObject | ||||
| @@ -121,6 +126,7 @@ final class AnalyticsGeneratorTests: XCTestCase { | ||||
|         // MARK: - Manager | ||||
|  | ||||
|         class AnalyticsManager { | ||||
|  | ||||
|             static var shared = AnalyticsManager() | ||||
|  | ||||
|             // MARK: - Properties | ||||
| @@ -222,11 +228,13 @@ 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 \(ResgenSwiftVersion) | ||||
| @@ -236,6 +244,7 @@ final class AnalyticsGeneratorTests: XCTestCase { | ||||
|         // MARK: - Protocol | ||||
|  | ||||
|         protocol AnalyticsManagerProtocol { | ||||
|  | ||||
|             func logScreen(name: String, path: String) | ||||
|             func logEvent( | ||||
|                 name: String, | ||||
| @@ -309,6 +318,7 @@ final class AnalyticsGeneratorTests: XCTestCase { | ||||
|         // MARK: - Manager | ||||
|  | ||||
|         class AnalyticsManager { | ||||
|  | ||||
|             static var shared = AnalyticsManager() | ||||
|  | ||||
|             // MARK: - Properties | ||||
| @@ -415,11 +425,14 @@ 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 \(ResgenSwiftVersion) | ||||
| @@ -430,6 +443,7 @@ final class AnalyticsGeneratorTests: XCTestCase { | ||||
|         // MARK: - Protocol | ||||
|  | ||||
|         protocol AnalyticsManagerProtocol { | ||||
|  | ||||
|             func logScreen(name: String, path: String) | ||||
|             func logEvent( | ||||
|                 name: String, | ||||
| @@ -547,6 +561,7 @@ final class AnalyticsGeneratorTests: XCTestCase { | ||||
|         // MARK: - Manager | ||||
|  | ||||
|         class AnalyticsManager { | ||||
|  | ||||
|             static var shared = AnalyticsManager() | ||||
|  | ||||
|             // MARK: - Properties | ||||
|   | ||||
		Reference in New Issue
	
	Block a user