diff --git a/README.md b/README.md index 3b85ede..e3f9578 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,13 @@ ResgenSwift is a package, fully written in Swift, to help you automatize ressource update and generation. -> 🧐 For all commands, see samples files in `SampleFiles` +> 🧐 For all commands, see samples files in `SampleFiles` and use `resgen-swift help` and `resgen-swift help ` for detailed help. ## Fonts Font generator generates an extension of `UIFont` and `Font` (or custom classes). It also prints content of `UIAppFonts` from your project `.plist`. If project `.plist` is specified, it will update `UIAppFonts` content of all `.plist`. -iOS required to use the **real name** of the font, this name can be different from its filename. To get the **real name**, it uses `fc-scan`. So, be sure that the `$PATH` contains path of `fc-scan`. +iOS required to use the **real name** of the font, this name can be different from its filename. To get the **real name**, it uses `fc-scan`. So, be sure that your `$PATH` variable contains path of `fc-scan`. **Example** @@ -36,7 +36,7 @@ swift run -c release ResgenSwift fonts $FORCE_FLAG "./Fonts/fonts.txt" \ ## Colors -Colors generator generates an extension of `UIColor` (or a custom class) along with colorsets in specified xcassets. +Colors generator generates colorsets in specified xcassets and an extension of `Color` (or a custom class) associated to those colorsets. If the extension name is not specified, no extension will be generated. ```sh swift run -c release ResgenSwift colors $FORCE_FLAG "./Colors/colors.txt" \ @@ -54,12 +54,13 @@ swift run -c release ResgenSwift colors $FORCE_FLAG "./Colors/colors.txt" \ 1. `-f`: force generation 2. Input colors file 3. `--style` can be `all` or `light` -4. `--extension-output-path`: path where to generate generated extension +4. `--extension-output-path` *(optional)* : path where to generate generated extension 5. `--extension-name` *(optional)* : name of the class to add SwiftUI getters 6. `--extension-name-ui-kit` *(optional)* : name of the class to add UIKit getters 7. `--extension-suffix` *(optional)* : additional text which is added to filename (ex: `AppColor+GreatApp.swift`) 8. `--static-members` *(optional)*: generate static properties or not +> ⚠️ Passing a `extensionOutputPath` without any `extensionName` does not generate extension. **But** passing an `extensionName` without `extensionOutputPath` will result in a error. ## Strings @@ -266,7 +267,7 @@ events: ## Images -Images generator will generate images assets along with extensions to access those images easily. +Images generator will generate images assets along with extensions to access those images easily. If the extension name is not specified, no extension will be generated. ```sh swift run -c release ResgenSwift images $FORCE_FLAG "./Images/images.txt" \ @@ -283,12 +284,13 @@ swift run -c release ResgenSwift images $FORCE_FLAG "./Images/images.txt" \ 1. `-f`: force generation 2. Input images definitions file 3. `--xcassets-path`: xcasset path where to generate imagesets -4. `--extension-output-path`: path where to generate generated extension +4. `--extension-output-path` *(optional)* : path where to generate generated extension 5. `--extension-name` *(optional)* : name of the class to add SwiftUI getters 6. `--extension-name-ui-kit` *(optional)* : name of the class to add UIKit getters 6. `--extension-suffix` *(optional)* : additional text which is added to filename (ex: `AppImage+GreatApp.swift`) 7. `--static-members` *(optional)*: generate static properties or not +> ⚠️ Passing a `extensionOutputPath` without any `extensionName` does not generate extension. **But** passing an `extensionName` without `extensionOutputPath` will result in a error. > ⚠️ Svg images will be copied in the assets and rendered as "Original", however if those images are not rendered correctly you can force the png generation by adding the key word "png" like this: id arrow_back 15 ? png ## All at once diff --git a/SampleFiles/Colors/Generated/UIColorYolo+GenAllScript.swift b/SampleFiles/Colors/Generated/UIColorYolo+GenAllScript.swift index d2c7564..28b639c 100644 --- a/SampleFiles/Colors/Generated/UIColorYolo+GenAllScript.swift +++ b/SampleFiles/Colors/Generated/UIColorYolo+GenAllScript.swift @@ -1,4 +1,4 @@ -// Generated by ResgenSwift.Color 1.2 +// Generated by ResgenSwift.Color 2.1.0 import UIKit diff --git a/SampleFiles/Strings/Generated/sampleStrings.xcstrings b/SampleFiles/Strings/Generated/sampleStrings.xcstrings new file mode 100644 index 0000000..323caba --- /dev/null +++ b/SampleFiles/Strings/Generated/sampleStrings.xcstrings @@ -0,0 +1,150 @@ +{ + "sourceLanguage" : "en", + "strings" : { + "generic_back" : { + "comment" : "", + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Back" + } + }, + "en-us" : { + "stringUnit" : { + "state" : "translated", + "value" : "Back" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Retour" + } + } + } + }, + "generic_loading_data" : { + "comment" : "", + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loading data..." + } + }, + "en-us" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loading data..." + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Chargement des données..." + } + } + } + }, + "generic_welcome_firstname_format" : { + "comment" : "", + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Welcome \\\"%@\\\" !" + } + }, + "en-us" : { + "stringUnit" : { + "state" : "translated", + "value" : "Welcome \\\"%@\\\" !" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bienvenue \\\"%@\\\" !" + } + } + } + }, + "param_lang" : { + "comment" : "", + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "en" + } + }, + "en-us" : { + "stringUnit" : { + "state" : "translated", + "value" : "en-us" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "fr" + } + } + } + }, + "placeholders_test_one" : { + "comment" : "", + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "You %%: %2$@ %1$@ Age: %3$d" + } + }, + "en-us" : { + "stringUnit" : { + "state" : "translated", + "value" : "You %%: %2$@ %1$@ Age: %3$d" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vous %%: %1$@ %2$@ Age: %3$d" + } + } + } + }, + "test_equal_symbol" : { + "comment" : "", + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "1€ = 1 point !" + } + }, + "en-us" : { + "stringUnit" : { + "state" : "translated", + "value" : "1€ = 1 point !" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "1€ = 1 point !" + } + } + } + } + }, + "version" : "1.0" +} \ No newline at end of file diff --git a/SampleFiles/Tags/Generated/Analytics+GenAllScript.swift b/SampleFiles/Tags/Generated/Analytics+GenAllScript.swift index 8742801..4dc0cd6 100644 --- a/SampleFiles/Tags/Generated/Analytics+GenAllScript.swift +++ b/SampleFiles/Tags/Generated/Analytics+GenAllScript.swift @@ -1,11 +1,13 @@ // Generated by ResgenSwift.Analytics 2.1.0 +import Foundation import MatomoTracker import FirebaseAnalytics // MARK: - Protocol protocol AnalyticsManagerProtocol { + func logScreen( name: String, path: String, @@ -22,75 +24,12 @@ protocol AnalyticsManagerProtocol { func setEnable(_ enable: Bool) } -// MARK: - Matomo - -class MatomoAnalyticsManager: AnalyticsManagerProtocol { - - // MARK: - Properties - - private var tracker: MatomoTracker - - // MARK: - Init - - init(siteId: String, url: String) { - debugPrint("[Matomo service] Server URL: \(url)") - debugPrint("[Matomo service] Site ID: \(siteId)") - tracker = MatomoTracker( - siteId: siteId, - baseURL: URL(string: url)! - ) - - #if DEBUG - tracker.dispatchInterval = 5 - #endif - - #if DEBUG - tracker.logger = DefaultLogger(minLevel: .verbose) - #endif - - debugPrint("[Matomo service] Configured with content base: \(tracker.contentBase?.absoluteString ?? "-")") - debugPrint("[Matomo service] Opt out: \(tracker.isOptedOut)") - } - - // MARK: - Methods - - func logScreen( - name: String, - path: String, - params: [String: Any]? - ) { - guard let trackerUrl = tracker.contentBase?.absoluteString else { return } - - let urlString = URL(string: "\(trackerUrl)" + "/" + "\(path)" + "iOS") - tracker.track( - view: [name], - url: urlString - ) - } - - func logEvent( - name: String, - action: String, - category: String, - params: [String: Any]? - ) { - tracker.track( - eventWithCategory: category, - action: action, - name: name, - number: nil, - url: nil - ) - } - - func setEnable(_ enable: Bool) { - tracker.isOptedOut = !enable - } -} - // MARK: - Firebase class FirebaseAnalyticsManager: AnalyticsManagerProtocol { + + // MARK: - Methods + func logScreen( name: String, path: String, @@ -157,16 +96,16 @@ class FirebaseAnalyticsManager: AnalyticsManagerProtocol { parameters: parameters ) } + func setEnable(_ enable: Bool) { Analytics.setAnalyticsCollectionEnabled(enable) } } - // MARK: - Traker Type enum TrackerType: CaseIterable { - case matomo + case firebase } @@ -190,7 +129,7 @@ class AnalyticsManager { } } - // MARK: - Methods + // MARK: - Enable Methods private func setAnalytics(enable: Bool, _ analytics: [TrackerType]) { managers.forEach { (key, value) in @@ -210,14 +149,12 @@ class AnalyticsManager { setAnalytics(enable: false, analytics) } - func configure(siteId: String, url: String) { - managers[TrackerType.matomo] = MatomoAnalyticsManager( - siteId: siteId, - url: url - ) + func configure() { managers[TrackerType.firebase] = FirebaseAnalyticsManager() } + // MARK: - Private Log Methods + private func logScreen( name: String, path: String, @@ -262,12 +199,12 @@ class AnalyticsManager { ) } - static func logEventS1DefTwo( + func logEventS1DefTwo( title: String, count: String, test2: String = "test" ) { - AnalyticsManager.shared.logEvent( + logEvent( name: "s1 def two", action: "test", category: "test", diff --git a/SampleFiles/genAllRessources.sh b/SampleFiles/genAllRessources.sh index 3de2c99..536a461 100755 --- a/SampleFiles/genAllRessources.sh +++ b/SampleFiles/genAllRessources.sh @@ -54,7 +54,7 @@ echo "\n-------------------------\n" # Analytics swift run -c release ResgenSwift analytics $FORCE_FLAG "./Tags/sampleTags.yml" \ - --target "firebase matomo" \ + --target "firebase" \ --extension-output-path "./Tags/Generated" \ --extension-name "Analytics" \ --extension-suffix "GenAllScript" \ diff --git a/Sources/ResgenSwift/Analytics/Generator/AnalyticsGenerator.swift b/Sources/ResgenSwift/Analytics/Generator/AnalyticsGenerator.swift index 0cff0cf..8e775cd 100644 --- a/Sources/ResgenSwift/Analytics/Generator/AnalyticsGenerator.swift +++ b/Sources/ResgenSwift/Analytics/Generator/AnalyticsGenerator.swift @@ -5,11 +5,14 @@ // Created by Loris Perret on 08/12/2023. // +// CPD-OFF + import CoreVideo import Foundation import ToolCore // Disabled cause it's a pain to handle in generated string +// swiftlint:disable type_body_length enum AnalyticsGenerator { @@ -89,48 +92,49 @@ enum AnalyticsGenerator { ) -> String { """ // Generated by ResgenSwift.\(Analytics.toolName) \(ResgenSwiftVersion) - + \(Self.getImport(targets: targets)) - + \(Self.getAnalyticsProtocol(targets: targets)) - - \(Self.getTrackerTypeEnum()) - + + \(Self.getTrackerTypeEnum(targets: targets)) + // MARK: - Manager class AnalyticsManager { - + static var shared = AnalyticsManager() - + private init() {} // MARK: - Properties - + var managers: [TrackerType: AnalyticsManagerProtocol] = [:] - + \(Self.getEnabledContent()) - + \(Self.getAnalyticsProperties(targets: targets)) - + \(Self.getPrivateLogFunction()) """ } - - private static func getTrackerTypeEnum() -> String { + + private static func getTrackerTypeEnum(targets: [TrackerType]) -> String { var result: [String] = [] - TrackerType.allCases.forEach { type in + targets.forEach { type in result.append(" case \(type)") } - + return """ // MARK: - Traker Type - + enum TrackerType: CaseIterable { + \(result.joined(separator: "\n")) } """ } - + private static func getEnabledContent() -> String { """ private var isEnabled: Bool { @@ -140,9 +144,9 @@ enum AnalyticsGenerator { true } } - - // MARK: - Methods - + + // MARK: - Enable Methods + private func setAnalytics(enable: Bool, _ analytics: [TrackerType]) { managers.forEach { (key, value) in if analytics.contains(where: { type in @@ -152,11 +156,11 @@ enum AnalyticsGenerator { } } } - + func enableAnalytics(_ analytics: [TrackerType] = TrackerType.allCases) { setAnalytics(enable: true, analytics) } - + func disableAnalytics(_ analytics: [TrackerType] = TrackerType.allCases) { setAnalytics(enable: false, analytics) } @@ -171,6 +175,7 @@ enum AnalyticsGenerator { if targets.contains(TrackerType.matomo) { result.append("import MatomoTracker") } + if targets.contains(TrackerType.firebase) { result.append("import FirebaseAnalytics") } @@ -180,13 +185,15 @@ enum AnalyticsGenerator { private static func getPrivateLogFunction() -> String { """ - private func logScreen( + // MARK: - Private Log Methods + + private func logScreen( name: String, path: String, params: [String: Any]? ) { guard isEnabled else { return } - + managers.values.forEach { manager in manager.logScreen( name: name, @@ -203,7 +210,7 @@ enum AnalyticsGenerator { params: [String: Any]? ) { guard isEnabled else { return } - + managers.values.forEach { manager in manager.logEvent( name: name, @@ -235,6 +242,7 @@ enum AnalyticsGenerator { ) """) } + if targets.contains(TrackerType.firebase) { content.append(" managers[TrackerType.firebase] = FirebaseAnalyticsManager()") } @@ -252,22 +260,22 @@ enum AnalyticsGenerator { // MARK: - Protocol protocol AnalyticsManagerProtocol { + func logScreen( name: String, path: String, params: [String: Any]? ) - + func logEvent( name: String, action: String, category: String, params: [String: Any]? ) - + func setEnable(_ enable: Bool) } - """ var result: [String] = [proto] @@ -280,7 +288,7 @@ enum AnalyticsGenerator { result.append(FirebaseGenerator.service) } - return result.joined(separator: "\n") + return result.joined(separator: "\n\n") } private static func getProperties( @@ -319,3 +327,5 @@ enum AnalyticsGenerator { """ } } + +// CPD-ON diff --git a/Sources/ResgenSwift/Analytics/Generator/FirebaseGenerator.swift b/Sources/ResgenSwift/Analytics/Generator/FirebaseGenerator.swift index 8d2d22d..3e8ff0f 100644 --- a/Sources/ResgenSwift/Analytics/Generator/FirebaseGenerator.swift +++ b/Sources/ResgenSwift/Analytics/Generator/FirebaseGenerator.swift @@ -13,11 +13,11 @@ enum FirebaseGenerator { static var service: String { [ - FirebaseGenerator.header, - FirebaseGenerator.logScreen, - FirebaseGenerator.logEvent, - FirebaseGenerator.enable, - FirebaseGenerator.footer + Self.header, + Self.logScreen, + Self.logEvent, + Self.enable, + Self.footer ] .joined(separator: "\n") } @@ -29,6 +29,9 @@ enum FirebaseGenerator { // MARK: - Firebase class FirebaseAnalyticsManager: AnalyticsManagerProtocol { + + // MARK: - Methods + """ } @@ -42,11 +45,11 @@ enum FirebaseGenerator { var parameters = [ AnalyticsParameterScreenName: name as NSObject ] - + if path.isEmpty == false { parameters["path"] = path + "/iOS" as NSObject } - + if let supplementaryParameters = params { for (newKey, newValue) in supplementaryParameters { if parameters.contains(where: { (key: String, value: NSObject) in @@ -54,11 +57,11 @@ enum FirebaseGenerator { }) { continue } - + parameters[newKey] = newValue as? NSObject } } - + Analytics.logEvent( AnalyticsEventScreenView, parameters: parameters @@ -79,15 +82,15 @@ enum FirebaseGenerator { var parameters: [String:NSObject] = [ AnalyticsParameterItemName: name.replacingOccurrences(of: " ", with: "_") as NSObject ] - + if category.isEmpty == false { parameters["AnalyticsParameterItemCategory"] = category as NSObject } - + if action.isEmpty == false { parameters["action"] = action as NSObject } - + if let supplementaryParameters = params { for (newKey, newValue) in supplementaryParameters { if parameters.contains(where: { (key: String, value: NSObject) in @@ -105,9 +108,10 @@ enum FirebaseGenerator { parameters: parameters ) } + """ } - + private static var enable: String { """ func setEnable(_ enable: Bool) { @@ -115,11 +119,10 @@ enum FirebaseGenerator { } """ } - + private static var footer: String { """ } - """ } } diff --git a/Sources/ResgenSwift/Analytics/Generator/MatomoGenerator.swift b/Sources/ResgenSwift/Analytics/Generator/MatomoGenerator.swift index 0217328..dd8c9bc 100644 --- a/Sources/ResgenSwift/Analytics/Generator/MatomoGenerator.swift +++ b/Sources/ResgenSwift/Analytics/Generator/MatomoGenerator.swift @@ -5,18 +5,20 @@ // Created by Loris Perret on 05/12/2023. // +// CPD-OFF + import Foundation enum MatomoGenerator { static var service: String { [ - MatomoGenerator.header, - MatomoGenerator.setup, - MatomoGenerator.logScreen, - MatomoGenerator.logEvent, - MatomoGenerator.enable, - MatomoGenerator.footer + Self.header, + Self.setup, + Self.logScreen, + Self.logEvent, + Self.enable, + Self.footer ] .joined(separator: "\n") } @@ -100,10 +102,10 @@ enum MatomoGenerator { url: nil ) } - + """ } - + private static var enable: String { """ func setEnable(_ enable: Bool) { @@ -115,7 +117,8 @@ enum MatomoGenerator { private static var footer: String { """ } - """ } } + +// CPD-ON diff --git a/Sources/ResgenSwift/Analytics/Model/AnalyticsDefinition.swift b/Sources/ResgenSwift/Analytics/Model/AnalyticsDefinition.swift index c84bf1c..1946cbf 100644 --- a/Sources/ResgenSwift/Analytics/Model/AnalyticsDefinition.swift +++ b/Sources/ResgenSwift/Analytics/Model/AnalyticsDefinition.swift @@ -52,20 +52,22 @@ class AnalyticsDefinition { private func getParameters() -> String { var result: String - + let paramsString = parameters.compactMap { parameter -> String? in guard parameter.value.isEmpty else { return nil } - + let defaultValue: String switch parameter.type { case .bool: defaultValue = "\(parameter.defaultValue.lowercased())" + case .int, .double: defaultValue = "\(parameter.defaultValue)" + case .string: defaultValue = "\"\(parameter.defaultValue)\"" } - + let defaultValueString = parameter.defaultValue.isEmpty ? "" : " = \(defaultValue)" return "\(parameter.name): \(parameter.type.rawValue)\(defaultValueString)" } @@ -90,9 +92,13 @@ class AnalyticsDefinition { for rep in parameter.replaceIn { switch rep { case "name": name = name.replacingFirstOccurrence(of: "_\(parameter.name.uppercased())_", with: "\\(\(parameter.name))") + case "path": path = path.replacingFirstOccurrence(of: "_\(parameter.name.uppercased())_", with: "\\(\(parameter.name))") + case "category": category = category.replacingFirstOccurrence(of: "_\(parameter.name.uppercased())_", with: "\\(\(parameter.name))") + case "action": action = action.replacingFirstOccurrence(of: "_\(parameter.name.uppercased())_", with: "\\(\(parameter.name))") + default: if let param = parameters.first(where: { $0.name == rep }), param.value.isEmpty == false { param.value = param.value.replacingFirstOccurrence(of: "_\(parameter.name.uppercased())_", with: "\\(\(parameter.name))") @@ -117,8 +123,10 @@ class AnalyticsDefinition { switch param.type { case .bool: params.append("\"\(param.name)\": \(param.value.lowercased())") + case .int, .double: params.append("\"\(param.name)\": \(param.value)") + case .string: params.append("\"\(param.name)\": \"\(param.value)\"") } diff --git a/Sources/ResgenSwift/Analytics/Model/AnalyticsParameter.swift b/Sources/ResgenSwift/Analytics/Model/AnalyticsParameter.swift index 42dd8b5..1accfa2 100644 --- a/Sources/ResgenSwift/Analytics/Model/AnalyticsParameter.swift +++ b/Sources/ResgenSwift/Analytics/Model/AnalyticsParameter.swift @@ -18,7 +18,7 @@ class AnalyticsParameter { var replaceIn: [String] = [] // MARK: - Init - + init(name: String, type: ParameterType, value: String, defaultValue: String) { self.name = name self.type = type diff --git a/Sources/ResgenSwift/Analytics/Model/ParameterType.swift b/Sources/ResgenSwift/Analytics/Model/ParameterType.swift index 1f60ef9..6cf8519 100644 --- a/Sources/ResgenSwift/Analytics/Model/ParameterType.swift +++ b/Sources/ResgenSwift/Analytics/Model/ParameterType.swift @@ -8,6 +8,7 @@ import Foundation enum ParameterType: String { + case string = "String" case int = "Int" case double = "Double" diff --git a/Sources/ResgenSwift/Analytics/Parser/AnalyticsFileParser.swift b/Sources/ResgenSwift/Analytics/Parser/AnalyticsFileParser.swift index 64a5cec..6b2460d 100644 --- a/Sources/ResgenSwift/Analytics/Parser/AnalyticsFileParser.swift +++ b/Sources/ResgenSwift/Analytics/Parser/AnalyticsFileParser.swift @@ -70,7 +70,7 @@ class AnalyticsFileParser { private static func getParameters(from parameters: [AnalyticsParameterDTO]) -> [AnalyticsParameter] { func verify(value: String?, for type: ParameterType) { guard let value, value.isEmpty == false else { return } - + switch type { case .int: if Int(value) == nil { @@ -78,23 +78,26 @@ class AnalyticsFileParser { print(error.description) Analytics.exit(withError: error) } + case .bool: if Bool(value.lowercased()) == nil { let error = AnalyticsError.invalidParameter("type of \(value) is not \(type)") print(error.description) Analytics.exit(withError: error) } + case .double: if Double(value) == nil { let error = AnalyticsError.invalidParameter("type of \(value) is not \(type)") print(error.description) Analytics.exit(withError: error) } + case .string: break } } - + return parameters.map { dtoParameter in // Type let type = dtoParameter.type.uppercasedFirst() @@ -104,13 +107,13 @@ class AnalyticsFileParser { print(error.description) Analytics.exit(withError: error) } - + if dtoParameter.value != nil, dtoParameter.replaceIn != nil { let error = AnalyticsError.invalidParameter("you can't set 'value' and 'replaceIn' for \(dtoParameter.name)") print(error.description) Analytics.exit(withError: error) } - + verify(value: dtoParameter.value, for: typeEnum) verify(value: dtoParameter.defaultValue, for: typeEnum) @@ -147,7 +150,7 @@ class AnalyticsFileParser { } if let parameters { - definition.parameters = AnalyticsFileParser.getParameters(from: parameters) + definition.parameters = Self.getParameters(from: parameters) } return definition @@ -215,7 +218,7 @@ class AnalyticsFileParser { if let category = event.category { definition.category = category } - + if let action = event.action { definition.action = action } diff --git a/Sources/ResgenSwift/Colors/Colors.swift b/Sources/ResgenSwift/Colors/Colors.swift index 0d5cab2..f8ba0cf 100644 --- a/Sources/ResgenSwift/Colors/Colors.swift +++ b/Sources/ResgenSwift/Colors/Colors.swift @@ -21,8 +21,6 @@ struct Colors: ParsableCommand { // MARK: - Static static let toolName = "Color" - static let defaultExtensionName = "Color" - static let defaultExtensionNameUIKit = "UIColor" static let assetsColorsFolderName = "Colors" // MARK: - Command options @@ -57,22 +55,28 @@ struct Colors: ParsableCommand { // -> Time: 3.4505380392074585 seconds // Generate extension - ColorExtensionGenerator.writeExtensionFile( - colors: parsedColors, - staticVar: options.staticMembers, - extensionName: options.extensionName, - extensionFilePath: options.extensionFilePath, - isSwiftUI: true - ) + if let extensionName = options.extensionName, + let extensionFilePath = options.extensionFilePath { + ColorExtensionGenerator.writeExtensionFile( + colors: parsedColors, + staticVar: options.staticMembers, + extensionName: extensionName, + extensionFilePath: extensionFilePath, + isSwiftUI: true + ) + } // Generate extension - ColorExtensionGenerator.writeExtensionFile( - colors: parsedColors, - staticVar: options.staticMembers, - extensionName: options.extensionNameUIKit, - extensionFilePath: options.extensionFilePathUIKit, - isSwiftUI: false - ) + if let extensionNameUIKit = options.extensionNameUIKit, + let extensionFilePathUIKit = options.extensionFilePathUIKit { + ColorExtensionGenerator.writeExtensionFile( + colors: parsedColors, + staticVar: options.staticMembers, + extensionName: extensionNameUIKit, + extensionFilePath: extensionFilePathUIKit, + isSwiftUI: false + ) + } print("[\(Self.toolName)] Colors generated") } @@ -96,18 +100,40 @@ struct Colors: ParsableCommand { 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) - Self.exit(withError: error) + // Extension for UIKit and SwiftUI should have different name (if both are defined) + if let extensionName = options.extensionName, + let extensionNameUIKit = options.extensionNameUIKit { + guard extensionName != extensionNameUIKit else { + let error = ColorsToolError.extensionNamesCollision(extensionName) + print(error.description) + Self.exit(withError: error) + } + } + + // If an extension need to be generated, ensure extensionOutputPath is defined + if options.extensionName != nil || + options.extensionNameUIKit != nil { + guard let extensionOutputPath = options.extensionOutputPath, + extensionOutputPath.isEmpty == false else { + let error = ColorsToolError.missingExtensionPath + print(error.description) + Self.exit(withError: error) + } } // Check if needed to regenerate + let fileToCompareToInput: String = { + // If there is no extension file to compare + // Then check the xcassets file instead + if let extensionFilePath = options.extensionFilePath { + return extensionFilePath + } + return options.xcassetsPath + }() guard GeneratorChecker.shouldGenerate( force: options.forceGeneration, inputFilePath: options.inputFile, - extensionFilePath: options.extensionFilePath + extensionFilePath: fileToCompareToInput ) else { print("[\(Self.toolName)] Colors are already up to date :) ") return false diff --git a/Sources/ResgenSwift/Colors/ColorsToolError.swift b/Sources/ResgenSwift/Colors/ColorsToolError.swift index 2a54abf..a0e2290 100644 --- a/Sources/ResgenSwift/Colors/ColorsToolError.swift +++ b/Sources/ResgenSwift/Colors/ColorsToolError.swift @@ -17,11 +17,12 @@ enum ColorsToolError: Error { case fileNotExists(String) case badColorDefinition(String, String) case deleteExistingColors(String) + case missingExtensionPath var description: String { switch self { case .extensionNamesCollision(let extensionName): - return "error: [\(Fonts.toolName)] Error on extension names, extension name and SwiftUI extension name should be different (\(extensionName) is used on both)" + return "error: [\(Colors.toolName)] Error on extension names, extension name and SwiftUI extension name should be different (\(extensionName) is used on both)" case .badFormat(let info): return "error: [\(Colors.toolName)] Bad line format: \(info). Accepted format are: colorName=\"#RGB/#ARGB\"; colorName \"#RGB/#ARGB\"; colorName \"#RGB/#ARGB\" \"#RGB/#ARGB\"" @@ -43,6 +44,9 @@ enum ColorsToolError: Error { case .deleteExistingColors(let assetsFolder): return "error: [\(Colors.toolName)] An error occured while deleting colors folder `\(assetsFolder)`" + + case .missingExtensionPath: + return "error: [\(Colors.toolName)] Extension need to be generated but no `extensionOutputPath` is provided" } } } diff --git a/Sources/ResgenSwift/Colors/ColorsToolOptions.swift b/Sources/ResgenSwift/Colors/ColorsToolOptions.swift index 97eb31d..4297e8e 100644 --- a/Sources/ResgenSwift/Colors/ColorsToolOptions.swift +++ b/Sources/ResgenSwift/Colors/ColorsToolOptions.swift @@ -24,17 +24,17 @@ struct ColorsToolOptions: ParsableArguments { @Option(help: "Path of xcassets where to generate colors", transform: { $0.replaceTiltWithHomeDirectoryPath() }) var xcassetsPath: String - @Option(help: "Path where to generate the extension.", transform: { $0.replaceTiltWithHomeDirectoryPath() }) - var extensionOutputPath: String - @Option(help: "Tell if it will generate static properties or not") var staticMembers: Bool = false - @Option(help: "Extension name. If not specified, it will generate an Color extension.") - var extensionName: String = Colors.defaultExtensionName + @Option(help: "Path where to generate the extension.", transform: { $0.replaceTiltWithHomeDirectoryPath() }) + var extensionOutputPath: String? - @Option(help: "SwiftUI Extension name. If not specified, it will generate an UIColor extension.") - var extensionNameUIKit: String = Colors.defaultExtensionNameUIKit + @Option(help: "SwiftUI extension name. If not specified, no extension will be generated.") + var extensionName: String? + + @Option(help: "UIKit extension name. If not specified, no extension will be generated.") + var extensionNameUIKit: String? @Option(help: "Extension suffix. Ex: MyApp, it will generate {extensionName}+ColorsMyApp.swift") var extensionSuffix: String? @@ -46,27 +46,35 @@ extension ColorsToolOptions { // MARK: - SwiftUI - var extensionFileName: String { + var extensionFileName: String? { + guard let extensionName else { return nil } + if let extensionSuffix { return "\(extensionName)+\(extensionSuffix).swift" } return "\(extensionName).swift" } - var extensionFilePath: String { - "\(extensionOutputPath)/\(extensionFileName)" + var extensionFilePath: String? { + guard let extensionOutputPath, let extensionFileName else { return nil } + + return "\(extensionOutputPath)/\(extensionFileName)" } // MARK: - UIKit - var extensionFileNameUIKit: String { + var extensionFileNameUIKit: String? { + guard let extensionNameUIKit else { return nil } + if let extensionSuffix { return "\(extensionNameUIKit)+\(extensionSuffix).swift" } return "\(extensionNameUIKit).swift" } - var extensionFilePathUIKit: String { - "\(extensionOutputPath)/\(extensionFileNameUIKit)" + var extensionFilePathUIKit: String? { + guard let extensionOutputPath, let extensionFileNameUIKit else { return nil } + + return "\(extensionOutputPath)/\(extensionFileNameUIKit)" } } diff --git a/Sources/ResgenSwift/Fonts/FontOptions.swift b/Sources/ResgenSwift/Fonts/FontOptions.swift index 42a3218..ac14250 100644 --- a/Sources/ResgenSwift/Fonts/FontOptions.swift +++ b/Sources/ResgenSwift/Fonts/FontOptions.swift @@ -18,20 +18,20 @@ struct FontsOptions: ParsableArguments { @Argument(help: "Input files where fonts ared defined.", transform: { $0.replaceTiltWithHomeDirectoryPath() }) var inputFile: String - @Option(help: "Path where to generate the extension.", transform: { $0.replaceTiltWithHomeDirectoryPath() }) - var extensionOutputPath: String - @Option(help: "Tell if it will generate static properties or methods") var staticMembers: Bool = false + @Option(help: "Path where to generate the extension.", transform: { $0.replaceTiltWithHomeDirectoryPath() }) + var extensionOutputPath: String + @Option(help: "Extension name. If not specified, it will generate an Font extension.") var extensionName: String = Fonts.defaultExtensionName - @Option(help: "Extension name. If not specified, it will generate an UIFont extension.") - var extensionNameUIKit: String = Fonts.defaultExtensionNameUIKit + @Option(help: "Extension name. If not specified, no extension will be generated.") + var extensionNameUIKit: String? @Option(help: "Extension suffix. Ex: MyApp, it will generate {extensionName}+FontsMyApp.swift") - var extensionSuffix: String = "" + var extensionSuffix: String? @Option(name: .customLong("info-plist-paths"), help: "Info.plist paths (array). Will be used to update UIAppFonts content") fileprivate var infoPlistPathsRaw: String = "" @@ -44,7 +44,7 @@ extension FontsOptions { // MARK: - SwiftUI var extensionFileName: String { - if extensionSuffix.isEmpty == false { + if let extensionSuffix { return "\(extensionName)+\(extensionSuffix).swift" } return "\(extensionName).swift" @@ -56,15 +56,19 @@ extension FontsOptions { // MARK: - UIKit - var extensionFileNameUIKit: String { - if extensionSuffix.isEmpty == false { + var extensionFileNameUIKit: String? { + guard let extensionNameUIKit else { return nil } + + if let extensionSuffix { return "\(extensionNameUIKit)+\(extensionSuffix).swift" } return "\(extensionNameUIKit).swift" } - var extensionFilePathUIKit: String { - "\(extensionOutputPath)/\(extensionFileNameUIKit)" + var extensionFilePathUIKit: String? { + guard let extensionFileNameUIKit else { return nil } + + return "\(extensionOutputPath)/\(extensionFileNameUIKit)" } // MARK: - diff --git a/Sources/ResgenSwift/Fonts/Fonts.swift b/Sources/ResgenSwift/Fonts/Fonts.swift index 9e4bbca..82d930d 100644 --- a/Sources/ResgenSwift/Fonts/Fonts.swift +++ b/Sources/ResgenSwift/Fonts/Fonts.swift @@ -22,7 +22,6 @@ struct Fonts: ParsableCommand { static let toolName = "Fonts" static let defaultExtensionName = "Font" - static let defaultExtensionNameUIKit = "UIFont" // MARK: - Command Options @@ -61,16 +60,25 @@ struct Fonts: ParsableCommand { isSwiftUI: true ) - FontExtensionGenerator.writeExtensionFile( - fontsNames: fontsNames, - staticVar: options.staticMembers, - extensionName: options.extensionNameUIKit, - extensionFilePath: options.extensionFilePathUIKit, - isSwiftUI: false - ) + if let extensionNameUIKit = options.extensionNameUIKit, + let extensionFilePathUIKit = options.extensionFilePathUIKit { + FontExtensionGenerator.writeExtensionFile( + fontsNames: fontsNames, + staticVar: options.staticMembers, + extensionName: extensionNameUIKit, + extensionFilePath: extensionFilePathUIKit, + isSwiftUI: false + ) + } - print("Info.plist has been updated with:") - print("\(FontPlistGenerator.generatePlistUIAppsFontContent(for: fontsNames, infoPlistPaths: options.infoPlistPaths))") + if options.infoPlistPaths.isEmpty == false { + let plistUpdateFontsData = FontPlistGenerator.generatePlistUIAppsFontContent( + for: fontsNames, + infoPlistPaths: options.infoPlistPaths + ) + print("Info.plist has been updated with:") + print(plistUpdateFontsData) + } print("[\(Self.toolName)] Fonts generated") } diff --git a/Sources/ResgenSwift/Fonts/FontsToolError.swift b/Sources/ResgenSwift/Fonts/FontsToolError.swift index 5e851e1..237e833 100644 --- a/Sources/ResgenSwift/Fonts/FontsToolError.swift +++ b/Sources/ResgenSwift/Fonts/FontsToolError.swift @@ -14,6 +14,7 @@ enum FontsToolError: Error { case inputFolderNotFound(String) case fileNotExists(String) case writeExtension(String, String) + case missingExtensionPath var description: String { switch self { @@ -31,6 +32,9 @@ enum FontsToolError: Error { case let .writeExtension(filename, info): return "error: [\(Fonts.toolName)] An error occured while writing extension in \(filename): \(info)" + + case .missingExtensionPath: + return "error: [\(Fonts.toolName)] Extension need to be generated but no `extensionOutputPath` is provided" } } } diff --git a/Sources/ResgenSwift/Generate/Model/ConfigurationFile.swift b/Sources/ResgenSwift/Generate/Model/ConfigurationFile.swift index 5a84654..44c5d62 100644 --- a/Sources/ResgenSwift/Generate/Model/ConfigurationFile.swift +++ b/Sources/ResgenSwift/Generate/Model/ConfigurationFile.swift @@ -131,7 +131,7 @@ struct ColorsConfiguration: Codable, CustomDebugStringConvertible { let inputFile: String let style: String let xcassetsPath: String - let extensionOutputPath: String + let extensionOutputPath: String? let extensionName: String? let extensionNameUIKit: String? let extensionSuffix: String? @@ -148,7 +148,7 @@ struct ColorsConfiguration: Codable, CustomDebugStringConvertible { inputFile: String, style: String, xcassetsPath: String, - extensionOutputPath: String, + extensionOutputPath: String?, extensionName: String?, extensionNameUIKit: String?, extensionSuffix: String?, @@ -170,7 +170,7 @@ struct ColorsConfiguration: Codable, CustomDebugStringConvertible { - Input file: \(inputFile) - Style: \(style) - Xcassets path: \(xcassetsPath) - - Extension output path: \(extensionOutputPath) + - Extension output path: \(extensionOutputPath ?? "-") - Extension name: \(extensionName ?? "-") - Extension name UIKit: \(extensionNameUIKit ?? "-") - Extension suffix: \(extensionSuffix ?? "-") @@ -181,7 +181,7 @@ struct ColorsConfiguration: Codable, CustomDebugStringConvertible { struct FontsConfiguration: Codable, CustomDebugStringConvertible { let inputFile: String - let extensionOutputPath: String + let extensionOutputPath: String? let extensionName: String? let extensionNameUIKit: String? let extensionSuffix: String? @@ -197,7 +197,7 @@ struct FontsConfiguration: Codable, CustomDebugStringConvertible { internal init( inputFile: String, - extensionOutputPath: String, + extensionOutputPath: String?, extensionName: String?, extensionNameUIKit: String?, extensionSuffix: String?, @@ -217,7 +217,7 @@ struct FontsConfiguration: Codable, CustomDebugStringConvertible { """ Fonts configuration: - Input file: \(inputFile) - - Extension output path: \(extensionOutputPath) + - Extension output path: \(extensionOutputPath ?? "-") - Extension name: \(extensionName ?? "-") - Extension name UIKit: \(extensionNameUIKit ?? "-") - Extension suffix: \(extensionSuffix ?? "-") @@ -230,7 +230,7 @@ struct ImagesConfiguration: Codable, CustomDebugStringConvertible { let inputFile: String let xcassetsPath: String - let extensionOutputPath: String + let extensionOutputPath: String? let extensionName: String? let extensionNameUIKit: String? let extensionSuffix: String? @@ -246,7 +246,7 @@ struct ImagesConfiguration: Codable, CustomDebugStringConvertible { internal init( inputFile: String, xcassetsPath: String, - extensionOutputPath: String, + extensionOutputPath: String?, extensionName: String?, extensionNameUIKit: String?, extensionSuffix: String?, @@ -266,7 +266,7 @@ struct ImagesConfiguration: Codable, CustomDebugStringConvertible { Images configuration: - Input file: \(inputFile) - Xcassets path: \(xcassetsPath) - - Extension output path: \(extensionOutputPath) + - Extension output path: \(extensionOutputPath ?? "-") - Extension name: \(extensionName ?? "-") - Extension name UIKit: \(extensionNameUIKit ?? "-") - Extension suffix: \(extensionSuffix ?? "-") @@ -280,7 +280,7 @@ struct StringsConfiguration: Codable, CustomDebugStringConvertible { let outputPath: String let langs: String let defaultLang: String - let extensionOutputPath: String + let extensionOutputPath: String? let extensionName: String? let extensionSuffix: String? private let staticMembers: Bool? @@ -305,7 +305,7 @@ struct StringsConfiguration: Codable, CustomDebugStringConvertible { outputPath: String, langs: String, defaultLang: String, - extensionOutputPath: String, + extensionOutputPath: String?, extensionName: String?, extensionSuffix: String?, staticMembers: Bool?, @@ -329,7 +329,7 @@ struct StringsConfiguration: Codable, CustomDebugStringConvertible { - Output path: \(outputPath) - Langs: \(langs) - Default lang: \(defaultLang) - - Extension output path: \(extensionOutputPath) + - Extension output path: \(extensionOutputPath ?? "-") - Extension name: \(extensionName ?? "-") - Extension suffix: \(extensionSuffix ?? "-") """ diff --git a/Sources/ResgenSwift/Generate/Runnable/ColorsConfiguration+Runnable.swift b/Sources/ResgenSwift/Generate/Runnable/ColorsConfiguration+Runnable.swift index 6f8bc11..b360d42 100644 --- a/Sources/ResgenSwift/Generate/Runnable/ColorsConfiguration+Runnable.swift +++ b/Sources/ResgenSwift/Generate/Runnable/ColorsConfiguration+Runnable.swift @@ -27,29 +27,23 @@ extension ColorsConfiguration: Runnable { style, "--xcassets-path", xcassetsPath.prependIfRelativePath(projectDirectory), - "--extension-output-path", - extensionOutputPath.prependIfRelativePath(projectDirectory), "--static-members", "\(staticMembersOptions)" ] - if let extensionName { - args += [ - "--extension-name", - extensionName - ] - } - if let extensionNameUIKit { - args += [ - "--extension-name-ui-kit", - extensionNameUIKit - ] - } - if let extensionSuffix { - args += [ - "--extension-suffix", - extensionSuffix - ] + // Add optional parameters + [ + ("--extension-output-path", extensionOutputPath?.prependIfRelativePath(projectDirectory)), + ("--extension-name", extensionName), + ("--extension-name-ui-kit", extensionNameUIKit), + ("--extension-suffix", extensionSuffix) + ].forEach { argumentName, argumentValue in + if let argumentValue { + args += [ + argumentName, + argumentValue + ] + } } return args diff --git a/Sources/ResgenSwift/Generate/Runnable/FontsConfiguration+Runnable.swift b/Sources/ResgenSwift/Generate/Runnable/FontsConfiguration+Runnable.swift index 6a9db9f..d603552 100644 --- a/Sources/ResgenSwift/Generate/Runnable/FontsConfiguration+Runnable.swift +++ b/Sources/ResgenSwift/Generate/Runnable/FontsConfiguration+Runnable.swift @@ -23,32 +23,26 @@ extension FontsConfiguration: Runnable { args += [ inputFile.prependIfRelativePath(projectDirectory), - "--extension-output-path", - extensionOutputPath.prependIfRelativePath(projectDirectory), "--static-members", "\(staticMembersOptions)" ] - if let extensionName { - args += [ - "--extension-name", - extensionName - ] - } - if let extensionNameUIKit { - args += [ - "--extension-name-ui-kit", - extensionNameUIKit - ] - } - - if let extensionSuffix { - args += [ - "--extension-suffix", - extensionSuffix - ] + // Add optional parameters + [ + ("--extension-output-path", extensionOutputPath?.prependIfRelativePath(projectDirectory)), + ("--extension-name", extensionName), + ("--extension-name-ui-kit", extensionNameUIKit), + ("--extension-suffix", extensionSuffix) + ].forEach { argumentName, argumentValue in + if let argumentValue { + args += [ + argumentName, + argumentValue + ] + } } + // Add infoPlist paths if let infoPlistPaths { let adjustedPlistPaths = infoPlistPaths .split(separator: " ") diff --git a/Sources/ResgenSwift/Generate/Runnable/ImagesConfiguration+Runnable.swift b/Sources/ResgenSwift/Generate/Runnable/ImagesConfiguration+Runnable.swift index 91d9a43..5631859 100644 --- a/Sources/ResgenSwift/Generate/Runnable/ImagesConfiguration+Runnable.swift +++ b/Sources/ResgenSwift/Generate/Runnable/ImagesConfiguration+Runnable.swift @@ -25,31 +25,23 @@ extension ImagesConfiguration: Runnable { inputFile.prependIfRelativePath(projectDirectory), "--xcassets-path", xcassetsPath.prependIfRelativePath(projectDirectory), - "--extension-output-path", - extensionOutputPath.prependIfRelativePath(projectDirectory), "--static-members", "\(staticMembersOptions)" ] - if let extensionName { - args += [ - "--extension-name", - extensionName - ] - } - - if let extensionNameUIKit { - args += [ - "--extension-name-ui-kit", - extensionNameUIKit - ] - } - - if let extensionSuffix { - args += [ - "--extension-suffix", - extensionSuffix - ] + // Add optional parameters + [ + ("--extension-output-path", extensionOutputPath?.prependIfRelativePath(projectDirectory)), + ("--extension-name", extensionName), + ("--extension-name-ui-kit", extensionNameUIKit), + ("--extension-suffix", extensionSuffix) + ].forEach { argumentName, argumentValue in + if let argumentValue { + args += [ + argumentName, + argumentValue + ] + } } return args diff --git a/Sources/ResgenSwift/Generate/Runnable/StringsConfiguration+Runnable.swift b/Sources/ResgenSwift/Generate/Runnable/StringsConfiguration+Runnable.swift index 5e094e4..707fa08 100644 --- a/Sources/ResgenSwift/Generate/Runnable/StringsConfiguration+Runnable.swift +++ b/Sources/ResgenSwift/Generate/Runnable/StringsConfiguration+Runnable.swift @@ -24,26 +24,24 @@ extension StringsConfiguration: Runnable { langs, "--default-lang", defaultLang, - "--extension-output-path", - extensionOutputPath.prependIfRelativePath(projectDirectory), "--static-members", "\(staticMembersOptions)", "--xc-strings", "\(xcStringsOptions)" ] - if let extensionName { - args += [ - "--extension-name", - extensionName - ] - } - - if let extensionSuffix { - args += [ - "--extension-suffix", - extensionSuffix - ] + // Add optional parameters + [ + ("--extension-output-path", extensionOutputPath?.prependIfRelativePath(projectDirectory)), + ("--extension-name", extensionName), + ("--extension-suffix", extensionSuffix) + ].forEach { argumentName, argumentValue in + if let argumentValue { + args += [ + argumentName, + argumentValue + ] + } } Stringium.main(args) diff --git a/Sources/ResgenSwift/Images/Images.swift b/Sources/ResgenSwift/Images/Images.swift index 5458f72..aa2438f 100644 --- a/Sources/ResgenSwift/Images/Images.swift +++ b/Sources/ResgenSwift/Images/Images.swift @@ -21,8 +21,6 @@ struct Images: ParsableCommand { // MARK: - Static static let toolName = "Images" - static let defaultExtensionName = "Image" - static let defaultExtensionNameUIKit = "UIImage" // MARK: - Command Options @@ -55,23 +53,29 @@ struct Images: ParsableCommand { ) // Generate extension - ImageExtensionGenerator.generateExtensionFile( - images: imagesToGenerate, - staticVar: options.staticMembers, - inputFilename: options.inputFilenameWithoutExt, - extensionName: options.extensionName, - extensionFilePath: options.extensionFilePath, - isSwiftUI: true - ) + if let extensionName = options.extensionName, + let extensionFilePath = options.extensionFilePath { + ImageExtensionGenerator.generateExtensionFile( + images: imagesToGenerate, + staticVar: options.staticMembers, + inputFilename: options.inputFilenameWithoutExt, + extensionName: extensionName, + extensionFilePath: extensionFilePath, + isSwiftUI: true + ) + } - ImageExtensionGenerator.generateExtensionFile( - images: imagesToGenerate, - staticVar: options.staticMembers, - inputFilename: options.inputFilenameWithoutExt, - extensionName: options.extensionNameUIKit, - extensionFilePath: options.extensionFilePathUIKit, - isSwiftUI: false - ) + if let extensionNameUIKit = options.extensionNameUIKit, + let extensionFilePathUIKit = options.extensionFilePathUIKit { + ImageExtensionGenerator.generateExtensionFile( + images: imagesToGenerate, + staticVar: options.staticMembers, + inputFilename: options.inputFilenameWithoutExt, + extensionName: extensionNameUIKit, + extensionFilePath: extensionFilePathUIKit, + isSwiftUI: false + ) + } print("[\(Self.toolName)] Images generated") } @@ -96,17 +100,39 @@ struct Images: ParsableCommand { _ = 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) - Self.exit(withError: error) + if let extensionName = options.extensionName, + let extensionNameUIKit = options.extensionNameUIKit { + guard extensionName != extensionNameUIKit else { + let error = ImagesError.extensionNamesCollision(extensionName) + print(error.description) + Self.exit(withError: error) + } + } + + // If an extension need to be generated, ensure extensionOutputPath is defined + if options.extensionName != nil || + options.extensionNameUIKit != nil { + guard let extensionOutputPath = options.extensionOutputPath, + extensionOutputPath.isEmpty == false else { + let error = ImagesError.missingExtensionPath + print(error.description) + Self.exit(withError: error) + } } // Check if needed to regenerate + let fileToCompareToInput: String = { + // If there is no extension file to compare + // Then check the xcassets file instead + if let extensionFilePath = options.extensionFilePath { + return extensionFilePath + } + return options.xcassetsPath + }() guard GeneratorChecker.shouldGenerate( force: options.forceExecution, inputFilePath: options.inputFile, - extensionFilePath: options.extensionFilePath + extensionFilePath: fileToCompareToInput ) else { print("[\(Self.toolName)] Images are already up to date :) ") return false diff --git a/Sources/ResgenSwift/Images/ImagesError.swift b/Sources/ResgenSwift/Images/ImagesError.swift index 85bd5b9..a24ccce 100644 --- a/Sources/ResgenSwift/Images/ImagesError.swift +++ b/Sources/ResgenSwift/Images/ImagesError.swift @@ -18,12 +18,13 @@ enum ImagesError: Error { case magickConvertNotFound case writeFile(String, String) case createAssetFolder(String) + case missingExtensionPath case unknown(String) var description: String { switch self { case .extensionNamesCollision(let extensionName): - return "error: [\(Fonts.toolName)] Error on extension names, extension name and SwiftUI extension name should be different (\(extensionName) is used on both)" + return "error: [\(Images.toolName)] Error on extension names, extension name and SwiftUI extension name should be different (\(extensionName) is used on both)" case .inputFolderNotFound(let inputFolder): return "error: [\(Images.toolName)] Input folder not found: \(inputFolder)" @@ -47,7 +48,10 @@ enum ImagesError: Error { return "error: [\(Images.toolName)] An error occured while writing content to \(filename): \(subErrorDescription)" case .createAssetFolder(let folder): - return "error: [\(Colors.toolName)] An error occured while creating folder `\(folder)`" + return "error: [\(Images.toolName)] An error occured while creating folder `\(folder)`" + + case .missingExtensionPath: + return "error: [\(Images.toolName)] Extension need to be generated but no `extensionOutputPath` is provided" case .unknown(let errorDescription): return "error: [\(Images.toolName)] Unknown error: \(errorDescription)" diff --git a/Sources/ResgenSwift/Images/ImagesOptions.swift b/Sources/ResgenSwift/Images/ImagesOptions.swift index 03554ed..f544a70 100644 --- a/Sources/ResgenSwift/Images/ImagesOptions.swift +++ b/Sources/ResgenSwift/Images/ImagesOptions.swift @@ -24,17 +24,17 @@ struct ImagesOptions: ParsableArguments { @Option(help: "Xcassets path where to generate images.", transform: { $0.replaceTiltWithHomeDirectoryPath() }) var xcassetsPath: String - @Option(help: "Path where to generate the extension.", transform: { $0.replaceTiltWithHomeDirectoryPath() }) - var extensionOutputPath: String - @Option(help: "Tell if it will generate static properties or not") var staticMembers: Bool = false - @Option(help: "Extension name. If not specified, it will generate an Image extension.") - var extensionName: String = Images.defaultExtensionName + @Option(help: "Path where to generate the extension.", transform: { $0.replaceTiltWithHomeDirectoryPath() }) + var extensionOutputPath: String? - @Option(help: "Extension name. If not specified, it will generate an UIImage extension.") - var extensionNameUIKit: String = Images.defaultExtensionNameUIKit + @Option(help: "SwiftUI extension name. If not specified, no extension will be generated.") + var extensionName: String? + + @Option(help: "UIKit extension name. If not specified, no extension will be generated.") + var extensionNameUIKit: String? @Option(help: "Extension suffix. Ex: MyApp, it will generate {extensionName}+Image{extensionSuffix}.swift") var extensionSuffix: String? @@ -46,28 +46,36 @@ extension ImagesOptions { // MARK: - SwiftUI - var extensionFileName: String { + var extensionFileName: String? { + guard let extensionName else { return nil } + if let extensionSuffix { return "\(extensionName)+\(extensionSuffix).swift" } return "\(extensionName).swift" } - var extensionFilePath: String { - "\(extensionOutputPath)/\(extensionFileName)" + var extensionFilePath: String? { + guard let extensionOutputPath, let extensionFileName else { return nil } + + return "\(extensionOutputPath)/\(extensionFileName)" } // MARK: - UIKit - var extensionFileNameUIKit: String { + var extensionFileNameUIKit: String? { + guard let extensionNameUIKit else { return nil } + if let extensionSuffix { return "\(extensionNameUIKit)+\(extensionSuffix).swift" } return "\(extensionNameUIKit).swift" } - var extensionFilePathUIKit: String { - "\(extensionOutputPath)/\(extensionFileNameUIKit)" + var extensionFilePathUIKit: String? { + guard let extensionOutputPath, let extensionFileNameUIKit else { return nil } + + return "\(extensionOutputPath)/\(extensionFileNameUIKit)" } // MARK: - diff --git a/Sources/ResgenSwift/Strings/Generator/StringsFileGenerator.swift b/Sources/ResgenSwift/Strings/Generator/StringsFileGenerator.swift index 4de70c2..67633f0 100644 --- a/Sources/ResgenSwift/Strings/Generator/StringsFileGenerator.swift +++ b/Sources/ResgenSwift/Strings/Generator/StringsFileGenerator.swift @@ -19,8 +19,7 @@ enum StringsFileGenerator { langs: [String], defaultLang: String, tags: [String], - outputPath: String, - inputFilenameWithoutExt: String + lprojPathFormat: String ) { var stringsFilesContent = [String: String]() @@ -37,7 +36,7 @@ enum StringsFileGenerator { langs.forEach { lang in guard let fileContent = stringsFilesContent[lang] else { return } - let stringsFilePath = "\(outputPath)/\(lang).lproj/\(inputFilenameWithoutExt).strings" + let stringsFilePath = String(format: lprojPathFormat, lang) let stringsFilePathURL = URL(fileURLWithPath: stringsFilePath) do { try fileContent.write(to: stringsFilePathURL, atomically: false, encoding: .utf8) @@ -54,8 +53,7 @@ enum StringsFileGenerator { langs: [String], defaultLang: String, tags: [String], - outputPath: String, - inputFilenameWithoutExt: String + xcStringsFilePath: String ) { let fileContent: String = Self.generateXcStringsFileContent( @@ -65,12 +63,11 @@ enum StringsFileGenerator { sections: sections ) - let stringsFilePath = "\(outputPath)/\(inputFilenameWithoutExt).xcstrings" - let stringsFilePathURL = URL(fileURLWithPath: stringsFilePath) + let stringsFilePathURL = URL(fileURLWithPath: xcStringsFilePath) do { try fileContent.write(to: stringsFilePathURL, atomically: false, encoding: .utf8) } catch { - let error = StringiumError.writeFile(error.localizedDescription, stringsFilePath) + let error = StringiumError.writeFile(error.localizedDescription, xcStringsFilePath) print(error.description) Stringium.exit(withError: error) } diff --git a/Sources/ResgenSwift/Strings/Stringium/Stringium.swift b/Sources/ResgenSwift/Strings/Stringium/Stringium.swift index 934060d..dd8289c 100644 --- a/Sources/ResgenSwift/Strings/Stringium/Stringium.swift +++ b/Sources/ResgenSwift/Strings/Stringium/Stringium.swift @@ -21,7 +21,6 @@ struct Stringium: ParsableCommand { // MARK: - Static static let toolName = "Stringium" - static let defaultExtensionName = "String" static let noTranslationTag: String = "notranslation" // MARK: - Command options @@ -49,8 +48,7 @@ struct Stringium: ParsableCommand { langs: options.langs, defaultLang: options.defaultLang, tags: options.tags, - outputPath: options.stringsFileOutputPath, - inputFilenameWithoutExt: options.inputFilenameWithoutExt + lprojPathFormat: options.lprojPathFormat ) } else { StringsFileGenerator.writeXcStringsFiles( @@ -58,22 +56,25 @@ struct Stringium: ParsableCommand { langs: options.langs, defaultLang: options.defaultLang, tags: options.tags, - outputPath: options.stringsFileOutputPath, - inputFilenameWithoutExt: options.inputFilenameWithoutExt + xcStringsFilePath: options.xcStringsFilePath ) } // 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 - ) + if let extensionName = options.extensionName, + let extensionFilePath = options.extensionFilePath { + print("Will generate extensions") + StringsFileGenerator.writeExtensionFiles( + sections: sections, + defaultLang: options.defaultLang, + tags: options.tags, + staticVar: options.staticMembers, + inputFilename: options.inputFilenameWithoutExt, + extensionName: extensionName, + extensionFilePath: extensionFilePath, + extensionSuffix: options.extensionSuffix ?? "" + ) + } print("[\(Self.toolName)] Strings generated") } @@ -104,10 +105,18 @@ struct Stringium: ParsableCommand { } // Check if needed to regenerate + let fileToCompareToInput: String = { + // If there is no extension file to compare + // Then check the xcassets file instead + if options.xcStrings { + return options.xcStringsFilePath + } + return String(format: options.lprojPathFormat, options.defaultLang) + }() guard GeneratorChecker.shouldGenerate( force: options.forceGeneration, inputFilePath: options.inputFile, - extensionFilePath: options.extensionFilePath + extensionFilePath: fileToCompareToInput ) else { print("[\(Self.toolName)] Strings are already up to date :) ") return false diff --git a/Sources/ResgenSwift/Strings/Stringium/StringiumOptions.swift b/Sources/ResgenSwift/Strings/Stringium/StringiumOptions.swift index 8c1a05e..0b116d1 100644 --- a/Sources/ResgenSwift/Strings/Stringium/StringiumOptions.swift +++ b/Sources/ResgenSwift/Strings/Stringium/StringiumOptions.swift @@ -15,35 +15,51 @@ struct StringiumOptions: ParsableArguments { @Flag(name: [.customShort("f"), .customShort("F")], help: "Should force generation") var forceGeneration = false - @Argument(help: "Input files where strings are defined.", transform: { $0.replaceTiltWithHomeDirectoryPath() }) + @Argument( + help: "Input files where strings are defined.", + transform: { $0.replaceTiltWithHomeDirectoryPath() } + ) var inputFile: String - @Option(name: .customLong("output-path"), help: "Path where to strings file.", transform: { $0.replaceTiltWithHomeDirectoryPath() }) + @Option( + name: .customLong("output-path"), + help: "Path where to strings file.", + transform: { $0.replaceTiltWithHomeDirectoryPath() } + ) fileprivate var outputPathRaw: String - @Option(name: .customLong("langs"), help: "Langs to generate.") + @Option( + name: .customLong("langs"), + help: "Langs to generate." + ) fileprivate var langsRaw: String @Option(help: "Default langs.") var defaultLang: String - @Option(name: .customLong("tags"), help: "Tags to generate.") + @Option( + name: .customLong("tags"), + help: "Tags to generate." + ) fileprivate var tagsRaw: String = "ios iosonly iosOnly notranslation" - @Option(help: "Path where to generate the extension.", transform: { $0.replaceTiltWithHomeDirectoryPath() }) - var extensionOutputPath: String - - @Option(help: "Tell if it will generate static properties or not") + @Option(help: "Generate static properties. False by default") var staticMembers: Bool = false - @Option(help: "Tell if it will generate xcStrings file or not") - var xcStrings: Bool = false + @Option(help: "Tell if it will generate xcStrings file or lproj file. True by default") + var xcStrings: Bool = true - @Option(help: "Extension name. If not specified, it will generate an String extension.") - var extensionName: String = Stringium.defaultExtensionName + @Option( + help: "Path where to generate the extension.", + transform: { $0.replaceTiltWithHomeDirectoryPath() } + ) + var extensionOutputPath: String? + + @Option(help: "Extension name. If not specified, no extension will be generated.") + var extensionName: String? @Option(help: "Extension suffix: {extensionName}+{extensionSuffix}.swift") - var extensionSuffix: String + var extensionSuffix: String? } // MARK: - Private var getter @@ -75,12 +91,21 @@ extension StringiumOptions { extension StringiumOptions { - var extensionFileName: String { - "\(extensionName)+\(extensionSuffix).swift" + private var extensionFileName: String? { + if let extensionName { + if let extensionSuffix { + return "\(extensionName)+\(extensionSuffix).swift" + } + return "\(extensionName).swift" + } + return nil } - var extensionFilePath: String { - "\(extensionOutputPath)/\(extensionFileName)" + var extensionFilePath: String? { + if let extensionOutputPath, let extensionFileName { + return "\(extensionOutputPath)/\(extensionFileName)" + } + return nil } var inputFilenameWithoutExt: String { @@ -88,4 +113,12 @@ extension StringiumOptions { .deletingPathExtension() .lastPathComponent } + + var xcStringsFilePath: String { + "\(stringsFileOutputPath)/\(inputFilenameWithoutExt).xcstrings" + } + + var lprojPathFormat: String { + "\(stringsFileOutputPath)/%@.lproj/\(inputFilenameWithoutExt).strings" + } } diff --git a/Tests/ResgenSwiftTests/Analytics/AnalyticsDefinitionTests.swift b/Tests/ResgenSwiftTests/Analytics/AnalyticsDefinitionTests.swift index 4ed4bbe..cf824d1 100644 --- a/Tests/ResgenSwiftTests/Analytics/AnalyticsDefinitionTests.swift +++ b/Tests/ResgenSwiftTests/Analytics/AnalyticsDefinitionTests.swift @@ -62,7 +62,8 @@ final class AnalyticsDefinitionTests: XCTestCase { func logScreenDefinitionName() { logScreen( name: "Ecran un", - path: "ecran_un/" + path: "ecran_un/", + params: nil ) } """ @@ -84,7 +85,7 @@ final class AnalyticsDefinitionTests: XCTestCase { name: "Ecran un", action: "", category: "", - params: [:] + params: nil ) } """ @@ -103,9 +104,10 @@ final class AnalyticsDefinitionTests: XCTestCase { // Expect let expectScreen = """ static func logScreenDefinitionName() { - logScreen( + AnalyticsManager.shared.logScreen( name: "Ecran un", - path: "ecran_un/" + path: "ecran_un/", + params: nil ) } """ @@ -123,11 +125,11 @@ final class AnalyticsDefinitionTests: XCTestCase { // Expect let expectEvent = """ static func logEventDefinitionName() { - logEvent( + AnalyticsManager.shared.logEvent( name: "Ecran un", action: "", category: "", - params: [:] + params: nil ) } """ diff --git a/Tests/ResgenSwiftTests/Analytics/AnalyticsGeneratorTests.swift b/Tests/ResgenSwiftTests/Analytics/AnalyticsGeneratorTests.swift index 5b6e987..57f0016 100644 --- a/Tests/ResgenSwiftTests/Analytics/AnalyticsGeneratorTests.swift +++ b/Tests/ResgenSwiftTests/Analytics/AnalyticsGeneratorTests.swift @@ -1,6 +1,6 @@ // // AnalyticsGeneratorTests.swift -// +// // // Created by Thibaut Schmitt on 06/09/2022. // @@ -14,7 +14,7 @@ import ToolCore @testable import ResgenSwift final class AnalyticsGeneratorTests: XCTestCase { - + private func getAnalyticsDefinition( id: String, path: String = "", @@ -31,7 +31,183 @@ final class AnalyticsGeneratorTests: XCTestCase { definition.category = category return definition } - + + private func protocolString() -> String { + """ + // MARK: - Protocol + + protocol AnalyticsManagerProtocol { + + func logScreen( + name: String, + path: String, + params: [String: Any]? + ) + + func logEvent( + name: String, + action: String, + category: String, + params: [String: Any]? + ) + + func setEnable(_ enable: Bool) + } + """ + } + + private func firebaseString() -> String { + """ + // MARK: - Firebase + + class FirebaseAnalyticsManager: AnalyticsManagerProtocol { + + // MARK: - Methods + + func logScreen( + name: String, + path: String, + params: [String: Any]? + ) { + var parameters = [ + AnalyticsParameterScreenName: name as NSObject + ] + + if path.isEmpty == false { + parameters["path"] = path + "/iOS" as NSObject + } + + if let supplementaryParameters = params { + for (newKey, newValue) in supplementaryParameters { + if parameters.contains(where: { (key: String, value: NSObject) in + key == newKey + }) { + continue + } + + parameters[newKey] = newValue as? NSObject + } + } + + Analytics.logEvent( + AnalyticsEventScreenView, + parameters: parameters + ) + } + + func logEvent( + name: String, + action: String, + category: String, + params: [String: Any]? + ) { + var parameters: [String:NSObject] = [ + AnalyticsParameterItemName: name.replacingOccurrences(of: " ", with: "_") as NSObject + ] + + if category.isEmpty == false { + parameters["AnalyticsParameterItemCategory"] = category as NSObject + } + + if action.isEmpty == false { + parameters["action"] = action as NSObject + } + + if let supplementaryParameters = params { + for (newKey, newValue) in supplementaryParameters { + if parameters.contains(where: { (key: String, value: NSObject) in + key == newKey + }) { + continue + } + + parameters[newKey] = newValue as? NSObject + } + } + + Analytics.logEvent( + AnalyticsEventSelectContent, + parameters: parameters + ) + } + + func setEnable(_ enable: Bool) { + Analytics.setAnalyticsCollectionEnabled(enable) + } + } + """ + } + + private func matomoString() -> String { + """ + // MARK: - Matomo + + class MatomoAnalyticsManager: AnalyticsManagerProtocol { + + // MARK: - Properties + + private var tracker: MatomoTracker + + // MARK: - Init + + init(siteId: String, url: String) { + debugPrint("[Matomo service] Server URL: \\(url)") + debugPrint("[Matomo service] Site ID: \\(siteId)") + tracker = MatomoTracker( + siteId: siteId, + baseURL: URL(string: url)! + ) + + #if DEBUG + tracker.dispatchInterval = 5 + #endif + + #if DEBUG + tracker.logger = DefaultLogger(minLevel: .verbose) + #endif + + debugPrint("[Matomo service] Configured with content base: \\(tracker.contentBase?.absoluteString ?? "-")") + debugPrint("[Matomo service] Opt out: \\(tracker.isOptedOut)") + } + + // MARK: - Methods + + func logScreen( + name: String, + path: String, + params: [String: Any]? + ) { + guard let trackerUrl = tracker.contentBase?.absoluteString else { return } + + let urlString = URL(string: "\\(trackerUrl)" + "/" + "\\(path)" + "iOS") + tracker.track( + view: [name], + url: urlString + ) + } + + func logEvent( + name: String, + action: String, + category: String, + params: [String: Any]? + ) { + tracker.track( + eventWithCategory: category, + action: action, + name: name, + number: nil, + url: nil + ) + } + + func setEnable(_ enable: Bool) { + tracker.isOptedOut = !enable + } + } + """ + } + func testGeneratedExtensionContentFirebase() { // Given let sectionOne = AnalyticsCategory(id: "section_one") @@ -39,19 +215,19 @@ final class AnalyticsGeneratorTests: XCTestCase { getAnalyticsDefinition(id: "s1_def_one", name: "s1 def one", type: .screen, tags: ["ios", "iosonly"]), getAnalyticsDefinition(id: "s1_def_two", name: "s1 def two", type: .event, tags: ["ios", "iosonly"]), ] - + let sectionTwo = AnalyticsCategory(id: "section_two") sectionTwo.definitions = [ getAnalyticsDefinition(id: "s2_def_one", name: "s2 def one", type: .screen, tags: ["ios","iosonly"]), getAnalyticsDefinition(id: "s2_def_two", name: "s2 def two", type: .event, tags: ["droid","droidonly"]), ] - + let sectionThree = AnalyticsCategory(id: "section_three") sectionThree.definitions = [ getAnalyticsDefinition(id: "s3_def_one", name: "s3 def one", type: .screen, tags: ["droid","droidonly"]), getAnalyticsDefinition(id: "s3_def_two", name: "s3 def two", type: .event, tags: ["droid","droidonly"]), ] - + // When let extensionContent = AnalyticsGenerator.getExtensionContent( targets: [TrackerType.firebase], @@ -65,64 +241,18 @@ final class AnalyticsGeneratorTests: XCTestCase { let expect = """ // Generated by ResgenSwift.Analytics \(ResgenSwiftVersion) + import Foundation import FirebaseAnalytics - // MARK: - Protocol + \(protocolString()) - protocol AnalyticsManagerProtocol { + \(firebaseString()) - func logScreen(name: String, path: String) - func logEvent( - name: String, - action: String, - category: String, - params: [String: Any]? - ) - } + // MARK: - Traker Type - // MARK: - Firebase + enum TrackerType: CaseIterable { - class FirebaseAnalyticsManager: AnalyticsManagerProtocol { - - func logScreen(name: String, path: String) { - var parameters = [ - AnalyticsParameterScreenName: name as NSObject - ] - - Analytics.logEvent( - AnalyticsEventScreenView, - parameters: parameters - ) - } - - func logEvent( - name: String, - action: String, - category: String, - params: [String: Any]? - ) { - var parameters: [String:NSObject] = [ - "action": action as NSObject, - "category": category as NSObject, - ] - - if let supplementaryParameters = params { - for (newKey, newValue) in supplementaryParameters { - if parameters.contains(where: { (key: String, value: NSObject) in - key == newKey - }) { - continue - } - - parameters[newKey] = newValue as? NSObject - } - } - - Analytics.logEvent( - name.replacingOccurrences(of: [" "], with: "_"), - parameters: parameters - ) - } + case firebase } // MARK: - Manager @@ -131,27 +261,59 @@ final class AnalyticsGeneratorTests: XCTestCase { static var shared = AnalyticsManager() + private init() {} + // MARK: - Properties - var managers: [AnalyticsManagerProtocol] = [] + var managers: [TrackerType: AnalyticsManagerProtocol] = [:] - private var isEnabled: Bool = true + private var isEnabled: Bool { + if ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1" { + false + } else { + true + } + } - // MARK: - Methods + // MARK: - Enable Methods - func setAnalyticsEnabled(_ enable: Bool) { - isEnabled = enable + private func setAnalytics(enable: Bool, _ analytics: [TrackerType]) { + managers.forEach { (key, value) in + if analytics.contains(where: { type in + type == key + }) { + value.setEnable(enable) + } + } + } + + func enableAnalytics(_ analytics: [TrackerType] = TrackerType.allCases) { + setAnalytics(enable: true, analytics) + } + + func disableAnalytics(_ analytics: [TrackerType] = TrackerType.allCases) { + setAnalytics(enable: false, analytics) } func configure() { - managers.append(FirebaseAnalyticsManager()) + managers[TrackerType.firebase] = FirebaseAnalyticsManager() } - private func logScreen(name: String, path: String) { + // MARK: - Private Log Methods + + private func logScreen( + name: String, + path: String, + params: [String: Any]? + ) { guard isEnabled else { return } - managers.forEach { manager in - manager.logScreen(name: name, path: path) + managers.values.forEach { manager in + manager.logScreen( + name: name, + path: path, + params: params + ) } } @@ -163,7 +325,7 @@ final class AnalyticsGeneratorTests: XCTestCase { ) { guard isEnabled else { return } - managers.forEach { manager in + managers.values.forEach { manager in manager.logEvent( name: name, action: action, @@ -178,7 +340,8 @@ final class AnalyticsGeneratorTests: XCTestCase { func logScreenS1DefOne() { logScreen( name: "s1 def one", - path: "" + path: "", + params: nil ) } @@ -187,7 +350,7 @@ final class AnalyticsGeneratorTests: XCTestCase { name: "s1 def two", action: "", category: "", - params: [:] + params: nil ) } @@ -196,19 +359,20 @@ final class AnalyticsGeneratorTests: XCTestCase { func logScreenS2DefOne() { logScreen( name: "s2 def one", - path: "" + path: "", + params: nil ) } } - + """ - + if extensionContent != expect { print(prettyFirstDifferenceBetweenStrings(s1: extensionContent, s2: expect)) } XCTAssertEqual(extensionContent.adaptForXCTest(), expect.adaptForXCTest()) } - + func testGeneratedExtensionContentMatomo() { // Given let sectionOne = AnalyticsCategory(id: "section_one") @@ -216,19 +380,19 @@ final class AnalyticsGeneratorTests: XCTestCase { getAnalyticsDefinition(id: "s1_def_one", path: "s1_def_one/", name: "s1 def one", type: .screen, tags: ["ios", "iosonly"]), getAnalyticsDefinition(id: "s1_def_two", action: "test", category: "test", name: "s1 def two", type: .event, tags: ["ios", "iosonly"]), ] - + let sectionTwo = AnalyticsCategory(id: "section_two") sectionTwo.definitions = [ getAnalyticsDefinition(id: "s2_def_one", path: "s2_def_one/", name: "s2 def one", type: .screen, tags: ["ios","iosonly"]), getAnalyticsDefinition(id: "s2_def_two", action: "test", category: "test", name: "s2 def two", type: .event, tags: ["droid","droidonly"]), ] - + let sectionThree = AnalyticsCategory(id: "section_three") sectionThree.definitions = [ getAnalyticsDefinition(id: "s3_def_one", path: "s3_def_one/", name: "s3 def one", type: .screen, tags: ["droid","droidonly"]), getAnalyticsDefinition(id: "s3_def_two", action: "test", category: "test", name: "s3 def two", type: .event, tags: ["droid","droidonly"]), ] - + // When let extensionContent = AnalyticsGenerator.getExtensionContent( targets: [TrackerType.matomo], @@ -241,80 +405,18 @@ final class AnalyticsGeneratorTests: XCTestCase { let expect = """ // Generated by ResgenSwift.Analytics \(ResgenSwiftVersion) + import Foundation import MatomoTracker - // MARK: - Protocol + \(protocolString()) - protocol AnalyticsManagerProtocol { + \(matomoString()) - func logScreen(name: String, path: String) - func logEvent( - name: String, - action: String, - category: String, - params: [String: Any]? - ) - } + // MARK: - Traker Type - // MARK: - Matomo + enum TrackerType: CaseIterable { - class MatomoAnalyticsManager: AnalyticsManagerProtocol { - - // MARK: - Properties - - private var tracker: MatomoTracker - - // MARK: - Init - - init(siteId: String, url: String) { - debugPrint("[Matomo service] Server URL: \\(url)") - debugPrint("[Matomo service] Site ID: \\(siteId)") - tracker = MatomoTracker( - siteId: siteId, - baseURL: URL(string: url)! - ) - - #if DEBUG - tracker.dispatchInterval = 5 - #endif - - #if DEBUG - tracker.logger = DefaultLogger(minLevel: .verbose) - #endif - - debugPrint("[Matomo service] Configured with content base: \\(tracker.contentBase?.absoluteString ?? "-")") - debugPrint("[Matomo service] Opt out: \\(tracker.isOptedOut)") - } - - // MARK: - Methods - - func logScreen(name: String, path: String) { - guard !tracker.isOptedOut else { return } - guard let trackerUrl = tracker.contentBase?.absoluteString else { return } - - let urlString = URL(string: "\\(trackerUrl)" + "/" + "\\(path)" + "iOS") - tracker.track( - view: [name], - url: urlString - ) - } - - func logEvent( - name: String, - action: String, - category: String, - params: [String: Any]? - ) { - guard !tracker.isOptedOut else { return } - - tracker.track( - eventWithCategory: category, - action: action, - name: name, - number: nil, - url: nil - ) - } + case matomo } // MARK: - Manager @@ -323,32 +425,62 @@ final class AnalyticsGeneratorTests: XCTestCase { static var shared = AnalyticsManager() + private init() {} + // MARK: - Properties - var managers: [AnalyticsManagerProtocol] = [] + var managers: [TrackerType: AnalyticsManagerProtocol] = [:] - private var isEnabled: Bool = true + private var isEnabled: Bool { + if ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1" { + false + } else { + true + } + } - // MARK: - Methods + // MARK: - Enable Methods - func setAnalyticsEnabled(_ enable: Bool) { - isEnabled = enable + private func setAnalytics(enable: Bool, _ analytics: [TrackerType]) { + managers.forEach { (key, value) in + if analytics.contains(where: { type in + type == key + }) { + value.setEnable(enable) + } + } + } + + func enableAnalytics(_ analytics: [TrackerType] = TrackerType.allCases) { + setAnalytics(enable: true, analytics) + } + + func disableAnalytics(_ analytics: [TrackerType] = TrackerType.allCases) { + setAnalytics(enable: false, analytics) } func configure(siteId: String, url: String) { - managers.append( - MatomoAnalyticsManager( - siteId: siteId, - url: url - ) + managers[TrackerType.matomo] = MatomoAnalyticsManager( + siteId: siteId, + url: url ) } - private func logScreen(name: String, path: String) { + // MARK: - Private Log Methods + + private func logScreen( + name: String, + path: String, + params: [String: Any]? + ) { guard isEnabled else { return } - managers.forEach { manager in - manager.logScreen(name: name, path: path) + managers.values.forEach { manager in + manager.logScreen( + name: name, + path: path, + params: params + ) } } @@ -360,7 +492,7 @@ final class AnalyticsGeneratorTests: XCTestCase { ) { guard isEnabled else { return } - managers.forEach { manager in + managers.values.forEach { manager in manager.logEvent( name: name, action: action, @@ -375,7 +507,8 @@ final class AnalyticsGeneratorTests: XCTestCase { func logScreenS1DefOne() { logScreen( name: "s1 def one", - path: "s1_def_one/" + path: "s1_def_one/", + params: nil ) } @@ -384,7 +517,7 @@ final class AnalyticsGeneratorTests: XCTestCase { name: "s1 def two", action: "test", category: "test", - params: [:] + params: nil ) } @@ -393,19 +526,20 @@ final class AnalyticsGeneratorTests: XCTestCase { func logScreenS2DefOne() { logScreen( name: "s2 def one", - path: "s2_def_one/" + path: "s2_def_one/", + params: nil ) } } """ - + if extensionContent != expect { print(prettyFirstDifferenceBetweenStrings(s1: extensionContent, s2: expect)) } XCTAssertEqual(extensionContent.adaptForXCTest(), expect.adaptForXCTest()) } - + func testGeneratedExtensionContentMatomoAndFirebase() { // Given let sectionOne = AnalyticsCategory(id: "section_one") @@ -413,19 +547,19 @@ final class AnalyticsGeneratorTests: XCTestCase { getAnalyticsDefinition(id: "s1_def_one", path: "s1_def_one/", name: "s1 def one", type: .screen, tags: ["ios", "iosonly"]), getAnalyticsDefinition(id: "s1_def_two", action: "test", category: "test", name: "s1 def two", type: .event, tags: ["ios", "iosonly"]), ] - + let sectionTwo = AnalyticsCategory(id: "section_two") sectionTwo.definitions = [ getAnalyticsDefinition(id: "s2_def_one", path: "s2_def_one/", name: "s2 def one", type: .screen, tags: ["ios","iosonly"]), getAnalyticsDefinition(id: "s2_def_two", action: "test", category: "test", name: "s2 def two", type: .event, tags: ["droid","droidonly"]), ] - + let sectionThree = AnalyticsCategory(id: "section_three") sectionThree.definitions = [ getAnalyticsDefinition(id: "s3_def_one", path: "s3_def_one/", name: "s3 def one", type: .screen, tags: ["droid","droidonly"]), getAnalyticsDefinition(id: "s3_def_two", action: "test", category: "test", name: "s3 def two", type: .event, tags: ["droid","droidonly"]), ] - + // When let extensionContent = AnalyticsGenerator.getExtensionContent( targets: [TrackerType.matomo, TrackerType.firebase], @@ -439,125 +573,22 @@ final class AnalyticsGeneratorTests: XCTestCase { let expect = """ // Generated by ResgenSwift.Analytics \(ResgenSwiftVersion) + import Foundation import MatomoTracker import FirebaseAnalytics - // MARK: - Protocol + \(protocolString()) - protocol AnalyticsManagerProtocol { + \(matomoString()) - func logScreen(name: String, path: String) - func logEvent( - name: String, - action: String, - category: String, - params: [String: Any]? - ) - } + \(firebaseString()) - // MARK: - Matomo + // MARK: - Traker Type - class MatomoAnalyticsManager: AnalyticsManagerProtocol { + enum TrackerType: CaseIterable { - // MARK: - Properties - - private var tracker: MatomoTracker - - // MARK: - Init - - init(siteId: String, url: String) { - debugPrint("[Matomo service] Server URL: \\(url)") - debugPrint("[Matomo service] Site ID: \\(siteId)") - tracker = MatomoTracker( - siteId: siteId, - baseURL: URL(string: url)! - ) - - #if DEBUG - tracker.dispatchInterval = 5 - #endif - - #if DEBUG - tracker.logger = DefaultLogger(minLevel: .verbose) - #endif - - debugPrint("[Matomo service] Configured with content base: \\(tracker.contentBase?.absoluteString ?? "-")") - debugPrint("[Matomo service] Opt out: \\(tracker.isOptedOut)") - } - - // MARK: - Methods - - func logScreen(name: String, path: String) { - guard !tracker.isOptedOut else { return } - guard let trackerUrl = tracker.contentBase?.absoluteString else { return } - - let urlString = URL(string: "\\(trackerUrl)" + "/" + "\\(path)" + "iOS") - tracker.track( - view: [name], - url: urlString - ) - } - - func logEvent( - name: String, - action: String, - category: String, - params: [String: Any]? - ) { - guard !tracker.isOptedOut else { return } - - tracker.track( - eventWithCategory: category, - action: action, - name: name, - number: nil, - url: nil - ) - } - } - - // MARK: - Firebase - - class FirebaseAnalyticsManager: AnalyticsManagerProtocol { - func logScreen(name: String, path: String) { - var parameters = [ - AnalyticsParameterScreenName: name as NSObject - ] - - Analytics.logEvent( - AnalyticsEventScreenView, - parameters: parameters - ) - } - - func logEvent( - name: String, - action: String, - category: String, - params: [String: Any]? - ) { - var parameters: [String:NSObject] = [ - "action": action as NSObject, - "category": category as NSObject, - ] - - if let supplementaryParameters = params { - for (newKey, newValue) in supplementaryParameters { - if parameters.contains(where: { (key: String, value: NSObject) in - key == newKey - }) { - continue - } - - parameters[newKey] = newValue as? NSObject - } - } - - Analytics.logEvent( - name.replacingOccurrences(of: [" "], with: "_"), - parameters: parameters - ) - } + case matomo + case firebase } // MARK: - Manager @@ -566,33 +597,63 @@ final class AnalyticsGeneratorTests: XCTestCase { static var shared = AnalyticsManager() + private init() {} + // MARK: - Properties - var managers: [AnalyticsManagerProtocol] = [] + var managers: [TrackerType: AnalyticsManagerProtocol] = [:] - private var isEnabled: Bool = true + private var isEnabled: Bool { + if ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1" { + false + } else { + true + } + } - // MARK: - Methods + // MARK: - Enable Methods - func setAnalyticsEnabled(_ enable: Bool) { - isEnabled = enable + private func setAnalytics(enable: Bool, _ analytics: [TrackerType]) { + managers.forEach { (key, value) in + if analytics.contains(where: { type in + type == key + }) { + value.setEnable(enable) + } + } + } + + func enableAnalytics(_ analytics: [TrackerType] = TrackerType.allCases) { + setAnalytics(enable: true, analytics) + } + + func disableAnalytics(_ analytics: [TrackerType] = TrackerType.allCases) { + setAnalytics(enable: false, analytics) } func configure(siteId: String, url: String) { - managers.append( - MatomoAnalyticsManager( - siteId: siteId, - url: url - ) + managers[TrackerType.matomo] = MatomoAnalyticsManager( + siteId: siteId, + url: url ) - managers.append(FirebaseAnalyticsManager()) + managers[TrackerType.firebase] = FirebaseAnalyticsManager() } - private func logScreen(name: String, path: String) { + // MARK: - Private Log Methods + + private func logScreen( + name: String, + path: String, + params: [String: Any]? + ) { guard isEnabled else { return } - - managers.forEach { manager in - manager.logScreen(name: name, path: path) + + managers.values.forEach { manager in + manager.logScreen( + name: name, + path: path, + params: params + ) } } @@ -604,7 +665,7 @@ final class AnalyticsGeneratorTests: XCTestCase { ) { guard isEnabled else { return } - managers.forEach { manager in + managers.values.forEach { manager in manager.logEvent( name: name, action: action, @@ -619,7 +680,8 @@ final class AnalyticsGeneratorTests: XCTestCase { func logScreenS1DefOne() { logScreen( name: "s1 def one", - path: "s1_def_one/" + path: "s1_def_one/", + params: nil ) } @@ -628,7 +690,7 @@ final class AnalyticsGeneratorTests: XCTestCase { name: "s1 def two", action: "test", category: "test", - params: [:] + params: nil ) } @@ -637,13 +699,14 @@ final class AnalyticsGeneratorTests: XCTestCase { func logScreenS2DefOne() { logScreen( name: "s2 def one", - path: "s2_def_one/" + path: "s2_def_one/", + params: nil ) } } """ - + if extensionContent != expect { print(prettyFirstDifferenceBetweenStrings(s1: extensionContent, s2: expect)) } diff --git a/Tests/ResgenSwiftTests/Generate/ColorsConfigurationTests.swift b/Tests/ResgenSwiftTests/Generate/ColorsConfigurationTests.swift index ea905f4..5bb6384 100644 --- a/Tests/ResgenSwiftTests/Generate/ColorsConfigurationTests.swift +++ b/Tests/ResgenSwiftTests/Generate/ColorsConfigurationTests.swift @@ -19,7 +19,7 @@ final class ColorsConfigurationTests: XCTestCase { let testingConfiguration = ColorsConfiguration(inputFile: "path/to/colors.txt", style: ColorStyle.all.rawValue, xcassetsPath: "path/to/assets.xcassets", - extensionOutputPath: "Colors/Generated", + extensionOutputPath: nil, extensionName: nil, extensionNameUIKit: nil, extensionSuffix: nil, @@ -34,8 +34,6 @@ final class ColorsConfigurationTests: XCTestCase { "all", "--xcassets-path", "projectDirectory/path/to/assets.xcassets", - "--extension-output-path", - "projectDirectory/Colors/Generated", "--static-members", "false" ] @@ -64,10 +62,10 @@ final class ColorsConfigurationTests: XCTestCase { "all", "--xcassets-path", "projectDirectory/path/to/assets.xcassets", - "--extension-output-path", - "projectDirectory/Colors/Generated", "--static-members", "false", + "--extension-output-path", + "projectDirectory/Colors/Generated", "--extension-name", "AppUIColor", "--extension-name-ui-kit", diff --git a/Tests/ResgenSwiftTests/Generate/FontsConfigurationTests.swift b/Tests/ResgenSwiftTests/Generate/FontsConfigurationTests.swift index 785b49c..5ca0249 100644 --- a/Tests/ResgenSwiftTests/Generate/FontsConfigurationTests.swift +++ b/Tests/ResgenSwiftTests/Generate/FontsConfigurationTests.swift @@ -17,7 +17,7 @@ final class FontsConfigurationTests: XCTestCase { func test_argsGeneration_requiredArgs() { // Given let testingConfiguration = FontsConfiguration(inputFile: "path/to/fonts.txt", - extensionOutputPath: "Fonts/Generated", + extensionOutputPath: nil, extensionName: nil, extensionNameUIKit: nil, extensionSuffix: nil, @@ -29,8 +29,6 @@ final class FontsConfigurationTests: XCTestCase { // Expect let expectedArguments = [ "projectDirectory/path/to/fonts.txt", - "--extension-output-path", - "projectDirectory/Fonts/Generated", "--static-members", "false" ] @@ -54,10 +52,10 @@ final class FontsConfigurationTests: XCTestCase { let expectedArguments = [ "-f", "projectDirectory/path/to/fonts.txt", - "--extension-output-path", - "projectDirectory/Fonts/Generated", "--static-members", "true", + "--extension-output-path", + "projectDirectory/Fonts/Generated", "--extension-name", "AppUIFont", "--extension-name-ui-kit", diff --git a/Tests/ResgenSwiftTests/Generate/ImagesConfigurationTests.swift b/Tests/ResgenSwiftTests/Generate/ImagesConfigurationTests.swift index 7e4f8ff..476ca39 100644 --- a/Tests/ResgenSwiftTests/Generate/ImagesConfigurationTests.swift +++ b/Tests/ResgenSwiftTests/Generate/ImagesConfigurationTests.swift @@ -18,7 +18,7 @@ final class ImagesConfigurationTests: XCTestCase { // Given let testingConfiguration = ImagesConfiguration(inputFile: "path/to/images.txt", xcassetsPath: "path/to/assets.xcassets", - extensionOutputPath: "Images/Generated", + extensionOutputPath: nil, extensionName: nil, extensionNameUIKit: nil, extensionSuffix: nil, @@ -32,8 +32,6 @@ final class ImagesConfigurationTests: XCTestCase { "projectDirectory/path/to/images.txt", "--xcassets-path", "projectDirectory/path/to/assets.xcassets", - "--extension-output-path", - "projectDirectory/Images/Generated", "--static-members", "false" ] @@ -60,10 +58,10 @@ final class ImagesConfigurationTests: XCTestCase { "projectDirectory/path/to/images.txt", "--xcassets-path", "projectDirectory/path/to/assets.xcassets", - "--extension-output-path", - "projectDirectory/Images/Generated", "--static-members", "true", + "--extension-output-path", + "projectDirectory/Images/Generated", "--extension-name", "AppUIImage", "--extension-name-ui-kit",