From 1a45ec7b0dd258218f5f2d6f0fcc1076526230b4 Mon Sep 17 00:00:00 2001 From: Loris Perret Date: Tue, 5 Dec 2023 16:54:44 +0100 Subject: [PATCH 01/21] New version of tags.txt --- SampleFiles/Tags/sampleTags.txt | 10 ++++++++-- SampleFiles/genAllRessources.sh | 30 +++++++++++++++--------------- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/SampleFiles/Tags/sampleTags.txt b/SampleFiles/Tags/sampleTags.txt index 98f67d4..5ace993 100644 --- a/SampleFiles/Tags/sampleTags.txt +++ b/SampleFiles/Tags/sampleTags.txt @@ -1,7 +1,13 @@ [[ScreenTag]] [screen_one] - ium = Ecran un + path = ecran_un/ + name = Ecran un + type = screen tags = droid,ios + comments = [screen_two] - ium = Ecran deux + path = ecran_deux/ + name = Ecran deux + type = event tags = droid,ios + comments = diff --git a/SampleFiles/genAllRessources.sh b/SampleFiles/genAllRessources.sh index 1530efc..ddab6b1 100755 --- a/SampleFiles/genAllRessources.sh +++ b/SampleFiles/genAllRessources.sh @@ -30,27 +30,27 @@ FORCE_FLAG="$1" # --default-lang "en" \ # --extension-output-path "./Twine/Generated" -echo "\n-------------------------\n" +#echo "\n-------------------------\n" # Strings -swift run -c release ResgenSwift strings stringium $FORCE_FLAG "./Strings/sampleStrings.txt" \ - --output-path "./Strings/Generated" \ - --langs "fr en en-us" \ - --default-lang "en" \ - --extension-output-path "./Strings/Generated" \ - --extension-name "String" \ - --extension-suffix "GenAllScript" +#swift run -c release ResgenSwift strings stringium $FORCE_FLAG "./Strings/sampleStrings.txt" \ +# --output-path "./Strings/Generated" \ +# --langs "fr en en-us" \ +# --default-lang "en" \ +# --extension-output-path "./Strings/Generated" \ +# --extension-name "String" \ +# --extension-suffix "GenAllScript" echo "\n-------------------------\n" ## Tags -#swift run -c release ResgenSwift strings tags $FORCE_FLAG "./Tags/sampleTags.txt" \ -# --lang "ium" \ -# --extension-output-path "./Tags/Generated" \ -# --extension-name "Tags" \ -# --extension-suffix "GenAllScript" -# -#echo "\n-------------------------\n" +swift run -c release ResgenSwift strings tags $FORCE_FLAG "./Tags/sampleTags.txt" \ + --target "matomo firebase" \ + --extension-output-path "./Tags/Generated" \ + --extension-name "Tags" \ + --extension-suffix "GenAllScript" + +echo "\n-------------------------\n" # ## Images #swift run -c release ResgenSwift images $FORCE_FLAG "./Images/sampleImages.txt" \ -- 2.39.5 From fa5bf192e86e1152aef239c9bb616140b20a094b Mon Sep 17 00:00:00 2001 From: Loris Perret Date: Tue, 5 Dec 2023 16:55:45 +0100 Subject: [PATCH 02/21] Delete lang option and add target option --- SampleFiles/resgenConfiguration.yml | 2 +- .../ResgenSwift/Generate/Model/ConfigurationFile.swift | 8 ++++---- .../Generate/Runnable/TagsConfiguration+Runnable.swift | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/SampleFiles/resgenConfiguration.yml b/SampleFiles/resgenConfiguration.yml index 29a6d1a..fbc512a 100644 --- a/SampleFiles/resgenConfiguration.yml +++ b/SampleFiles/resgenConfiguration.yml @@ -72,7 +72,7 @@ colors: tags: - inputFile: ./Tags/sampleTags.txt - lang: ium + target: "matomo firebase" extensionOutputPath: ./Tags/Generated extensionName: Tags extensionSuffix: GenAllScript diff --git a/Sources/ResgenSwift/Generate/Model/ConfigurationFile.swift b/Sources/ResgenSwift/Generate/Model/ConfigurationFile.swift index 9cf8c47..47ddb79 100644 --- a/Sources/ResgenSwift/Generate/Model/ConfigurationFile.swift +++ b/Sources/ResgenSwift/Generate/Model/ConfigurationFile.swift @@ -266,7 +266,7 @@ struct StringsConfiguration: Codable, CustomDebugStringConvertible { struct TagsConfiguration: Codable, CustomDebugStringConvertible { let inputFile: String - let lang: String + let target: String let extensionOutputPath: String let extensionName: String? let extensionSuffix: String? @@ -280,13 +280,13 @@ struct TagsConfiguration: Codable, CustomDebugStringConvertible { } internal init(inputFile: String, - lang: String, + target: String, extensionOutputPath: String, extensionName: String?, extensionSuffix: String?, staticMembers: Bool?) { self.inputFile = inputFile - self.lang = lang + self.target = target self.extensionOutputPath = extensionOutputPath self.extensionName = extensionName self.extensionSuffix = extensionSuffix @@ -297,7 +297,7 @@ struct TagsConfiguration: Codable, CustomDebugStringConvertible { """ Tags configuration: - Input file: \(inputFile) - - Lang: \(lang) + - Target: \(target) - Extension output path: \(extensionOutputPath) - Extension name: \(extensionName ?? "-") - Extension suffix: \(extensionSuffix ?? "-") diff --git a/Sources/ResgenSwift/Generate/Runnable/TagsConfiguration+Runnable.swift b/Sources/ResgenSwift/Generate/Runnable/TagsConfiguration+Runnable.swift index 04c6648..198f5ea 100644 --- a/Sources/ResgenSwift/Generate/Runnable/TagsConfiguration+Runnable.swift +++ b/Sources/ResgenSwift/Generate/Runnable/TagsConfiguration+Runnable.swift @@ -17,8 +17,8 @@ extension TagsConfiguration: Runnable { args += [ inputFile.prependIfRelativePath(projectDirectory), - "--lang", - lang, + "--target", + target, "--extension-output-path", extensionOutputPath.prependIfRelativePath(projectDirectory), "--static-members", -- 2.39.5 From ce274219fc0e5b930c0c4e7c42034fa588296390 Mon Sep 17 00:00:00 2001 From: Loris Perret Date: Tue, 5 Dec 2023 16:56:44 +0100 Subject: [PATCH 03/21] Add new tag generation --- .../Strings/Generator/TagsGenerator.swift | 86 --------- .../TagsV2/Generator/TagsGenerator.swift | 178 ++++++++++++++++++ .../TagsV2/Model/TagDefinition.swift | 155 +++++++++++++++ .../ResgenSwift/TagsV2/Model/TagSection.swift | 39 ++++ .../TagsV2/Parser/TagFileParser.swift | 93 +++++++++ .../{Strings/Tag => TagsV2}/Tags.swift | 22 ++- .../{Strings/Tag => TagsV2}/TagsOptions.swift | 4 +- .../Strings/TagsGeneratorTests.swift | 14 +- 8 files changed, 493 insertions(+), 98 deletions(-) delete mode 100644 Sources/ResgenSwift/Strings/Generator/TagsGenerator.swift create mode 100644 Sources/ResgenSwift/TagsV2/Generator/TagsGenerator.swift create mode 100644 Sources/ResgenSwift/TagsV2/Model/TagDefinition.swift create mode 100644 Sources/ResgenSwift/TagsV2/Model/TagSection.swift create mode 100644 Sources/ResgenSwift/TagsV2/Parser/TagFileParser.swift rename Sources/ResgenSwift/{Strings/Tag => TagsV2}/Tags.swift (82%) rename Sources/ResgenSwift/{Strings/Tag => TagsV2}/TagsOptions.swift (92%) diff --git a/Sources/ResgenSwift/Strings/Generator/TagsGenerator.swift b/Sources/ResgenSwift/Strings/Generator/TagsGenerator.swift deleted file mode 100644 index ee09d73..0000000 --- a/Sources/ResgenSwift/Strings/Generator/TagsGenerator.swift +++ /dev/null @@ -1,86 +0,0 @@ -// -// TagsGenerator.swift -// -// -// Created by Thibaut Schmitt on 10/01/2022. -// - -import Foundation -import ToolCore -import CoreVideo - -class 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) - - // Write content - let extensionFilePathURL = URL(fileURLWithPath: extensionFilePath) - do { - try extensionFileContent.write(to: extensionFilePathURL, atomically: false, encoding: .utf8) - } catch (let error) { - let error = StringiumError.writeFile(extensionFilePath, error.localizedDescription) - print(error.description) - Stringium.exit(withError: error) - } - } - - // MARK: - Extension content - - static func getExtensionContent(sections: [Section], lang: String, tags: [String], staticVar: Bool, extensionName: String) -> String { - [ - Self.getHeader(extensionClassname: extensionName, staticVar: staticVar), - Self.getProperties(sections: sections, lang: lang, tags: tags, staticVar: staticVar), - Self.getFooter() - ] - .joined(separator: "\n") - } - - // MARK: - Extension part - - private static func getHeader(extensionClassname: String, staticVar: Bool) -> String { - """ - // Generated by ResgenSwift.Strings.\(Tags.toolName) \(ResgenSwiftVersion) - - \(staticVar ? "typelias Tags = String\n\n" : "")import UIKit - - extension \(extensionClassname) { - """ - } - - private static func getProperties(sections: [Section], lang: String, tags: [String], staticVar: Bool) -> String { - sections - .compactMap { section in - // Check that at least one string will be generated - guard section.hasOneOrMoreMatchingTags(tags: tags) else { - return nil// Go to next section - } - - var res = "\n // MARK: - \(section.name)" - section.definitions.forEach { definition in - guard definition.hasOneOrMoreMatchingTags(inputTags: tags) == true else { - return // Go to next definition - } - - if staticVar { - res += "\n\n\(definition.getStaticProperty(forLang: lang))" - } else { - res += "\n\n\(definition.getProperty(forLang: lang))" - } - } - return res - } - .joined(separator: "\n") - } - - private static func getFooter() -> String { - """ - } - - """ - } -} diff --git a/Sources/ResgenSwift/TagsV2/Generator/TagsGenerator.swift b/Sources/ResgenSwift/TagsV2/Generator/TagsGenerator.swift new file mode 100644 index 0000000..c53e334 --- /dev/null +++ b/Sources/ResgenSwift/TagsV2/Generator/TagsGenerator.swift @@ -0,0 +1,178 @@ +// +// TagsGenerator.swift +// +// +// Created by Thibaut Schmitt on 10/01/2022. +// + +import Foundation +import ToolCore +import CoreVideo + +class TagsGenerator { + static func writeExtensionFiles(sections: [TagSection], target: String, tags: [String], staticVar: Bool, extensionName: String, extensionFilePath: String) { + // Get target type from enum + target.split(separator: " ").forEach { target in + Tags.TargetType.allCases.forEach { value in + + } + } + + let targetsString: [String] = target.components(separatedBy: " ") + var targets: [Tags.TargetType] = [] + + Tags.TargetType.allCases.forEach { enumTarget in + if targetsString.contains(enumTarget.value) { + targets.append(enumTarget) + } + } + + + + // Get extension content + let extensionFileContent = Self.getExtensionContent(sections: sections, + targets: targets, + 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) { + let error = StringiumError.writeFile(extensionFilePath, error.localizedDescription) + print(error.description) + Stringium.exit(withError: error) + } + } + + // MARK: - Extension content + + static func getExtensionContent(sections: [TagSection], targets: [Tags.TargetType], tags: [String], staticVar: Bool, extensionName: String) -> String { + [ + Self.getHeader(extensionClassname: extensionName, staticVar: staticVar, targets: targets), + Self.getProperties(sections: sections, target: "target", tags: tags, staticVar: staticVar), + Self.getFooter() + ] + .joined(separator: "\n") + } + + // MARK: - Extension part + + private static func getHeader(extensionClassname: String, staticVar: Bool, targets: [Tags.TargetType]) -> String { + """ + // Generated by ResgenSwift.\(Tags.toolName) \(ResgenSwiftVersion) + + \(staticVar ? "typelias Tags = String\n\n" : "")import UIKit + + \(Self.getAnalytics(targets: targets)) + + extension \(extensionClassname) { + + // MARK: - Properties + + let managers: [AnalyticsManager] = [\(Self.getAnalyticsProperties(targets: targets))] + """ + } + + private static func getAnalyticsProperties(targets: [Tags.TargetType]) -> String { + let matomo = "MatomoAnalyticsManager()" + let firebase = "FirebaseAnalyticsManager()" + + var result: [String] = [] + + if targets.contains(Tags.TargetType.matomo) { + result.append(matomo) + } + + if targets.contains(Tags.TargetType.firebase) { + result.append(firebase) + } + + return result.joined(separator: ", ") + } + + private static func getAnalytics(targets: [Tags.TargetType]) -> String { + let proto = """ + // MARK: - Protocol + + protocol AnalyticsManager { + func logScreen(name: String, path: String) + func logEvent(name: String) + } + """ + + let matomo = """ + // MARK: - Matomo + + class MatomoAnalyticsManager: AnalyticsManager { + func logScreen(name: String, path: String) { + + } + + func logEvent(name: String) { + + } + } + """ + + let firebase = """ + // MARK: - Firebase + + class FirebaseAnalyticsManager: AnalyticsManager { + func logScreen(name: String, path: String) { + + } + + func logEvent(name: String) { + + } + } + """ + + var result: [String] = [proto] + + if targets.contains(Tags.TargetType.matomo) { + result.append(matomo) + } + + if targets.contains(Tags.TargetType.firebase) { + result.append(firebase) + } + + return result.joined(separator: "\n") + } + + private static func getProperties(sections: [TagSection], target: 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 + } + + var res = "\n // MARK: - \(section.name)" + section.definitions.forEach { definition in + guard definition.hasOneOrMoreMatchingTags(inputTags: tags) == true else { + return // Go to next definition + } + + if staticVar { + res += "\n\n\(definition.getStaticProperty(forTarget: target))" + } else { + res += "\n\n\(definition.getProperty(forTarget: target))" + } + } + return res + } + .joined(separator: "\n") + } + + private static func getFooter() -> String { + """ + } + + """ + } +} diff --git a/Sources/ResgenSwift/TagsV2/Model/TagDefinition.swift b/Sources/ResgenSwift/TagsV2/Model/TagDefinition.swift new file mode 100644 index 0000000..0fd3f25 --- /dev/null +++ b/Sources/ResgenSwift/TagsV2/Model/TagDefinition.swift @@ -0,0 +1,155 @@ +// +// TagDefinition.swift +// +// +// Created by Loris Perret on 05/12/2023. +// + +import Foundation + +class TagDefinition { + let title: String + var path: String = "" + var name: String = "" + var type: String = "" + var tags = [String]() + var comment: String? + + var isValid: Bool { + title.isEmpty == false && + name.isEmpty == false && + (type == TagType.screen.value || type == TagType.event.value) + } + + init(title: String) { + self.title = title + } + + static func match(_ line: String) -> TagDefinition? { + guard line.range(of: "\\[(.*?)]$", options: .regularExpression, range: nil, locale: nil) != nil else { + return nil + } + + let definitionTitle = line + .replacingOccurrences(of: ["[", "]"], with: "") + .removeLeadingTrailingWhitespace() + + return TagDefinition(title: definitionTitle) + } + + func hasOneOrMoreMatchingTags(inputTags: [String]) -> Bool { + if Set(inputTags).intersection(Set(self.tags)).isEmpty { + return false + } + return true + } + + // MARK: - + + private func getStringParameters(input: String) -> (inputParameters: [String], translationArguments: [String])? { + var methodsParameters = [String]() + + let printfPlaceholderRegex = try! NSRegularExpression(pattern: "%(?:\\d+\\$)?[+-]?(?:[ 0]|'.{1})?-?\\d*(?:\\.\\d+)?[blcdeEufFgGosxX@]*") + printfPlaceholderRegex.enumerateMatches(in: input, options: [], range: NSRange(location: 0, length: input.count)) { match, _, stop in + guard let match = match else { return } + + if let range = Range(match.range, in: input), let last = input[range].last { + switch last { + case "d", "u": + methodsParameters.append("Int") + case "f", "F": + methodsParameters.append("Double") + case "@", "s", "c": + methodsParameters.append("String") + case "%": + // if you need to print %, you have to add %% + break + default: + break + } + } + } + + if methodsParameters.isEmpty { + return nil + } + + var inputParameters = [String]() + var translationArguments = [String]() + for (index, paramType) in methodsParameters.enumerated() { + let paramName = "arg\(index)" + translationArguments.append(paramName) + inputParameters.append("\(paramName): \(paramType)") + } + + return (inputParameters: inputParameters, translationArguments: translationArguments) + } + + private func getFuncName() -> String { + var pascalCaseTitle: String = "" + name.components(separatedBy: " ").forEach { word in + pascalCaseTitle.append(contentsOf: word.uppercasedFirst()) + } + + return "log\(pascalCaseTitle)" + } + + private func getlogFunction() -> String { + if type == TagType.screen.value { + "manager.logScreen(name: name, path: path)" + } else { + "manager.logEvent(name: name)" + } + } + + // MARK: - Raw strings + + func getProperty(forTarget target: String) -> String { + return """ + func \(getFuncName())() { + managers.forEach { manager in + \(getlogFunction()) + } + } + """ + } + + func getStaticProperty(forTarget target: String) -> String { +// guard let translation = translations[lang] else { +// let error = StringiumError.langNotDefined(lang, title, reference != nil) +// print(error.description) +// Stringium.exit(withError: error) +// } +// +// return """ +// /// Translation in \(lang) : +// /// \(translation) +// static var \(title): String { +// "\(translation)" +// } +// """ + return """ + static func \(getFuncName())() { + managers.forEach { manager in + \(getlogFunction()) + } + } + """ + } +} + +extension TagDefinition { + enum TagType { + case screen + case event + + var value: String { + switch self { + case .screen: + "screen" + case .event: + "event" + } + } + } +} diff --git a/Sources/ResgenSwift/TagsV2/Model/TagSection.swift b/Sources/ResgenSwift/TagsV2/Model/TagSection.swift new file mode 100644 index 0000000..afabacd --- /dev/null +++ b/Sources/ResgenSwift/TagsV2/Model/TagSection.swift @@ -0,0 +1,39 @@ +// +// TagSection.swift +// +// +// Created by Loris Perret on 05/12/2023. +// + +import Foundation + +class TagSection { + let name: String // OnBoarding + var definitions = [TagDefinition]() + + init(name: String) { + self.name = name + } + + static func match(_ line: String) -> TagSection? { + guard line.range(of: "\\[\\[(.*?)]]$", options: .regularExpression, range: nil, locale: nil) != nil else { + return nil + } + + let sectionName = line + .replacingOccurrences(of: ["[", "]"], with: "") + .removeLeadingTrailingWhitespace() + return TagSection(name: sectionName) + } + + func hasOneOrMoreMatchingTags(tags: [String]) -> Bool { + let allTags = definitions.flatMap { $0.tags } + let allTagsSet = Set(allTags) + + let intersection = Set(tags).intersection(allTagsSet) + if intersection.isEmpty { + return false + } + return true + } +} diff --git a/Sources/ResgenSwift/TagsV2/Parser/TagFileParser.swift b/Sources/ResgenSwift/TagsV2/Parser/TagFileParser.swift new file mode 100644 index 0000000..c8e93e2 --- /dev/null +++ b/Sources/ResgenSwift/TagsV2/Parser/TagFileParser.swift @@ -0,0 +1,93 @@ +// +// TagFileParser.swift +// +// +// Created by Loris Perret on 05/12/2023. +// + +import Foundation + +class TagFileParser { + static func parse(_ inputFile: String) -> [TagSection] { + let inputFileContent = try! String(contentsOfFile: inputFile, encoding: .utf8) + let stringsByLines = inputFileContent.components(separatedBy: .newlines) + + var sections = [TagSection]() + + // Parse file + stringsByLines.forEach { + // TagSection + if let section = TagSection.match($0) { + sections.append(section) + return + } + + // Definition + if let definition = TagDefinition.match($0) { + sections.last?.definitions.append(definition) + return + } + + // Definition content + if $0.isEmpty == false { + // name = Test => ["name ", " Test"] + let splitLine = $0 + .removeLeadingTrailingWhitespace() + .split(separator: "=") + + guard let lastDefinition = sections.last?.definitions.last, + let leftElement = splitLine.first else { + return + } + + let rightElement: String = splitLine.dropFirst().joined(separator: "=") + + // "name " => "name" + let leftHand = String(leftElement).removeTrailingWhitespace() + // " Test" => "Test" + let rightHand = String(rightElement).removeLeadingWhitespace() + + // Handle comments, tags and translation + switch leftHand { + case "comments": + lastDefinition.comment = rightHand + + case "tags": + lastDefinition.tags = rightHand + .split(separator: ",") + .map { String($0) } + + case "path": + lastDefinition.path = rightHand + + case "name": + lastDefinition.name = rightHand + + case "type": + lastDefinition.type = rightHand + + default: + break + } + } + } + + // Keep only valid definition + var invalidDefinitionNames = [String]() + sections.forEach { section in + section.definitions = section.definitions + .filter { + if $0.isValid == false { + invalidDefinitionNames.append($0.name) + return false + } + return true + } + } + if invalidDefinitionNames.count > 0 { + print("warning: [\(Stringium.toolName)] Found \(invalidDefinitionNames.count) definition (\(invalidDefinitionNames.joined(separator: ", "))") + } + + return sections + } +} diff --git a/Sources/ResgenSwift/Strings/Tag/Tags.swift b/Sources/ResgenSwift/TagsV2/Tags.swift similarity index 82% rename from Sources/ResgenSwift/Strings/Tag/Tags.swift rename to Sources/ResgenSwift/TagsV2/Tags.swift index 7fedafa..971392f 100644 --- a/Sources/ResgenSwift/Strings/Tag/Tags.swift +++ b/Sources/ResgenSwift/TagsV2/Tags.swift @@ -33,7 +33,7 @@ struct Tags: ParsableCommand { mutating func run() { print("[\(Self.toolName)] Starting tags generation") - print("[\(Self.toolName)] Will use inputFile \(options.inputFile) to generate strings for lang: \(options.lang)") + print("[\(Self.toolName)] Will use inputFile \(options.inputFile) to generate tags for target: \(options.target)") // Check requirements guard checkRequirements() else { return } @@ -41,11 +41,11 @@ struct Tags: ParsableCommand { print("[\(Self.toolName)] Will generate tags") // Parse input file - let sections = TwineFileParser.parse(options.inputFile) + let sections = TagFileParser.parse(options.inputFile) // Generate extension TagsGenerator.writeExtensionFiles(sections: sections, - lang: options.lang, + target: options.target, tags: ["ios", "iosonly", Self.noTranslationTag], staticVar: options.staticMembers, extensionName: options.extensionName, @@ -77,3 +77,19 @@ struct Tags: ParsableCommand { return true } } + +extension Tags { + enum TargetType: CaseIterable { + case matomo + case firebase + + var value: String { + switch self { + case .matomo: + "matomo" + case .firebase: + "firebase" + } + } + } +} diff --git a/Sources/ResgenSwift/Strings/Tag/TagsOptions.swift b/Sources/ResgenSwift/TagsV2/TagsOptions.swift similarity index 92% rename from Sources/ResgenSwift/Strings/Tag/TagsOptions.swift rename to Sources/ResgenSwift/TagsV2/TagsOptions.swift index e5372b9..d4e4fac 100644 --- a/Sources/ResgenSwift/Strings/Tag/TagsOptions.swift +++ b/Sources/ResgenSwift/TagsV2/TagsOptions.swift @@ -15,8 +15,8 @@ struct TagsOptions: ParsableArguments { @Argument(help: "Input files where tags ared defined.", transform: { $0.replaceTiltWithHomeDirectoryPath() }) var inputFile: String - @Option(help: "Lang to generate. (\"ium\" by default)") - var lang: String = "ium" + @Option(help: "Target(s) analytics to generate. (\"matomo\" | \"firebase\")") + var target: String @Option(help: "Path where to generate the extension.", transform: { $0.replaceTiltWithHomeDirectoryPath() }) var extensionOutputPath: String diff --git a/Tests/ResgenSwiftTests/Strings/TagsGeneratorTests.swift b/Tests/ResgenSwiftTests/Strings/TagsGeneratorTests.swift index 561c70c..619f587 100644 --- a/Tests/ResgenSwiftTests/Strings/TagsGeneratorTests.swift +++ b/Tests/ResgenSwiftTests/Strings/TagsGeneratorTests.swift @@ -13,28 +13,28 @@ import ToolCore final class TagsGeneratorTests: XCTestCase { - private func getDefinition(name: String, lang: String, tags: [String]) -> Definition { - let definition = Definition(name: name) + private func getDefinition(name: String, lang: String, tags: [String]) -> TagDefinition { + let definition = TagDefinition(title: name) definition.tags = tags - definition.translations = [lang: "Some translation"] +// definition.translations = [lang: "Some translation"] return definition } func testGeneratedExtensionContent() { // Given - let sectionOne = Section(name: "section_one") + let sectionOne = TagSection(name: "section_one") sectionOne.definitions = [ getDefinition(name: "s1_def_one", lang: "ium", tags: ["ios","iosonly"]), getDefinition(name: "s1_def_two", lang: "ium", tags: ["ios","iosonly"]), ] - let sectionTwo = Section(name: "section_two") + let sectionTwo = TagSection(name: "section_two") sectionTwo.definitions = [ getDefinition(name: "s2_def_one", lang: "ium", tags: ["ios","iosonly"]), getDefinition(name: "s2_def_two", lang: "ium", tags: ["droid","droidonly"]) ] - let sectionThree = Section(name: "section_three") + let sectionThree = TagSection(name: "section_three") sectionThree.definitions = [ getDefinition(name: "s3_def_one", lang: "ium", tags: ["droid","droidonly"]), getDefinition(name: "s3_def_two", lang: "ium", tags: ["droid","droidonly"]) @@ -42,7 +42,7 @@ final class TagsGeneratorTests: XCTestCase { // When let extensionContent = TagsGenerator.getExtensionContent(sections: [sectionOne, sectionTwo, sectionThree], - lang: "ium", + targets: [Tags.TargetType.firebase], tags: ["ios", "iosonly"], staticVar: false, extensionName: "GenTags") -- 2.39.5 From 5fd680110c5567592b0bb0ed2f7971869a544580 Mon Sep 17 00:00:00 2001 From: Loris Perret Date: Tue, 5 Dec 2023 17:09:24 +0100 Subject: [PATCH 04/21] Fix forgotten code --- .../Tags/Generated/Tags+GenAllScript.swift | 51 +++++++++++++++---- .../TagsV2/Generator/TagsGenerator.swift | 6 --- 2 files changed, 42 insertions(+), 15 deletions(-) diff --git a/SampleFiles/Tags/Generated/Tags+GenAllScript.swift b/SampleFiles/Tags/Generated/Tags+GenAllScript.swift index 45fc4ea..1702990 100644 --- a/SampleFiles/Tags/Generated/Tags+GenAllScript.swift +++ b/SampleFiles/Tags/Generated/Tags+GenAllScript.swift @@ -1,20 +1,53 @@ -// Generated by ResgenSwift.Strings.Tags 1.2 +// Generated by ResgenSwift.Tags 1.2 import UIKit +// MARK: - Protocol + +protocol AnalyticsManager { + func logScreen(name: String, path: String) + func logEvent(name: String) +} +// MARK: - Matomo + +class MatomoAnalyticsManager: AnalyticsManager { + func logScreen(name: String, path: String) { + + } + + func logEvent(name: String) { + + } +} +// MARK: - Firebase + +class FirebaseAnalyticsManager: AnalyticsManager { + func logScreen(name: String, path: String) { + + } + + func logEvent(name: String) { + + } +} + extension Tags { + + // MARK: - Properties + + let managers: [AnalyticsManager] = [MatomoAnalyticsManager(), FirebaseAnalyticsManager()] // MARK: - ScreenTag - /// Translation in ium : - /// Ecran un - var screen_one: String { - "Ecran un" + func logEcranUn() { + managers.forEach { manager in + manager.logScreen(name: name, path: path) + } } - /// Translation in ium : - /// Ecran deux - var screen_two: String { - "Ecran deux" + func logEcranDeux() { + managers.forEach { manager in + manager.logEvent(name: name) + } } } diff --git a/Sources/ResgenSwift/TagsV2/Generator/TagsGenerator.swift b/Sources/ResgenSwift/TagsV2/Generator/TagsGenerator.swift index c53e334..eb32e67 100644 --- a/Sources/ResgenSwift/TagsV2/Generator/TagsGenerator.swift +++ b/Sources/ResgenSwift/TagsV2/Generator/TagsGenerator.swift @@ -12,12 +12,6 @@ import CoreVideo class TagsGenerator { static func writeExtensionFiles(sections: [TagSection], target: String, tags: [String], staticVar: Bool, extensionName: String, extensionFilePath: String) { // Get target type from enum - target.split(separator: " ").forEach { target in - Tags.TargetType.allCases.forEach { value in - - } - } - let targetsString: [String] = target.components(separatedBy: " ") var targets: [Tags.TargetType] = [] -- 2.39.5 From 3e133773a9bf256ebb08e9730628c2f3a9b22047 Mon Sep 17 00:00:00 2001 From: Loris Perret Date: Wed, 6 Dec 2023 10:34:27 +0100 Subject: [PATCH 05/21] Add Manager + MatomoManager --- .../Tags/Generated/Tags+GenAllScript.swift | 83 ++++++++++++-- .../Tags/Generator/FirebaseGenerator.swift | 36 ++++++ .../Tags/Generator/MatomoGenerator.swift | 99 ++++++++++++++++ .../Generator/TagsGenerator.swift | 107 ++++++++++++------ .../Model/TagDefinition.swift | 39 ++----- .../{TagsV2 => Tags}/Model/TagSection.swift | 0 .../Parser/TagFileParser.swift | 0 .../ResgenSwift/{TagsV2 => Tags}/Tags.swift | 0 .../{TagsV2 => Tags}/TagsOptions.swift | 0 9 files changed, 288 insertions(+), 76 deletions(-) create mode 100644 Sources/ResgenSwift/Tags/Generator/FirebaseGenerator.swift create mode 100644 Sources/ResgenSwift/Tags/Generator/MatomoGenerator.swift rename Sources/ResgenSwift/{TagsV2 => Tags}/Generator/TagsGenerator.swift (62%) rename Sources/ResgenSwift/{TagsV2 => Tags}/Model/TagDefinition.swift (77%) rename Sources/ResgenSwift/{TagsV2 => Tags}/Model/TagSection.swift (100%) rename Sources/ResgenSwift/{TagsV2 => Tags}/Parser/TagFileParser.swift (100%) rename Sources/ResgenSwift/{TagsV2 => Tags}/Tags.swift (100%) rename Sources/ResgenSwift/{TagsV2 => Tags}/TagsOptions.swift (100%) diff --git a/SampleFiles/Tags/Generated/Tags+GenAllScript.swift b/SampleFiles/Tags/Generated/Tags+GenAllScript.swift index 1702990..ebed455 100644 --- a/SampleFiles/Tags/Generated/Tags+GenAllScript.swift +++ b/SampleFiles/Tags/Generated/Tags+GenAllScript.swift @@ -1,27 +1,70 @@ // Generated by ResgenSwift.Tags 1.2 import UIKit +import MatomoTracker +import Firebase // MARK: - Protocol -protocol AnalyticsManager { +protocol AnalyticsManagerProtocol { func logScreen(name: String, path: String) func logEvent(name: String) } + // MARK: - Matomo -class MatomoAnalyticsManager: AnalyticsManager { - func logScreen(name: String, path: String) { +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) { - + guard !tracker.isOptedOut else { return } + tracker.track( + eventWithCategory: "category", + action: "action", + name: name, + number: nil, + url: nil + ) } } + // MARK: - Firebase -class FirebaseAnalyticsManager: AnalyticsManager { +class FirebaseAnalyticsManager: AnalyticsManagerProtocol { func logScreen(name: String, path: String) { } @@ -31,23 +74,41 @@ class FirebaseAnalyticsManager: AnalyticsManager { } } -extension Tags { +// MARK: - Manager + +class AnalyticsManager { + static var shared = AnalyticsManager() // MARK: - Properties - let managers: [AnalyticsManager] = [MatomoAnalyticsManager(), FirebaseAnalyticsManager()] + var managers: [AnalyticsManagerProtocol] = [] - // MARK: - ScreenTag + func configure(sideId: String, url: String) { + managers.append(MatomoAnalyticsManager(siteId: sideId, url: url)) - func logEcranUn() { + managers.append(FirebaseAnalyticsManager()) + FirebaseApp.configure() + } + + private func logScreen(name: String, path: String) { managers.forEach { manager in manager.logScreen(name: name, path: path) } } - - func logEcranDeux() { + + private func logEvent(name: String) { managers.forEach { manager in manager.logEvent(name: name) } } + + // MARK: - ScreenTag + + func logScreenEcranUn(name: String, path: String) { + logScreen(name: name, path: path) + } + + func logEventEcranDeux(name: String) { + logEvent(name: name) + } } diff --git a/Sources/ResgenSwift/Tags/Generator/FirebaseGenerator.swift b/Sources/ResgenSwift/Tags/Generator/FirebaseGenerator.swift new file mode 100644 index 0000000..4226c55 --- /dev/null +++ b/Sources/ResgenSwift/Tags/Generator/FirebaseGenerator.swift @@ -0,0 +1,36 @@ +// +// FirebaseGenerator.swift +// +// +// Created by Loris Perret on 05/12/2023. +// + +import Foundation + +enum FirebaseGenerator { + var header: String { + """ + // MARK: - Firebase + + class FirebaseAnalyticsManager: AnalyticsManagerProtocol { + """ + } + + var footer: String { + """ + } + + """ + } + + var logScreen: String { + """ + """ + } + + var logEvent: String { + """ + """ + } + +} diff --git a/Sources/ResgenSwift/Tags/Generator/MatomoGenerator.swift b/Sources/ResgenSwift/Tags/Generator/MatomoGenerator.swift new file mode 100644 index 0000000..2d7b56a --- /dev/null +++ b/Sources/ResgenSwift/Tags/Generator/MatomoGenerator.swift @@ -0,0 +1,99 @@ +// +// MatomoGenerator.swift +// +// +// Created by Loris Perret on 05/12/2023. +// + +import Foundation + +enum MatomoGenerator { + case service + + var content: String { + [ + MatomoGenerator.service.header, + MatomoGenerator.service.setup, + MatomoGenerator.service.logScreen, + MatomoGenerator.service.logEvent, + MatomoGenerator.service.footer + ] + .joined(separator: "\n") + } + + private var header: String { + """ + // MARK: - Matomo + + class MatomoAnalyticsManager: AnalyticsManagerProtocol { + + // MARK: - Properties + + private var tracker: MatomoTracker + + """ + } + + private var setup: String { + """ + // 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 + + """ + } + + private var logScreen: String { + """ + func logScreen(name: String, path: String) { + guard !tracker.isOptedOut else { return } + guard let trackerUrl = tracker.contentBase?.absoluteString else { return } + let urlString = URL(string: "\\(trackerUrl)" + "/" + "\\(path)" + "iOS") + tracker.track( + view: [name], + url: urlString + ) + } + + """ + } + + private var logEvent: String { + """ + func logEvent(name: String) { + guard !tracker.isOptedOut else { return } + tracker.track( + eventWithCategory: "category", + action: "action", + name: name, + number: nil, + url: nil + ) + } + """ + } + + private var footer: String { + """ + } + + """ + } +} diff --git a/Sources/ResgenSwift/TagsV2/Generator/TagsGenerator.swift b/Sources/ResgenSwift/Tags/Generator/TagsGenerator.swift similarity index 62% rename from Sources/ResgenSwift/TagsV2/Generator/TagsGenerator.swift rename to Sources/ResgenSwift/Tags/Generator/TagsGenerator.swift index eb32e67..48f2e67 100644 --- a/Sources/ResgenSwift/TagsV2/Generator/TagsGenerator.swift +++ b/Sources/ResgenSwift/Tags/Generator/TagsGenerator.swift @@ -10,10 +10,11 @@ import ToolCore import CoreVideo class TagsGenerator { + static var targets: [Tags.TargetType] = [] + static func writeExtensionFiles(sections: [TagSection], target: String, tags: [String], staticVar: Bool, extensionName: String, extensionFilePath: String) { // Get target type from enum let targetsString: [String] = target.components(separatedBy: " ") - var targets: [Tags.TargetType] = [] Tags.TargetType.allCases.forEach { enumTarget in if targetsString.contains(enumTarget.value) { @@ -25,7 +26,6 @@ class TagsGenerator { // Get extension content let extensionFileContent = Self.getExtensionContent(sections: sections, - targets: targets, tags: tags, staticVar: staticVar, extensionName: extensionName) @@ -43,10 +43,10 @@ class TagsGenerator { // MARK: - Extension content - static func getExtensionContent(sections: [TagSection], targets: [Tags.TargetType], tags: [String], staticVar: Bool, extensionName: String) -> String { + static func getExtensionContent(sections: [TagSection], tags: [String], staticVar: Bool, extensionName: String) -> String { [ - Self.getHeader(extensionClassname: extensionName, staticVar: staticVar, targets: targets), - Self.getProperties(sections: sections, target: "target", tags: tags, staticVar: staticVar), + Self.getHeader(extensionClassname: extensionName, staticVar: staticVar), + Self.getProperties(sections: sections, tags: tags, staticVar: staticVar), Self.getFooter() ] .joined(separator: "\n") @@ -54,67 +54,100 @@ class TagsGenerator { // MARK: - Extension part - private static func getHeader(extensionClassname: String, staticVar: Bool, targets: [Tags.TargetType]) -> String { + private static func getHeader(extensionClassname: String, staticVar: Bool) -> String { """ // Generated by ResgenSwift.\(Tags.toolName) \(ResgenSwiftVersion) \(staticVar ? "typelias Tags = String\n\n" : "")import UIKit + \(Self.getImport()) - \(Self.getAnalytics(targets: targets)) + \(Self.getAnalytics()) - extension \(extensionClassname) { + // MARK: - Manager + + class AnalyticsManager { + static var shared = AnalyticsManager() // MARK: - Properties - let managers: [AnalyticsManager] = [\(Self.getAnalyticsProperties(targets: targets))] + var managers: [AnalyticsManagerProtocol] = [] + + \(Self.getAnalyticsProperties()) + + \(Self.getPrivateLogFunction()) """ } - private static func getAnalyticsProperties(targets: [Tags.TargetType]) -> String { - let matomo = "MatomoAnalyticsManager()" - let firebase = "FirebaseAnalyticsManager()" - + private static func getImport() -> String { var result: [String] = [] if targets.contains(Tags.TargetType.matomo) { - result.append(matomo) + result.append("import MatomoTracker") } - if targets.contains(Tags.TargetType.firebase) { - result.append(firebase) + result.append("import Firebase") } - return result.joined(separator: ", ") + return result.joined(separator: "\n") } - private static func getAnalytics(targets: [Tags.TargetType]) -> String { + private static func getPrivateLogFunction() -> String { + """ + private func logScreen(name: String, path: String) { + managers.forEach { manager in + manager.logScreen(name: name, path: path) + } + } + + private func logEvent(name: String) { + managers.forEach { manager in + manager.logEvent(name: name) + } + } + """ + } + + private static func getAnalyticsProperties() -> String { + var header = " // MARK: - Methods\n" + var content: [String] = [] + let footer = " }" + + if targets.contains(Tags.TargetType.matomo) { + header = "func configure(sideId: String, url: String) {" + } else if targets.contains(Tags.TargetType.firebase) { + header = "func configure() {" + } + + if targets.contains(Tags.TargetType.matomo) { + content.append(" managers.append(MatomoAnalyticsManager(siteId: sideId, url: url))\n") + } + if targets.contains(Tags.TargetType.firebase) { + content.append(" managers.append(FirebaseAnalyticsManager())") + } + + return [ + header, + content.joined(separator: "\n"), + footer + ] + .joined(separator: "\n") + } + + private static func getAnalytics() -> String { let proto = """ // MARK: - Protocol - protocol AnalyticsManager { + protocol AnalyticsManagerProtocol { func logScreen(name: String, path: String) func logEvent(name: String) } - """ - let matomo = """ - // MARK: - Matomo - - class MatomoAnalyticsManager: AnalyticsManager { - func logScreen(name: String, path: String) { - - } - - func logEvent(name: String) { - - } - } """ let firebase = """ // MARK: - Firebase - class FirebaseAnalyticsManager: AnalyticsManager { + class FirebaseAnalyticsManager: AnalyticsManagerProtocol { func logScreen(name: String, path: String) { } @@ -128,7 +161,7 @@ class TagsGenerator { var result: [String] = [proto] if targets.contains(Tags.TargetType.matomo) { - result.append(matomo) + result.append(MatomoGenerator.service.content) } if targets.contains(Tags.TargetType.firebase) { @@ -138,7 +171,7 @@ class TagsGenerator { return result.joined(separator: "\n") } - private static func getProperties(sections: [TagSection], target: String, tags: [String], staticVar: Bool) -> String { + private static func getProperties(sections: [TagSection], tags: [String], staticVar: Bool) -> String { sections .compactMap { section in // Check that at least one string will be generated @@ -153,9 +186,9 @@ class TagsGenerator { } if staticVar { - res += "\n\n\(definition.getStaticProperty(forTarget: target))" + res += "\n\n\(definition.getStaticProperty())" } else { - res += "\n\n\(definition.getProperty(forTarget: target))" + res += "\n\n\(definition.getProperty())" } } return res diff --git a/Sources/ResgenSwift/TagsV2/Model/TagDefinition.swift b/Sources/ResgenSwift/Tags/Model/TagDefinition.swift similarity index 77% rename from Sources/ResgenSwift/TagsV2/Model/TagDefinition.swift rename to Sources/ResgenSwift/Tags/Model/TagDefinition.swift index 0fd3f25..97736c2 100644 --- a/Sources/ResgenSwift/TagsV2/Model/TagDefinition.swift +++ b/Sources/ResgenSwift/Tags/Model/TagDefinition.swift @@ -91,48 +91,31 @@ class TagDefinition { pascalCaseTitle.append(contentsOf: word.uppercasedFirst()) } - return "log\(pascalCaseTitle)" + return "log\(type == TagType.screen.value ? "Screen" : "Event")\(pascalCaseTitle)" } private func getlogFunction() -> String { if type == TagType.screen.value { - "manager.logScreen(name: name, path: path)" + "logScreen(name: name, path: path)" } else { - "manager.logEvent(name: name)" + "logEvent(name: name)" } } // MARK: - Raw strings - func getProperty(forTarget target: String) -> String { - return """ - func \(getFuncName())() { - managers.forEach { manager in - \(getlogFunction()) - } + func getProperty() -> String { + """ + func \(getFuncName())(name: String\(type == TagType.screen.value ? ", path: String" : "")) { + \(getlogFunction()) } """ } - func getStaticProperty(forTarget target: String) -> String { -// guard let translation = translations[lang] else { -// let error = StringiumError.langNotDefined(lang, title, reference != nil) -// print(error.description) -// Stringium.exit(withError: error) -// } -// -// return """ -// /// Translation in \(lang) : -// /// \(translation) -// static var \(title): String { -// "\(translation)" -// } -// """ - return """ - static func \(getFuncName())() { - managers.forEach { manager in - \(getlogFunction()) - } + func getStaticProperty() -> String { + """ + static func \(getFuncName())(name: name\(type == TagType.screen.value ? ", path: String" : "")) { + \(getlogFunction()) } """ } diff --git a/Sources/ResgenSwift/TagsV2/Model/TagSection.swift b/Sources/ResgenSwift/Tags/Model/TagSection.swift similarity index 100% rename from Sources/ResgenSwift/TagsV2/Model/TagSection.swift rename to Sources/ResgenSwift/Tags/Model/TagSection.swift diff --git a/Sources/ResgenSwift/TagsV2/Parser/TagFileParser.swift b/Sources/ResgenSwift/Tags/Parser/TagFileParser.swift similarity index 100% rename from Sources/ResgenSwift/TagsV2/Parser/TagFileParser.swift rename to Sources/ResgenSwift/Tags/Parser/TagFileParser.swift diff --git a/Sources/ResgenSwift/TagsV2/Tags.swift b/Sources/ResgenSwift/Tags/Tags.swift similarity index 100% rename from Sources/ResgenSwift/TagsV2/Tags.swift rename to Sources/ResgenSwift/Tags/Tags.swift diff --git a/Sources/ResgenSwift/TagsV2/TagsOptions.swift b/Sources/ResgenSwift/Tags/TagsOptions.swift similarity index 100% rename from Sources/ResgenSwift/TagsV2/TagsOptions.swift rename to Sources/ResgenSwift/Tags/TagsOptions.swift -- 2.39.5 From 6aef8bc2dee5ecb2f92be4ea94fc5aa85634e508 Mon Sep 17 00:00:00 2001 From: Loris Perret Date: Wed, 6 Dec 2023 11:23:26 +0100 Subject: [PATCH 06/21] Add FirebaseManager --- .../Tags/Generated/Tags+GenAllScript.swift | 25 +++++++--- .../Tags/Generator/FirebaseGenerator.swift | 48 ++++++++++++++----- .../Tags/Generator/TagsGenerator.swift | 33 +++++++------ .../Tags/Model/TagDefinition.swift | 8 ++-- 4 files changed, 73 insertions(+), 41 deletions(-) diff --git a/SampleFiles/Tags/Generated/Tags+GenAllScript.swift b/SampleFiles/Tags/Generated/Tags+GenAllScript.swift index ebed455..e906ace 100644 --- a/SampleFiles/Tags/Generated/Tags+GenAllScript.swift +++ b/SampleFiles/Tags/Generated/Tags+GenAllScript.swift @@ -66,11 +66,15 @@ class MatomoAnalyticsManager: AnalyticsManagerProtocol { class FirebaseAnalyticsManager: AnalyticsManagerProtocol { func logScreen(name: String, path: String) { - + Analytics.logEvent(AnalyticsEventScreenView, parameters: [AnalyticsParameterScreenName: name]) } func logEvent(name: String) { - + var parameters = [ + AnalyticsParameterValue: name + ] + + Analytics.logEvent(AnalyticsEventSelectContent, parameters: parameters) } } @@ -83,20 +87,27 @@ class AnalyticsManager { var managers: [AnalyticsManagerProtocol] = [] + private var isEnabled: Bool = true + + // MARK: - Methods + + func setAnalyticsEnabled(_ enable: Bool) { isEnabled = enable } + func configure(sideId: String, url: String) { managers.append(MatomoAnalyticsManager(siteId: sideId, url: url)) managers.append(FirebaseAnalyticsManager()) - FirebaseApp.configure() } private func logScreen(name: String, path: String) { + guard isEnabled else { return } managers.forEach { manager in manager.logScreen(name: name, path: path) } } private func logEvent(name: String) { + guard isEnabled else { return } managers.forEach { manager in manager.logEvent(name: name) } @@ -104,11 +115,11 @@ class AnalyticsManager { // MARK: - ScreenTag - func logScreenEcranUn(name: String, path: String) { - logScreen(name: name, path: path) + func logScreenEcranUn() { + logScreen(name: "Ecran un", path: "ecran_un/") } - func logEventEcranDeux(name: String) { - logEvent(name: name) + func logEventEcranDeux() { + logEvent(name: "Ecran deux") } } diff --git a/Sources/ResgenSwift/Tags/Generator/FirebaseGenerator.swift b/Sources/ResgenSwift/Tags/Generator/FirebaseGenerator.swift index 4226c55..5f2b690 100644 --- a/Sources/ResgenSwift/Tags/Generator/FirebaseGenerator.swift +++ b/Sources/ResgenSwift/Tags/Generator/FirebaseGenerator.swift @@ -8,7 +8,19 @@ import Foundation enum FirebaseGenerator { - var header: String { + case service + + var content: String { + [ + FirebaseGenerator.service.header, + FirebaseGenerator.service.logScreen, + FirebaseGenerator.service.logEvent, + FirebaseGenerator.service.footer + ] + .joined(separator: "\n") + } + + private var header: String { """ // MARK: - Firebase @@ -16,21 +28,31 @@ enum FirebaseGenerator { """ } - var footer: String { + private var logScreen: String { + """ + func logScreen(name: String, path: String) { + Analytics.logEvent(AnalyticsEventScreenView, parameters: [AnalyticsParameterScreenName: name]) + } + + """ + } + + private var logEvent: String { + """ + func logEvent(name: String) { + var parameters = [ + AnalyticsParameterValue: name + ] + + Analytics.logEvent(AnalyticsEventSelectContent, parameters: parameters) + } + """ + } + + private var footer: String { """ } """ } - - var logScreen: String { - """ - """ - } - - var logEvent: String { - """ - """ - } - } diff --git a/Sources/ResgenSwift/Tags/Generator/TagsGenerator.swift b/Sources/ResgenSwift/Tags/Generator/TagsGenerator.swift index 48f2e67..f5f7fce 100644 --- a/Sources/ResgenSwift/Tags/Generator/TagsGenerator.swift +++ b/Sources/ResgenSwift/Tags/Generator/TagsGenerator.swift @@ -62,7 +62,6 @@ class TagsGenerator { \(Self.getImport()) \(Self.getAnalytics()) - // MARK: - Manager class AnalyticsManager { @@ -72,12 +71,24 @@ class TagsGenerator { var managers: [AnalyticsManagerProtocol] = [] + \(Self.getEnabledContent()) + \(Self.getAnalyticsProperties()) \(Self.getPrivateLogFunction()) """ } + private static func getEnabledContent() -> String { + """ + private var isEnabled: Bool = true + + // MARK: - Methods + + func setAnalyticsEnabled(_ enable: Bool) { isEnabled = enable } + """ + } + private static func getImport() -> String { var result: [String] = [] @@ -94,12 +105,14 @@ class TagsGenerator { private static func getPrivateLogFunction() -> String { """ private func logScreen(name: String, path: String) { + guard isEnabled else { return } managers.forEach { manager in manager.logScreen(name: name, path: path) } } private func logEvent(name: String) { + guard isEnabled else { return } managers.forEach { manager in manager.logEvent(name: name) } @@ -108,7 +121,7 @@ class TagsGenerator { } private static func getAnalyticsProperties() -> String { - var header = " // MARK: - Methods\n" + var header = "" var content: [String] = [] let footer = " }" @@ -144,20 +157,6 @@ class TagsGenerator { """ - let firebase = """ - // MARK: - Firebase - - class FirebaseAnalyticsManager: AnalyticsManagerProtocol { - func logScreen(name: String, path: String) { - - } - - func logEvent(name: String) { - - } - } - """ - var result: [String] = [proto] if targets.contains(Tags.TargetType.matomo) { @@ -165,7 +164,7 @@ class TagsGenerator { } if targets.contains(Tags.TargetType.firebase) { - result.append(firebase) + result.append(FirebaseGenerator.service.content) } return result.joined(separator: "\n") diff --git a/Sources/ResgenSwift/Tags/Model/TagDefinition.swift b/Sources/ResgenSwift/Tags/Model/TagDefinition.swift index 97736c2..806ec8b 100644 --- a/Sources/ResgenSwift/Tags/Model/TagDefinition.swift +++ b/Sources/ResgenSwift/Tags/Model/TagDefinition.swift @@ -96,9 +96,9 @@ class TagDefinition { private func getlogFunction() -> String { if type == TagType.screen.value { - "logScreen(name: name, path: path)" + "logScreen(name: \"\(name)\", path: \"\(path)\")" } else { - "logEvent(name: name)" + "logEvent(name: \"\(name)\")" } } @@ -106,7 +106,7 @@ class TagDefinition { func getProperty() -> String { """ - func \(getFuncName())(name: String\(type == TagType.screen.value ? ", path: String" : "")) { + func \(getFuncName())() { \(getlogFunction()) } """ @@ -114,7 +114,7 @@ class TagDefinition { func getStaticProperty() -> String { """ - static func \(getFuncName())(name: name\(type == TagType.screen.value ? ", path: String" : "")) { + static func \(getFuncName())() { \(getlogFunction()) } """ -- 2.39.5 From 2a144fc00ef857d14d41ade5982582611b3249c4 Mon Sep 17 00:00:00 2001 From: Loris Perret Date: Wed, 6 Dec 2023 14:48:47 +0100 Subject: [PATCH 07/21] Test Tags generation --- .../Tags/Generator/TagsGenerator.swift | 4 +- .../Strings/StringsFileGeneratorTests.swift | 38 ++ .../Strings/TagsGeneratorTests.swift | 82 ---- Tests/ResgenSwiftTests/Tags/DiffString.swift | 135 ++++++ .../Tags/TagDefinitionTests.swift | 162 +++++++ .../Tags/TagSectionTests.swift | 104 ++++ .../Tags/TagsGeneratorTests.swift | 454 ++++++++++++++++++ 7 files changed, 895 insertions(+), 84 deletions(-) delete mode 100644 Tests/ResgenSwiftTests/Strings/TagsGeneratorTests.swift create mode 100644 Tests/ResgenSwiftTests/Tags/DiffString.swift create mode 100644 Tests/ResgenSwiftTests/Tags/TagDefinitionTests.swift create mode 100644 Tests/ResgenSwiftTests/Tags/TagSectionTests.swift create mode 100644 Tests/ResgenSwiftTests/Tags/TagsGeneratorTests.swift diff --git a/Sources/ResgenSwift/Tags/Generator/TagsGenerator.swift b/Sources/ResgenSwift/Tags/Generator/TagsGenerator.swift index f5f7fce..e803a69 100644 --- a/Sources/ResgenSwift/Tags/Generator/TagsGenerator.swift +++ b/Sources/ResgenSwift/Tags/Generator/TagsGenerator.swift @@ -126,13 +126,13 @@ class TagsGenerator { let footer = " }" if targets.contains(Tags.TargetType.matomo) { - header = "func configure(sideId: String, url: String) {" + header = "func configure(siteId: String, url: String) {" } else if targets.contains(Tags.TargetType.firebase) { header = "func configure() {" } if targets.contains(Tags.TargetType.matomo) { - content.append(" managers.append(MatomoAnalyticsManager(siteId: sideId, url: url))\n") + content.append(" managers.append(MatomoAnalyticsManager(siteId: siteId, url: url))") } if targets.contains(Tags.TargetType.firebase) { content.append(" managers.append(FirebaseAnalyticsManager())") diff --git a/Tests/ResgenSwiftTests/Strings/StringsFileGeneratorTests.swift b/Tests/ResgenSwiftTests/Strings/StringsFileGeneratorTests.swift index 5d3193c..764e5af 100644 --- a/Tests/ResgenSwiftTests/Strings/StringsFileGeneratorTests.swift +++ b/Tests/ResgenSwiftTests/Strings/StringsFileGeneratorTests.swift @@ -144,6 +144,22 @@ final class StringsFileGeneratorTests: XCTestCase { extension GenStrings { + enum KeyStrings: String { + case s1_def_one = "s1_def_one" + case s1_def_two = "s1_def_two" + case s2_def_one = "s2_def_one" + case s2_def_two = "s2_def_two" + + var keyPath: KeyPath { + switch self { + case .s1_def_one: return \\GenStrings.s1_def_one + case .s1_def_two: return \\GenStrings.s1_def_two + case .s2_def_one: return \\GenStrings.s2_def_one + case .s2_def_two: return \\GenStrings.s2_def_two + } + } + } + // MARK: - section_one /// Translation in fr : @@ -174,6 +190,9 @@ final class StringsFileGeneratorTests: XCTestCase { } """ + if extensionContent != expect { + print(prettyFirstDifferenceBetweenStrings(s1: extensionContent, s2: expect)) + } XCTAssertEqual(extensionContent.adaptForXCTest(), expect.adaptForXCTest()) } @@ -221,6 +240,22 @@ final class StringsFileGeneratorTests: XCTestCase { extension GenStrings { + enum KeyStrings: String { + case s1_def_one = "s1_def_one" + case s1_def_two = "s1_def_two" + case s2_def_one = "s2_def_one" + case s2_def_two = "s2_def_two" + + var keyPath: KeyPath { + switch self { + case .s1_def_one: return \\GenStrings.s1_def_one + case .s1_def_two: return \\GenStrings.s1_def_two + case .s2_def_one: return \\GenStrings.s2_def_one + case .s2_def_two: return \\GenStrings.s2_def_two + } + } + } + // MARK: - section_one /// Translation in fr : @@ -251,6 +286,9 @@ final class StringsFileGeneratorTests: XCTestCase { } """ + if extensionContent != expect { + print(prettyFirstDifferenceBetweenStrings(s1: extensionContent, s2: expect)) + } XCTAssertEqual(extensionContent.adaptForXCTest(), expect.adaptForXCTest()) } } diff --git a/Tests/ResgenSwiftTests/Strings/TagsGeneratorTests.swift b/Tests/ResgenSwiftTests/Strings/TagsGeneratorTests.swift deleted file mode 100644 index 619f587..0000000 --- a/Tests/ResgenSwiftTests/Strings/TagsGeneratorTests.swift +++ /dev/null @@ -1,82 +0,0 @@ -// -// TagsGeneratorTests.swift -// -// -// Created by Thibaut Schmitt on 06/09/2022. -// - -import Foundation -import XCTest -import ToolCore - -@testable import ResgenSwift - -final class TagsGeneratorTests: XCTestCase { - - private func getDefinition(name: String, lang: String, tags: [String]) -> TagDefinition { - let definition = TagDefinition(title: name) - definition.tags = tags -// definition.translations = [lang: "Some translation"] - return definition - } - - func testGeneratedExtensionContent() { - // Given - let sectionOne = TagSection(name: "section_one") - sectionOne.definitions = [ - getDefinition(name: "s1_def_one", lang: "ium", tags: ["ios","iosonly"]), - getDefinition(name: "s1_def_two", lang: "ium", tags: ["ios","iosonly"]), - ] - - let sectionTwo = TagSection(name: "section_two") - sectionTwo.definitions = [ - getDefinition(name: "s2_def_one", lang: "ium", tags: ["ios","iosonly"]), - getDefinition(name: "s2_def_two", lang: "ium", tags: ["droid","droidonly"]) - ] - - let sectionThree = TagSection(name: "section_three") - sectionThree.definitions = [ - getDefinition(name: "s3_def_one", lang: "ium", tags: ["droid","droidonly"]), - getDefinition(name: "s3_def_two", lang: "ium", tags: ["droid","droidonly"]) - ] - - // When - let extensionContent = TagsGenerator.getExtensionContent(sections: [sectionOne, sectionTwo, sectionThree], - targets: [Tags.TargetType.firebase], - tags: ["ios", "iosonly"], - staticVar: false, - extensionName: "GenTags") - // Expect Tags - let expect = """ - // Generated by ResgenSwift.Strings.Tags \(ResgenSwiftVersion) - - import UIKit - - extension GenTags { - // MARK: - section_one - - /// Translation in ium : - /// Some translation - var s1_def_one: String { - "Some translation" - } - - /// Translation in ium : - /// Some translation - var s1_def_two: String { - "Some translation" - } - - // MARK: - section_two - - /// Translation in ium : - /// Some translation - var s2_def_one: String { - "Some translation" - } - } - """ - - XCTAssertEqual(extensionContent.adaptForXCTest(), expect.adaptForXCTest()) - } -} diff --git a/Tests/ResgenSwiftTests/Tags/DiffString.swift b/Tests/ResgenSwiftTests/Tags/DiffString.swift new file mode 100644 index 0000000..60f41cf --- /dev/null +++ b/Tests/ResgenSwiftTests/Tags/DiffString.swift @@ -0,0 +1,135 @@ +// +// File.swift +// +// +// Created by Loris Perret on 06/12/2023. +// + +import Foundation + + +/// Find first differing character between two strings +/// +/// :param: s1 First String +/// :param: s2 Second String +/// +/// :returns: .DifferenceAtIndex(i) or .NoDifference +public func firstDifferenceBetweenStrings(s1: NSString, s2: NSString) -> FirstDifferenceResult { + let len1 = s1.length + let len2 = s2.length + + let lenMin = min(len1, len2) + + for i in 0.. String { + let firstDifferenceResult = firstDifferenceBetweenStrings(s1: s1 as NSString, s2: s2 as NSString) + return prettyDescriptionOfFirstDifferenceResult(firstDifferenceResult: firstDifferenceResult, s1: s1 as NSString, s2: s2 as NSString) as String +} + + +/// Create a formatted String representation of a FirstDifferenceResult for two strings +/// +/// :param: firstDifferenceResult FirstDifferenceResult +/// :param: s1 First string used in generation of firstDifferenceResult +/// :param: s2 Second string used in generation of firstDifferenceResult +/// +/// :returns: a printable string, possibly containing significant whitespace and newlines +public func prettyDescriptionOfFirstDifferenceResult(firstDifferenceResult: FirstDifferenceResult, s1: NSString, s2: NSString) -> NSString { + + func diffString(index: Int, s1: NSString, s2: NSString) -> NSString { + let markerArrow = "\u{2b06}" // "⬆" + let ellipsis = "\u{2026}" // "…" + /// Given a string and a range, return a string representing that substring. + /// + /// If the range starts at a position other than 0, an ellipsis + /// will be included at the beginning. + /// + /// If the range ends before the actual end of the string, + /// an ellipsis is added at the end. + func windowSubstring(s: NSString, range: NSRange) -> String { + let validRange = NSMakeRange(range.location, min(range.length, s.length - range.location)) + let substring = s.substring(with: validRange) + + let prefix = range.location > 0 ? ellipsis : "" + let suffix = (s.length - range.location > range.length) ? ellipsis : "" + + return "\(prefix)\(substring)\(suffix)" + } + + // Show this many characters before and after the first difference + let windowPrefixLength = 10 + let windowSuffixLength = 10 + let windowLength = windowPrefixLength + 1 + windowSuffixLength + + let windowIndex = max(index - windowPrefixLength, 0) + let windowRange = NSMakeRange(windowIndex, windowLength) + + let sub1 = windowSubstring(s: s1, range: windowRange) + let sub2 = windowSubstring(s: s2, range: windowRange) + + let markerPosition = min(windowSuffixLength, index) + (windowIndex > 0 ? 1 : 0) + + let markerPrefix = String(repeating: " " as Character, count: markerPosition) + let markerLine = "\(markerPrefix)\(markerArrow)" + + return "Difference at index \(index):\n\(sub1)\n\(sub2)\n\(markerLine)" as NSString + } + + switch firstDifferenceResult { + case .NoDifference: return "No difference" + case .DifferenceAtIndex(let index): return diffString(index: index, s1: s1, s2: s2) + } +} + + +/// Result type for firstDifferenceBetweenStrings() +public enum FirstDifferenceResult { + /// Strings are identical + case NoDifference + + /// Strings differ at the specified index. + /// + /// This could mean that characters at the specified index are different, + /// or that one string is longer than the other + case DifferenceAtIndex(Int) +} + +extension FirstDifferenceResult { + /// Textual representation of a FirstDifferenceResult + public var description: String { + switch self { + case .NoDifference: + return "NoDifference" + case .DifferenceAtIndex(let index): + return "DifferenceAtIndex(\(index))" + } + } + + /// Textual representation of a FirstDifferenceResult for debugging purposes + public var debugDescription: String { + return self.description + } +} diff --git a/Tests/ResgenSwiftTests/Tags/TagDefinitionTests.swift b/Tests/ResgenSwiftTests/Tags/TagDefinitionTests.swift new file mode 100644 index 0000000..9a9d116 --- /dev/null +++ b/Tests/ResgenSwiftTests/Tags/TagDefinitionTests.swift @@ -0,0 +1,162 @@ +// +// TagDefinitionTests.swift +// +// +// Created by Loris Perret on 06/12/2023. +// + +import Foundation +import XCTest + +@testable import ResgenSwift + +final class TagDefinitionTests: XCTestCase { + + // MARK: - Match line + + func testMatchingTagDefinition() { + // Given + let line = "[definition_name]" + + // When + let definition = TagDefinition.match(line) + + // Expect + XCTAssertNotNil(definition) + XCTAssertEqual(definition?.title, "definition_name") + } + + func testNotMatchingTagDefinition() { + // Given + let line1 = "definition_name" + let line2 = "[definition_name" + let line3 = "definition_name]" + + // When + let definition1 = TagDefinition.match(line1) + let definition2 = TagDefinition.match(line2) + let definition3 = TagDefinition.match(line3) + + // Expect + XCTAssertNil(definition1) + XCTAssertNil(definition2) + XCTAssertNil(definition3) + } + + // MARK: - Matching tags + + func testMatchingTags() { + // Given + let definition = TagDefinition(title: "definition_name") + definition.tags = ["ios","iosonly","notranslation"] + + // When + let match1 = definition.hasOneOrMoreMatchingTags(inputTags: ["ios"]) + let match2 = definition.hasOneOrMoreMatchingTags(inputTags: ["iosonly"]) + let match3 = definition.hasOneOrMoreMatchingTags(inputTags: ["notranslation"]) + + + // Expect + XCTAssertTrue(match1) + XCTAssertTrue(match2) + XCTAssertTrue(match3) + } + + func testNotMatchingTags() { + // Given + let definition = TagDefinition(title: "definition_name") + definition.tags = ["ios","iosonly","notranslation"] + + // When + let match1 = definition.hasOneOrMoreMatchingTags(inputTags: ["droid"]) + let match2 = definition.hasOneOrMoreMatchingTags(inputTags: ["droidonly"]) + let match3 = definition.hasOneOrMoreMatchingTags(inputTags: ["azerty"]) + + // Expect + XCTAssertFalse(match1) + XCTAssertFalse(match2) + XCTAssertFalse(match3) + } + + // MARK: - Raw properties + + func testGeneratedRawPropertyScreen() { + // Given + let definition = TagDefinition(title: "definition_name") + definition.path = "ecran_un/" + definition.name = "Ecran un" + definition.type = "screen" + + // When + let propertyScreen = definition.getProperty() + + // Expect + let expectScreen = """ + func logScreenEcranUn() { + logScreen(name: "Ecran un", path: "ecran_un/") + } + """ + + XCTAssertEqual(propertyScreen.adaptForXCTest(), expectScreen.adaptForXCTest()) + } + + func testGeneratedRawPropertyEvent() { + // Given + let definition = TagDefinition(title: "definition_name") + definition.path = "ecran_un/" + definition.name = "Ecran un" + definition.type = "event" + + // When + let propertyEvent = definition.getProperty() + + // Expect + let expectEvent = """ + func logEventEcranUn() { + logEvent(name: "Ecran un") + } + """ + + XCTAssertEqual(propertyEvent.adaptForXCTest(), expectEvent.adaptForXCTest()) + } + + func testGeneratedRawStaticPropertyScreen() { + // Given + let definition = TagDefinition(title: "definition_name") + definition.path = "ecran_un/" + definition.name = "Ecran un" + definition.type = "screen" + + // When + let propertyScreen = definition.getStaticProperty() + + // Expect + let expectScreen = """ + static func logScreenEcranUn() { + logScreen(name: "Ecran un", path: "ecran_un/") + } + """ + + XCTAssertEqual(propertyScreen.adaptForXCTest(), expectScreen.adaptForXCTest()) + } + + func testGeneratedRawStaticPropertyEvent() { + // Given + let definition = TagDefinition(title: "definition_name") + definition.path = "ecran_un/" + definition.name = "Ecran un" + definition.type = "event" + + // When + let propertyEvent = definition.getStaticProperty() + + // Expect + let expectEvent = """ + static func logEventEcranUn() { + logEvent(name: "Ecran un") + } + """ + + XCTAssertEqual(propertyEvent.adaptForXCTest(), expectEvent.adaptForXCTest()) + } +} diff --git a/Tests/ResgenSwiftTests/Tags/TagSectionTests.swift b/Tests/ResgenSwiftTests/Tags/TagSectionTests.swift new file mode 100644 index 0000000..979fe38 --- /dev/null +++ b/Tests/ResgenSwiftTests/Tags/TagSectionTests.swift @@ -0,0 +1,104 @@ +// +// TagSectionTests.swift +// +// +// Created by Loris Perret on 06/12/2023. +// + +import Foundation +import XCTest + +@testable import ResgenSwift + +final class TagSectionTests: XCTestCase { + + // MARK: - Match line + + func testMatchingTagSection() { + // Given + let line = "[[section_name]]" + + // When + let section = TagSection.match(line) + + // Expect + XCTAssertNotNil(section) + XCTAssertEqual(section?.name, "section_name") + } + + func testNotMatchingTagSection() { + // Given + let lines = ["section_name", + "[section_name]", + "[section_name", + "[[section_name", + "[[section_name]", + "section_name]", + "section_name]]", + "[section_name]]"] + + // When + let matches = lines.compactMap { TagSection.match($0) } + + // Expect + XCTAssertEqual(matches.isEmpty, true) + } + + // MARK: - Matching tags + + func testMatchingTags() { + // Given + let section = TagSection(name: "section_name") + section.definitions = [ + { + let def = TagDefinition(title: "definition_name") + def.tags = ["ios","iosonly"] + return def + }(), + { + let def = TagDefinition(title: "definition_name_two") + def.tags = ["droid","droidonly"] + return def + }() + ] + + // When + let match1 = section.hasOneOrMoreMatchingTags(tags: ["ios"]) + let match2 = section.hasOneOrMoreMatchingTags(tags: ["iosonly"]) + let match3 = section.hasOneOrMoreMatchingTags(tags: ["droid"]) + let match4 = section.hasOneOrMoreMatchingTags(tags: ["droidonly"]) + + // Expect + XCTAssertTrue(match1) + XCTAssertTrue(match2) + XCTAssertTrue(match3) + XCTAssertTrue(match4) + } + + func testNotMatchingTags() { + // Given + let section = TagSection(name: "section_name") + section.definitions = [ + { + let def = TagDefinition(title: "definition_name") + def.tags = ["ios","iosonly"] + return def + }(), + { + let def = TagDefinition(title: "definition_name_two") + def.tags = ["droid","droidonly"] + return def + }() + ] + + // When + let match1 = section.hasOneOrMoreMatchingTags(tags: ["web"]) + let match2 = section.hasOneOrMoreMatchingTags(tags: ["webonly"]) + let match3 = section.hasOneOrMoreMatchingTags(tags: ["azerty"]) + + // Expect + XCTAssertFalse(match1) + XCTAssertFalse(match2) + XCTAssertFalse(match3) + } +} diff --git a/Tests/ResgenSwiftTests/Tags/TagsGeneratorTests.swift b/Tests/ResgenSwiftTests/Tags/TagsGeneratorTests.swift new file mode 100644 index 0000000..b10dfb5 --- /dev/null +++ b/Tests/ResgenSwiftTests/Tags/TagsGeneratorTests.swift @@ -0,0 +1,454 @@ +// +// TagsGeneratorTests.swift +// +// +// Created by Thibaut Schmitt on 06/09/2022. +// + +import Foundation +import XCTest +import ToolCore + +@testable import ResgenSwift + +final class TagsGeneratorTests: XCTestCase { + + private func getTagDefinition(title: String, path: String, name: String, type: String, tags: [String]) -> TagDefinition { + let definition = TagDefinition(title: title) + definition.path = path + definition.name = name + definition.type = type + definition.tags = tags + return definition + } + + func testGeneratedExtensionContentFirebase() { + // Given + let sectionOne = TagSection(name: "section_one") + sectionOne.definitions = [ + getTagDefinition(title: "s1_def_one", path: "s1_def_one/", name: "s1 def one", type: "screen", tags: ["ios", "iosonly"]), + getTagDefinition(title: "s1_def_two", path: "s1_def_two/", name: "s1 def two", type: "event", tags: ["ios", "iosonly"]), + ] + + let sectionTwo = TagSection(name: "section_two") + sectionTwo.definitions = [ + getTagDefinition(title: "s2_def_one", path: "s2_def_one/", name: "s2 def one", type: "screen", tags: ["ios","iosonly"]), + getTagDefinition(title: "s2_def_two", path: "s2_def_two/", name: "s2 def two", type: "event", tags: ["droid","droidonly"]), + ] + + let sectionThree = TagSection(name: "section_three") + sectionThree.definitions = [ + getTagDefinition(title: "s3_def_one", path: "s3_def_one/", name: "s3 def one", type: "screen", tags: ["droid","droidonly"]), + getTagDefinition(title: "s3_def_two", path: "s3_def_two/", name: "s3 def two", type: "event", tags: ["droid","droidonly"]), + ] + + // When + TagsGenerator.targets = [Tags.TargetType.firebase] + let extensionContent = TagsGenerator.getExtensionContent(sections: [sectionOne, sectionTwo, sectionThree], + tags: ["ios", "iosonly"], + staticVar: false, + extensionName: "GenTags") + // Expect Tags + let expect = """ + // Generated by ResgenSwift.Tags 1.2 + + import UIKit + import Firebase + + // MARK: - Protocol + + protocol AnalyticsManagerProtocol { + func logScreen(name: String, path: String) + func logEvent(name: String) + } + + // MARK: - Firebase + + class FirebaseAnalyticsManager: AnalyticsManagerProtocol { + func logScreen(name: String, path: String) { + Analytics.logEvent(AnalyticsEventScreenView, parameters: [AnalyticsParameterScreenName: name]) + } + + func logEvent(name: String) { + var parameters = [ + AnalyticsParameterValue: name + ] + + Analytics.logEvent(AnalyticsEventSelectContent, parameters: parameters) + } + } + + // MARK: - Manager + + class AnalyticsManager { + static var shared = AnalyticsManager() + + // MARK: - Properties + + var managers: [AnalyticsManagerProtocol] = [] + + private var isEnabled: Bool = true + + // MARK: - Methods + + func setAnalyticsEnabled(_ enable: Bool) { isEnabled = enable } + + func configure() { + managers.append(FirebaseAnalyticsManager()) + } + + private func logScreen(name: String, path: String) { + guard isEnabled else { return } + managers.forEach { manager in + manager.logScreen(name: name, path: path) + } + } + + private func logEvent(name: String) { + guard isEnabled else { return } + managers.forEach { manager in + manager.logEvent(name: name) + } + } + + // MARK: - section_one + + func logScreenS1DefOne() { + logScreen(name: "s1 def one", path: "s1_def_one/") + } + + func logEventS1DefTwo() { + logEvent(name: "s1 def two") + } + + // MARK: - section_two + + func logScreenS2DefOne() { + logScreen(name: "s2 def one", path: "s2_def_one/") + } + } + + """ + + if extensionContent != expect { + print(prettyFirstDifferenceBetweenStrings(s1: extensionContent, s2: expect)) + } + XCTAssertEqual(extensionContent.adaptForXCTest(), expect.adaptForXCTest()) + } + + func testGeneratedExtensionContentMatomo() { + // Given + let sectionOne = TagSection(name: "section_one") + sectionOne.definitions = [ + getTagDefinition(title: "s1_def_one", path: "s1_def_one/", name: "s1 def one", type: "screen", tags: ["ios", "iosonly"]), + getTagDefinition(title: "s1_def_two", path: "s1_def_two/", name: "s1 def two", type: "event", tags: ["ios", "iosonly"]), + ] + + let sectionTwo = TagSection(name: "section_two") + sectionTwo.definitions = [ + getTagDefinition(title: "s2_def_one", path: "s2_def_one/", name: "s2 def one", type: "screen", tags: ["ios","iosonly"]), + getTagDefinition(title: "s2_def_two", path: "s2_def_two/", name: "s2 def two", type: "event", tags: ["droid","droidonly"]), + ] + + let sectionThree = TagSection(name: "section_three") + sectionThree.definitions = [ + getTagDefinition(title: "s3_def_one", path: "s3_def_one/", name: "s3 def one", type: "screen", tags: ["droid","droidonly"]), + getTagDefinition(title: "s3_def_two", path: "s3_def_two/", name: "s3 def two", type: "event", tags: ["droid","droidonly"]), + ] + + // When + TagsGenerator.targets = [Tags.TargetType.matomo] + let extensionContent = TagsGenerator.getExtensionContent(sections: [sectionOne, sectionTwo, sectionThree], + tags: ["ios", "iosonly"], + staticVar: false, + extensionName: "GenTags") + // Expect Tags + let expect = """ + // Generated by ResgenSwift.Tags 1.2 + + import UIKit + import MatomoTracker + + // MARK: - Protocol + + protocol AnalyticsManagerProtocol { + func logScreen(name: String, path: String) + func logEvent(name: 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) { + 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) { + guard !tracker.isOptedOut else { return } + tracker.track( + eventWithCategory: "category", + action: "action", + name: name, + number: nil, + url: nil + ) + } + } + + // MARK: - Manager + + class AnalyticsManager { + static var shared = AnalyticsManager() + + // MARK: - Properties + + var managers: [AnalyticsManagerProtocol] = [] + + private var isEnabled: Bool = true + + // MARK: - Methods + + func setAnalyticsEnabled(_ enable: Bool) { isEnabled = enable } + + func configure(siteId: String, url: String) { + managers.append(MatomoAnalyticsManager(siteId: siteId, url: url)) + } + + private func logScreen(name: String, path: String) { + guard isEnabled else { return } + managers.forEach { manager in + manager.logScreen(name: name, path: path) + } + } + + private func logEvent(name: String) { + guard isEnabled else { return } + managers.forEach { manager in + manager.logEvent(name: name) + } + } + + // MARK: - section_one + + func logScreenS1DefOne() { + logScreen(name: "s1 def one", path: "s1_def_one/") + } + + func logEventS1DefTwo() { + logEvent(name: "s1 def two") + } + + // MARK: - section_two + + func logScreenS2DefOne() { + logScreen(name: "s2 def one", path: "s2_def_one/") + } + } + + """ + + if extensionContent != expect { + print(prettyFirstDifferenceBetweenStrings(s1: extensionContent, s2: expect)) + } + XCTAssertEqual(extensionContent.adaptForXCTest(), expect.adaptForXCTest()) + } + + func testGeneratedExtensionContentMatomoAndFirebase() { + // Given + let sectionOne = TagSection(name: "section_one") + sectionOne.definitions = [ + getTagDefinition(title: "s1_def_one", path: "s1_def_one/", name: "s1 def one", type: "screen", tags: ["ios", "iosonly"]), + getTagDefinition(title: "s1_def_two", path: "s1_def_two/", name: "s1 def two", type: "event", tags: ["ios", "iosonly"]), + ] + + let sectionTwo = TagSection(name: "section_two") + sectionTwo.definitions = [ + getTagDefinition(title: "s2_def_one", path: "s2_def_one/", name: "s2 def one", type: "screen", tags: ["ios","iosonly"]), + getTagDefinition(title: "s2_def_two", path: "s2_def_two/", name: "s2 def two", type: "event", tags: ["droid","droidonly"]), + ] + + let sectionThree = TagSection(name: "section_three") + sectionThree.definitions = [ + getTagDefinition(title: "s3_def_one", path: "s3_def_one/", name: "s3 def one", type: "screen", tags: ["droid","droidonly"]), + getTagDefinition(title: "s3_def_two", path: "s3_def_two/", name: "s3 def two", type: "event", tags: ["droid","droidonly"]), + ] + + // When + TagsGenerator.targets = [Tags.TargetType.matomo, Tags.TargetType.firebase] + let extensionContent = TagsGenerator.getExtensionContent(sections: [sectionOne, sectionTwo, sectionThree], + tags: ["ios", "iosonly"], + staticVar: false, + extensionName: "GenTags") + // Expect Tags + let expect = """ + // Generated by ResgenSwift.Tags 1.2 + + import UIKit + import MatomoTracker + import Firebase + + // MARK: - Protocol + + protocol AnalyticsManagerProtocol { + func logScreen(name: String, path: String) + func logEvent(name: 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) { + 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) { + 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) { + Analytics.logEvent(AnalyticsEventScreenView, parameters: [AnalyticsParameterScreenName: name]) + } + + func logEvent(name: String) { + var parameters = [ + AnalyticsParameterValue: name + ] + + Analytics.logEvent(AnalyticsEventSelectContent, parameters: parameters) + } + } + + // MARK: - Manager + + class AnalyticsManager { + static var shared = AnalyticsManager() + + // MARK: - Properties + + var managers: [AnalyticsManagerProtocol] = [] + + private var isEnabled: Bool = true + + // MARK: - Methods + + func setAnalyticsEnabled(_ enable: Bool) { isEnabled = enable } + + func configure(siteId: String, url: String) { + managers.append(MatomoAnalyticsManager(siteId: siteId, url: url)) + managers.append(FirebaseAnalyticsManager()) + } + + private func logScreen(name: String, path: String) { + guard isEnabled else { return } + managers.forEach { manager in + manager.logScreen(name: name, path: path) + } + } + + private func logEvent(name: String) { + guard isEnabled else { return } + managers.forEach { manager in + manager.logEvent(name: name) + } + } + + // MARK: - section_one + + func logScreenS1DefOne() { + logScreen(name: "s1 def one", path: "s1_def_one/") + } + + func logEventS1DefTwo() { + logEvent(name: "s1 def two") + } + + // MARK: - section_two + + func logScreenS2DefOne() { + logScreen(name: "s2 def one", path: "s2_def_one/") + } + } + + """ + + if extensionContent != expect { + print(prettyFirstDifferenceBetweenStrings(s1: extensionContent, s2: expect)) + } + XCTAssertEqual(extensionContent.adaptForXCTest(), expect.adaptForXCTest()) + } +} -- 2.39.5 From 09c153ba65d0db9668e34c23e39622ae07c0f0e4 Mon Sep 17 00:00:00 2001 From: Loris Perret Date: Wed, 6 Dec 2023 14:51:01 +0100 Subject: [PATCH 08/21] Actualiser Jenkinsfile --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index b2f2f51..85bdaad 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,6 +1,6 @@ library "openiumpipeline" -env.DEVELOPER_DIR= "/Applications/Xcode-14.3.0.app/Contents/Developer" +env.DEVELOPER_DIR="/Applications/Xcode-15.0.1.app/Contents/Developer" //env.SIMULATOR_DEVICE_TYPES="iPad--7th-generation-" env.IS_PACKAGE_SWIFT=1 env.TARGETS_MACOS=1 -- 2.39.5 From 3fc2fd9bacf9ad66c74a4171d876cd759657f0a0 Mon Sep 17 00:00:00 2001 From: Loris Perret Date: Fri, 8 Dec 2023 11:29:29 +0100 Subject: [PATCH 09/21] fix: Tags -> Anlytics --- .../Generated/Analytics+GenAllScript.swift | 190 ++++++++++++++++++ SampleFiles/Tags/sampleTags.yml | 24 +++ Sources/ResgenSwift/Analytics/Analytics.swift | 67 ++++++ .../AnalyticsOptions.swift} | 14 +- .../Generator/AnalyticsGenerator.swift} | 75 ++++--- .../Generator/FirebaseGenerator.swift | 32 ++- .../Generator/MatomoGenerator.swift | 18 +- .../Analytics/Model/AnalyticsCategory.swift | 28 +++ .../Analytics/Model/AnalyticsDefinition.swift | 174 ++++++++++++++++ .../Analytics/Model/AnalyticsFile.swift | 47 +++++ .../Analytics/Model/AnalyticsParameter.swift | 19 ++ .../Parser/AnalyticsFileParser.swift | 173 ++++++++++++++++ .../AnalyticsConfiguration+Runnable.swift | 43 ++++ .../Tags/Model/TagDefinition.swift | 138 ------------- .../ResgenSwift/Tags/Model/TagSection.swift | 39 ---- .../Tags/Parser/TagFileParser.swift | 93 --------- Sources/ResgenSwift/Tags/Tags.swift | 95 --------- .../AnalyticsDefinitionTests.swift} | 64 +----- .../AnalyticsGeneratorTests.swift} | 95 +++++---- .../AnalyticsSectionTests.swift} | 52 +---- .../{Tags => Analytics}/DiffString.swift | 0 21 files changed, 930 insertions(+), 550 deletions(-) create mode 100644 SampleFiles/Tags/Generated/Analytics+GenAllScript.swift create mode 100644 SampleFiles/Tags/sampleTags.yml create mode 100644 Sources/ResgenSwift/Analytics/Analytics.swift rename Sources/ResgenSwift/{Tags/TagsOptions.swift => Analytics/AnalyticsOptions.swift} (80%) rename Sources/ResgenSwift/{Tags/Generator/TagsGenerator.swift => Analytics/Generator/AnalyticsGenerator.swift} (69%) rename Sources/ResgenSwift/{Tags => Analytics}/Generator/FirebaseGenerator.swift (52%) rename Sources/ResgenSwift/{Tags => Analytics}/Generator/MatomoGenerator.swift (85%) create mode 100644 Sources/ResgenSwift/Analytics/Model/AnalyticsCategory.swift create mode 100644 Sources/ResgenSwift/Analytics/Model/AnalyticsDefinition.swift create mode 100644 Sources/ResgenSwift/Analytics/Model/AnalyticsFile.swift create mode 100644 Sources/ResgenSwift/Analytics/Model/AnalyticsParameter.swift create mode 100644 Sources/ResgenSwift/Analytics/Parser/AnalyticsFileParser.swift create mode 100644 Sources/ResgenSwift/Generate/Runnable/AnalyticsConfiguration+Runnable.swift delete mode 100644 Sources/ResgenSwift/Tags/Model/TagDefinition.swift delete mode 100644 Sources/ResgenSwift/Tags/Model/TagSection.swift delete mode 100644 Sources/ResgenSwift/Tags/Parser/TagFileParser.swift delete mode 100644 Sources/ResgenSwift/Tags/Tags.swift rename Tests/ResgenSwiftTests/{Tags/TagDefinitionTests.swift => Analytics/AnalyticsDefinitionTests.swift} (61%) rename Tests/ResgenSwiftTests/{Tags/TagsGeneratorTests.swift => Analytics/AnalyticsGeneratorTests.swift} (72%) rename Tests/ResgenSwiftTests/{Tags/TagSectionTests.swift => Analytics/AnalyticsSectionTests.swift} (52%) rename Tests/ResgenSwiftTests/{Tags => Analytics}/DiffString.swift (100%) diff --git a/SampleFiles/Tags/Generated/Analytics+GenAllScript.swift b/SampleFiles/Tags/Generated/Analytics+GenAllScript.swift new file mode 100644 index 0000000..bf18797 --- /dev/null +++ b/SampleFiles/Tags/Generated/Analytics+GenAllScript.swift @@ -0,0 +1,190 @@ +// Generated by ResgenSwift.Analytics 1.2 + +import MatomoTracker +import Firebase + +// MARK: - Protocol + +protocol AnalyticsManagerProtocol { + func logScreen(name: String, path: String) + func logEvent( + name: String, + action: String, + category: String, + params: [String: Any]? + ) +} + +// 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) { + 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 + ] + + Analytics.logEvent( + AnalyticsEventScreenView, + parameters: parameters + ) + } + + func logEvent( + name: String, + action: String, + category: String, + params: [String: Any]? + ) { + var parameters: [String:Any] = [ + "action": action, + "category": category, + ] + + if let supplementaryParameters = params { + parameters.merge(supplementaryParameters) { (origin, new) -> Any in + return origin + } + } + + Analytics.logEvent( + name, + parameters: parameters + ) + } +} + +// MARK: - Manager + +class AnalyticsManager { + static var shared = AnalyticsManager() + + // MARK: - Properties + + var managers: [AnalyticsManagerProtocol] = [] + + private var isEnabled: Bool = true + + // MARK: - Methods + + func setAnalyticsEnabled(_ enable: Bool) { + isEnabled = enable + } + + func configure(siteId: String, url: String) { + managers.append( + MatomoAnalyticsManager( + siteId: siteId, + url: url + ) + ) + managers.append(FirebaseAnalyticsManager()) + } + + private func logScreen(name: String, path: String) { + guard isEnabled else { return } + + managers.forEach { manager in + manager.logScreen(name: name, path: path) + } + } + + private func logEvent( + name: String, + action: String, + category: String, + params: [String: Any]? + ) { + guard isEnabled else { return } + + managers.forEach { manager in + manager.logEvent( + name: name, + action: action, + category: category, + params: params + ) + } + } + + // MARK: - Introduction + + func logScreenIntroductionScreen(title: String) { + logScreen( + name: "Bienvenue \(title)", + path: "introduction/" + ) + } + + func logEventIntroductionScreen(test: String, data: Int) { + logEvent( + name: "Bienvenue", + action: "action", + category: "category", + params: [ + "test": test, + "data": data + ] + ) + } +} diff --git a/SampleFiles/Tags/sampleTags.yml b/SampleFiles/Tags/sampleTags.yml new file mode 100644 index 0000000..4933909 --- /dev/null +++ b/SampleFiles/Tags/sampleTags.yml @@ -0,0 +1,24 @@ +--- +categories: + - id: Introduction + screens: + - id: introduction_screen + name: Bienvenue _TITLE_ + path: introduction/ + tags: droid,ios + parameters: + - name: title + type: String + replaceIn: name + + events: + - id: introduction_screen + name: Bienvenue + category: category + action: action + tags: droid,ios + parameters: + - name: test + type: String + - name: data + type: Int diff --git a/Sources/ResgenSwift/Analytics/Analytics.swift b/Sources/ResgenSwift/Analytics/Analytics.swift new file mode 100644 index 0000000..dd9a61c --- /dev/null +++ b/Sources/ResgenSwift/Analytics/Analytics.swift @@ -0,0 +1,67 @@ +// +// Analytics.swift +// +// +// Created by Loris Perret on 08/12/2023. +// + +import ToolCore +import Foundation +import ArgumentParser + +struct Analytics: ParsableCommand { + + // MARK: - Command Configuration + + static var configuration = CommandConfiguration( + abstract: "Generate analytics extension file.", + version: ResgenSwiftVersion + ) + + + // MARK: - Static + + static let toolName = "Analytics" + static let defaultExtensionName = "Analytics" + + // MARK: - Command Options + + @OptionGroup var options: AnalyticsOptions + + // MARK: - Run + + mutating func run() { + print("[\(Self.toolName)] Starting analytics generation") + print("[\(Self.toolName)] Will use inputFile \(options.inputFile) to generate analytics for target: \(options.target)") + print("[\(Self.toolName)] Will generate analytics") + + // Parse input file + 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) + + print("[\(Self.toolName)] Analytics generated") + } +} + +extension Analytics { + enum TargetType: CaseIterable { + case matomo + case firebase + + var value: String { + switch self { + case .matomo: + "matomo" + case .firebase: + "firebase" + } + } + } +} diff --git a/Sources/ResgenSwift/Tags/TagsOptions.swift b/Sources/ResgenSwift/Analytics/AnalyticsOptions.swift similarity index 80% rename from Sources/ResgenSwift/Tags/TagsOptions.swift rename to Sources/ResgenSwift/Analytics/AnalyticsOptions.swift index d4e4fac..0f3172e 100644 --- a/Sources/ResgenSwift/Tags/TagsOptions.swift +++ b/Sources/ResgenSwift/Analytics/AnalyticsOptions.swift @@ -1,14 +1,14 @@ // -// TagOptions.swift +// AnalyticsOptions.swift // // -// Created by Thibaut Schmitt on 10/01/2022. +// Created by Loris Perret on 08/12/2023. // import Foundation import ArgumentParser -struct TagsOptions: ParsableArguments { +struct AnalyticsOptions: ParsableArguments { @Flag(name: [.customShort("f"), .customShort("F")], help: "Should force generation") var forceGeneration = false @@ -24,16 +24,16 @@ struct TagsOptions: ParsableArguments { @Option(help: "Tell if it will generate static properties or not") var staticMembers: Bool = false - @Option(help: "Extension name. If not specified, it will generate a Tag extension.") - var extensionName: String = Tags.defaultExtensionName + @Option(help: "Extension name. If not specified, it will generate a Analytics extension.") + var extensionName: String = Analytics.defaultExtensionName - @Option(help: "Extension suffix. Ex: MyApp, it will generate {extensionName}+Tag{extensionSuffix}.swift") + @Option(help: "Extension suffix. Ex: MyApp, it will generate {extensionName}+Analytics{extensionSuffix}.swift") var extensionSuffix: String? } // MARK: - Computed var -extension TagsOptions { +extension AnalyticsOptions { var extensionFileName: String { if let extensionSuffix = extensionSuffix { return "\(extensionName)+\(extensionSuffix).swift" diff --git a/Sources/ResgenSwift/Tags/Generator/TagsGenerator.swift b/Sources/ResgenSwift/Analytics/Generator/AnalyticsGenerator.swift similarity index 69% rename from Sources/ResgenSwift/Tags/Generator/TagsGenerator.swift rename to Sources/ResgenSwift/Analytics/Generator/AnalyticsGenerator.swift index e803a69..f16f2ef 100644 --- a/Sources/ResgenSwift/Tags/Generator/TagsGenerator.swift +++ b/Sources/ResgenSwift/Analytics/Generator/AnalyticsGenerator.swift @@ -1,22 +1,22 @@ // -// TagsGenerator.swift -// +// AnalyticsGenerator.swift // -// Created by Thibaut Schmitt on 10/01/2022. +// +// Created by Loris Perret on 08/12/2023. // import Foundation import ToolCore import CoreVideo -class TagsGenerator { - static var targets: [Tags.TargetType] = [] +class AnalyticsGenerator { + static var targets: [Analytics.TargetType] = [] - static func writeExtensionFiles(sections: [TagSection], target: String, tags: [String], staticVar: Bool, extensionName: String, extensionFilePath: String) { + 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: " ") - Tags.TargetType.allCases.forEach { enumTarget in + Analytics.TargetType.allCases.forEach { enumTarget in if targetsString.contains(enumTarget.value) { targets.append(enumTarget) } @@ -43,7 +43,7 @@ class TagsGenerator { // MARK: - Extension content - static func getExtensionContent(sections: [TagSection], tags: [String], staticVar: Bool, extensionName: String) -> String { + static func getExtensionContent(sections: [AnalyticsCategory], tags: [String], staticVar: Bool, extensionName: String) -> String { [ Self.getHeader(extensionClassname: extensionName, staticVar: staticVar), Self.getProperties(sections: sections, tags: tags, staticVar: staticVar), @@ -56,9 +56,8 @@ class TagsGenerator { private static func getHeader(extensionClassname: String, staticVar: Bool) -> String { """ - // Generated by ResgenSwift.\(Tags.toolName) \(ResgenSwiftVersion) + // Generated by ResgenSwift.\(Analytics.toolName) \(ResgenSwiftVersion) - \(staticVar ? "typelias Tags = String\n\n" : "")import UIKit \(Self.getImport()) \(Self.getAnalytics()) @@ -85,17 +84,19 @@ class TagsGenerator { // MARK: - Methods - func setAnalyticsEnabled(_ enable: Bool) { isEnabled = enable } + func setAnalyticsEnabled(_ enable: Bool) { + isEnabled = enable + } """ } private static func getImport() -> String { var result: [String] = [] - if targets.contains(Tags.TargetType.matomo) { + if targets.contains(Analytics.TargetType.matomo) { result.append("import MatomoTracker") } - if targets.contains(Tags.TargetType.firebase) { + if targets.contains(Analytics.TargetType.firebase) { result.append("import Firebase") } @@ -106,15 +107,27 @@ class TagsGenerator { """ private func logScreen(name: String, path: String) { guard isEnabled else { return } + managers.forEach { manager in manager.logScreen(name: name, path: path) } } - private func logEvent(name: String) { + private func logEvent( + name: String, + action: String, + category: String, + params: [String: Any]? + ) { guard isEnabled else { return } + managers.forEach { manager in - manager.logEvent(name: name) + manager.logEvent( + name: name, + action: action, + category: category, + params: params + ) } } """ @@ -125,16 +138,23 @@ class TagsGenerator { var content: [String] = [] let footer = " }" - if targets.contains(Tags.TargetType.matomo) { + if targets.contains(Analytics.TargetType.matomo) { header = "func configure(siteId: String, url: String) {" - } else if targets.contains(Tags.TargetType.firebase) { + } else if targets.contains(Analytics.TargetType.firebase) { header = "func configure() {" } - if targets.contains(Tags.TargetType.matomo) { - content.append(" managers.append(MatomoAnalyticsManager(siteId: siteId, url: url))") + if targets.contains(Analytics.TargetType.matomo) { + content.append(""" + managers.append( + MatomoAnalyticsManager( + siteId: siteId, + url: url + ) + ) + """) } - if targets.contains(Tags.TargetType.firebase) { + if targets.contains(Analytics.TargetType.firebase) { content.append(" managers.append(FirebaseAnalyticsManager())") } @@ -152,25 +172,30 @@ class TagsGenerator { protocol AnalyticsManagerProtocol { func logScreen(name: String, path: String) - func logEvent(name: String) + func logEvent( + name: String, + action: String, + category: String, + params: [String: Any]? + ) } """ var result: [String] = [proto] - if targets.contains(Tags.TargetType.matomo) { + if targets.contains(Analytics.TargetType.matomo) { result.append(MatomoGenerator.service.content) } - if targets.contains(Tags.TargetType.firebase) { + if targets.contains(Analytics.TargetType.firebase) { result.append(FirebaseGenerator.service.content) } return result.joined(separator: "\n") } - private static func getProperties(sections: [TagSection], 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 @@ -178,7 +203,7 @@ class TagsGenerator { return nil// Go to next section } - var res = "\n // MARK: - \(section.name)" + var res = "\n // MARK: - \(section.id)" section.definitions.forEach { definition in guard definition.hasOneOrMoreMatchingTags(inputTags: tags) == true else { return // Go to next definition diff --git a/Sources/ResgenSwift/Tags/Generator/FirebaseGenerator.swift b/Sources/ResgenSwift/Analytics/Generator/FirebaseGenerator.swift similarity index 52% rename from Sources/ResgenSwift/Tags/Generator/FirebaseGenerator.swift rename to Sources/ResgenSwift/Analytics/Generator/FirebaseGenerator.swift index 5f2b690..4352b0e 100644 --- a/Sources/ResgenSwift/Tags/Generator/FirebaseGenerator.swift +++ b/Sources/ResgenSwift/Analytics/Generator/FirebaseGenerator.swift @@ -31,7 +31,14 @@ enum FirebaseGenerator { private var logScreen: String { """ func logScreen(name: String, path: String) { - Analytics.logEvent(AnalyticsEventScreenView, parameters: [AnalyticsParameterScreenName: name]) + var parameters = [ + AnalyticsParameterScreenName: name + ] + + Analytics.logEvent( + AnalyticsEventScreenView, + parameters: parameters + ) } """ @@ -39,12 +46,27 @@ enum FirebaseGenerator { private var logEvent: String { """ - func logEvent(name: String) { - var parameters = [ - AnalyticsParameterValue: name + func logEvent( + name: String, + action: String, + category: String, + params: [String: Any]? + ) { + var parameters: [String:Any] = [ + "action": action, + "category": category, ] + + if let supplementaryParameters = params { + parameters.merge(supplementaryParameters) { (origin, new) -> Any in + return origin + } + } - Analytics.logEvent(AnalyticsEventSelectContent, parameters: parameters) + Analytics.logEvent( + name, + parameters: parameters + ) } """ } diff --git a/Sources/ResgenSwift/Tags/Generator/MatomoGenerator.swift b/Sources/ResgenSwift/Analytics/Generator/MatomoGenerator.swift similarity index 85% rename from Sources/ResgenSwift/Tags/Generator/MatomoGenerator.swift rename to Sources/ResgenSwift/Analytics/Generator/MatomoGenerator.swift index 2d7b56a..739e8cb 100644 --- a/Sources/ResgenSwift/Tags/Generator/MatomoGenerator.swift +++ b/Sources/ResgenSwift/Analytics/Generator/MatomoGenerator.swift @@ -41,7 +41,10 @@ enum MatomoGenerator { 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)!) + tracker = MatomoTracker( + siteId: siteId, + baseURL: URL(string: url)! + ) #if DEBUG tracker.dispatchInterval = 5 @@ -65,6 +68,7 @@ enum MatomoGenerator { 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], @@ -77,11 +81,17 @@ enum MatomoGenerator { private var logEvent: String { """ - func logEvent(name: String) { + func logEvent( + name: String, + action: String, + category: String, + params: [String: Any]? + ) { guard !tracker.isOptedOut else { return } + tracker.track( - eventWithCategory: "category", - action: "action", + eventWithCategory: category, + action: action, name: name, number: nil, url: nil diff --git a/Sources/ResgenSwift/Analytics/Model/AnalyticsCategory.swift b/Sources/ResgenSwift/Analytics/Model/AnalyticsCategory.swift new file mode 100644 index 0000000..9cec892 --- /dev/null +++ b/Sources/ResgenSwift/Analytics/Model/AnalyticsCategory.swift @@ -0,0 +1,28 @@ +// +// AnalyticsCategory.swift +// +// +// Created by Loris Perret on 05/12/2023. +// + +import Foundation + +class AnalyticsCategory { + let id: String // OnBoarding + var definitions = [AnalyticsDefinition]() + + init(id: String) { + self.id = id + } + + func hasOneOrMoreMatchingTags(tags: [String]) -> Bool { + let allTags = definitions.flatMap { $0.tags } + let allTagsSet = Set(allTags) + + let intersection = Set(tags).intersection(allTagsSet) + if intersection.isEmpty { + return false + } + return true + } +} diff --git a/Sources/ResgenSwift/Analytics/Model/AnalyticsDefinition.swift b/Sources/ResgenSwift/Analytics/Model/AnalyticsDefinition.swift new file mode 100644 index 0000000..e0bdbe1 --- /dev/null +++ b/Sources/ResgenSwift/Analytics/Model/AnalyticsDefinition.swift @@ -0,0 +1,174 @@ +// +// AnalyticsDefinition.swift +// +// +// Created by Loris Perret on 05/12/2023. +// + +import Foundation + +class AnalyticsDefinition { + let id: String + var name: String + var path: String = "" + var category: String = "" + var action: String = "" + var comments: String = "" + var tags: [String] = [] + var parameters: [AnalyticsParameter] = [] + var type: TagType + + init(id: String, name: String, type: TagType) { + self.id = id + self.name = name + self.type = type + } + + func hasOneOrMoreMatchingTags(inputTags: [String]) -> Bool { + if Set(inputTags).intersection(Set(self.tags)).isEmpty { + return false + } + return true + } + + // MARK: - Methods + + private func getFuncName() -> String { + var pascalCaseTitle: String = "" + id.components(separatedBy: "_").forEach { word in + pascalCaseTitle.append(contentsOf: word.uppercasedFirst()) + } + + return "log\(type == .screen ? "Screen" : "Event")\(pascalCaseTitle)" + } + + private func getParameters() -> String { + var params = parameters + var result: String + + if type == .screen { + params = params.filter{ param in + !param.replaceIn.isEmpty + } + } + + let paramsString = params.map { parameter in + "\(parameter.name): \(parameter.type)" + } + + if paramsString.count > 2 { + result = """ + ( + \(paramsString.joined(separator: ",\n\t\t")) + ) + """ + } else { + result = """ + (\(paramsString.joined(separator: ", "))) + """ + } + + return result + } + + private func replaceIn(){ + for parameter in parameters { + 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: break + } + } + } + } + + private func getlogFunction() -> String { + var params: [String] = [] + var result: String + + let supplementaryParams = parameters.filter { param in + param.replaceIn.isEmpty + } + + supplementaryParams.forEach { param in + params.append("\"\(param.name)\": \(param.name)") + } + + if params.count > 1 { + result = """ + [ + \(params.joined(separator: ",\n\t\t\t\t")) + ] + """ + } else { + result = """ + [\(params.joined(separator: ", "))] + """ + } + + if type == .screen { + return """ + logScreen( + name: "\(name)", + path: "\(path)" + ) + """ + } else { + return """ + logEvent( + name: "\(name)", + action: "\(action)", + category: "\(category)", + params: \(result) + ) + """ + } + } + + // MARK: - Raw strings + + func getProperty() -> String { + replaceIn() + return """ + func \(getFuncName())\(getParameters()) { + \(getlogFunction()) + } + """ + } + + func getStaticProperty() -> String { + replaceIn() + return """ + static func \(getFuncName())\(getParameters()) { + \(getlogFunction()) + } + """ + } +} + +extension AnalyticsDefinition { + enum TagType { + case screen + case event + } +} + +extension String { + func replacingFirstOccurrence(of: String, with: String) -> Self { + if let range = self.range(of: of) { + let tmp = self.replacingOccurrences( + of: of, + with: with, + options: .literal, + range: range + ) + + return tmp + } + + return self + } +} diff --git a/Sources/ResgenSwift/Analytics/Model/AnalyticsFile.swift b/Sources/ResgenSwift/Analytics/Model/AnalyticsFile.swift new file mode 100644 index 0000000..5c4f690 --- /dev/null +++ b/Sources/ResgenSwift/Analytics/Model/AnalyticsFile.swift @@ -0,0 +1,47 @@ +// +// AnalyticsFile.swift +// +// +// Created by Loris Perret on 06/12/2023. +// + +import Foundation + +struct AnalyticsFile: Codable { + var categories: [AnalyticsCategoryDTO] +} + +struct AnalyticsCategoryDTO: Codable { + var id: String + var screens: [AnalyticsDefinitionScreenDTO]? + var events: [AnalyticsDefinitionEventDTO]? +} + +protocol AnalyticsDefinitionDTO: Codable {} + +struct AnalyticsDefinitionScreenDTO: AnalyticsDefinitionDTO { + var id: String + var name: String + var tags: String + var comments: String? + var parameters: [AnalyticsParameterDTO]? + + var path: String? +} + +struct AnalyticsDefinitionEventDTO: AnalyticsDefinitionDTO { + var id: String + var name: String + var tags: String + var comments: String? + var parameters: [AnalyticsParameterDTO]? + + var category: String? + var action: String? +} + +struct AnalyticsParameterDTO: Codable { + var name: String + var type: String + var replaceIn: String? +} diff --git a/Sources/ResgenSwift/Analytics/Model/AnalyticsParameter.swift b/Sources/ResgenSwift/Analytics/Model/AnalyticsParameter.swift new file mode 100644 index 0000000..e7f1079 --- /dev/null +++ b/Sources/ResgenSwift/Analytics/Model/AnalyticsParameter.swift @@ -0,0 +1,19 @@ +// +// AnalyticsParameter.swift +// +// +// Created by Loris Perret on 06/12/2023. +// + +import Foundation + +class AnalyticsParameter { + var name: String + var type: String + var replaceIn: [String] = [] + + init(name: String, type: String) { + self.name = name + self.type = type + } +} diff --git a/Sources/ResgenSwift/Analytics/Parser/AnalyticsFileParser.swift b/Sources/ResgenSwift/Analytics/Parser/AnalyticsFileParser.swift new file mode 100644 index 0000000..0992aed --- /dev/null +++ b/Sources/ResgenSwift/Analytics/Parser/AnalyticsFileParser.swift @@ -0,0 +1,173 @@ +// +// AnalyticsFileParser.swift +// +// +// Created by Loris Perret on 06/12/2023. +// + +import Foundation +import Yams + +class AnalyticsFileParser { + private static var inputFile: String = "" + private static var target: String = "" + + private static func parseYaml() -> AnalyticsFile { + guard let data = FileManager().contents(atPath: inputFile) else { + let error = GenerateError.fileNotExists(inputFile) + Generate.exit(withError: error) + } + + do { + let tagFile = try YAMLDecoder().decode(AnalyticsFile.self, from: data) + return tagFile + } catch let error { + Generate.exit(withError: error) + } + } + + private static func getParameters(fromData data: [AnalyticsParameterDTO]) -> [AnalyticsParameter] { + var parameters: [AnalyticsParameter] = [] + + data.forEach { value in + // Type + + let type = value.type.uppercasedFirst() + + guard + type == "String" || + type == "Int" || + type == "Double" || + type == "Bool" + else { + let error = GenerateError.invalidParameter("type of \(value.name)") + Generate.exit(withError: error) + } + + let parameter: AnalyticsParameter = AnalyticsParameter(name: value.name, type: type) + + if let replaceIn = value.replaceIn { + parameter.replaceIn = replaceIn.components(separatedBy: ",") + } + + parameters.append(parameter) + } + + return parameters + } + + private static func getTagDefinition( + id: String, + name: String, + type: AnalyticsDefinition.TagType, + tags: String, + comments: String?, + parameters: [AnalyticsParameterDTO]? + ) -> AnalyticsDefinition { + let definition: AnalyticsDefinition = AnalyticsDefinition(id: id, name: name, type: type) + definition.tags = tags.components(separatedBy: ",") + + if let comments = comments { + definition.comments = comments + } + + if let parameters = parameters { + definition.parameters = Self.getParameters(fromData: parameters) + } + + return definition + } + + private static func getTagDefinitionScreen(fromData screens: [AnalyticsDefinitionScreenDTO]) -> [AnalyticsDefinition] { + var definitions: [AnalyticsDefinition] = [] + + for screen in screens { + let definition: AnalyticsDefinition = Self.getTagDefinition( + id: screen.id, + name: screen.name, + type: .screen, + tags: screen.tags, + comments: screen.comments, + parameters: screen.parameters + ) + + guard target.contains(Analytics.TargetType.matomo.value) else { continue } + + // Path + + guard let path = screen.path else { + let error = GenerateError.missingElement("screen path") + Generate.exit(withError: error) + } + + definition.path = path + + definitions.append(definition) + } + + return definitions + } + + private static func getTagDefinitionEvent(fromData events: [AnalyticsDefinitionEventDTO]) -> [AnalyticsDefinition] { + var definitions: [AnalyticsDefinition] = [] + + for event in events { + let definition: AnalyticsDefinition = Self.getTagDefinition( + id: event.id, + name: event.name, + type: .event, + tags: event.tags, + comments: event.comments, + parameters: event.parameters + ) + + guard target.contains(Analytics.TargetType.matomo.value) else { continue } + + // Category + + guard let category = event.category else { + let error = GenerateError.missingElement("event category") + Generate.exit(withError: error) + } + + definition.category = category + + // Action + + guard let action = event.action else { + let error = GenerateError.missingElement("event action") + Generate.exit(withError: error) + } + + definition.action = action + + definitions.append(definition) + } + + return definitions + } + + static func parse(_ inputFile: String, target: String) -> [AnalyticsCategory] { + self.inputFile = inputFile + self.target = target + + let tagFile: AnalyticsFile = Self.parseYaml() + var sections: [AnalyticsCategory] = [] + + tagFile.categories.forEach { categorie in + let section: AnalyticsCategory = AnalyticsCategory(id: categorie.id) + + if let screens = categorie.screens { + section.definitions.append(contentsOf: Self.getTagDefinitionScreen(fromData: screens)) + } + + if let events = categorie.events { + section.definitions.append(contentsOf: Self.getTagDefinitionEvent(fromData: events)) + } + + sections.append(section) + } + + return sections + } +} diff --git a/Sources/ResgenSwift/Generate/Runnable/AnalyticsConfiguration+Runnable.swift b/Sources/ResgenSwift/Generate/Runnable/AnalyticsConfiguration+Runnable.swift new file mode 100644 index 0000000..1689a24 --- /dev/null +++ b/Sources/ResgenSwift/Generate/Runnable/AnalyticsConfiguration+Runnable.swift @@ -0,0 +1,43 @@ +// +// AnalyticsConfiguration+Runnable.swift +// +// +// Created by Loris Perret on 08/12/2023. +// + +import Foundation + +extension AnalyticsConfiguration: Runnable { + func run(projectDirectory: String, force: Bool) { + var args = [String]() + + if force { + args += ["-f"] + } + + args += [ + inputFile.prependIfRelativePath(projectDirectory), + "--target", + target, + "--extension-output-path", + extensionOutputPath.prependIfRelativePath(projectDirectory), + "--static-members", + "\(staticMembersOptions)" + ] + + if let extensionName = extensionName { + args += [ + "--extension-name", + extensionName + ] + } + if let extensionSuffix = extensionSuffix { + args += [ + "--extension-suffix", + extensionSuffix + ] + } + + Analytics.main(args) + } +} diff --git a/Sources/ResgenSwift/Tags/Model/TagDefinition.swift b/Sources/ResgenSwift/Tags/Model/TagDefinition.swift deleted file mode 100644 index 806ec8b..0000000 --- a/Sources/ResgenSwift/Tags/Model/TagDefinition.swift +++ /dev/null @@ -1,138 +0,0 @@ -// -// TagDefinition.swift -// -// -// Created by Loris Perret on 05/12/2023. -// - -import Foundation - -class TagDefinition { - let title: String - var path: String = "" - var name: String = "" - var type: String = "" - var tags = [String]() - var comment: String? - - var isValid: Bool { - title.isEmpty == false && - name.isEmpty == false && - (type == TagType.screen.value || type == TagType.event.value) - } - - init(title: String) { - self.title = title - } - - static func match(_ line: String) -> TagDefinition? { - guard line.range(of: "\\[(.*?)]$", options: .regularExpression, range: nil, locale: nil) != nil else { - return nil - } - - let definitionTitle = line - .replacingOccurrences(of: ["[", "]"], with: "") - .removeLeadingTrailingWhitespace() - - return TagDefinition(title: definitionTitle) - } - - func hasOneOrMoreMatchingTags(inputTags: [String]) -> Bool { - if Set(inputTags).intersection(Set(self.tags)).isEmpty { - return false - } - return true - } - - // MARK: - - - private func getStringParameters(input: String) -> (inputParameters: [String], translationArguments: [String])? { - var methodsParameters = [String]() - - let printfPlaceholderRegex = try! NSRegularExpression(pattern: "%(?:\\d+\\$)?[+-]?(?:[ 0]|'.{1})?-?\\d*(?:\\.\\d+)?[blcdeEufFgGosxX@]*") - printfPlaceholderRegex.enumerateMatches(in: input, options: [], range: NSRange(location: 0, length: input.count)) { match, _, stop in - guard let match = match else { return } - - if let range = Range(match.range, in: input), let last = input[range].last { - switch last { - case "d", "u": - methodsParameters.append("Int") - case "f", "F": - methodsParameters.append("Double") - case "@", "s", "c": - methodsParameters.append("String") - case "%": - // if you need to print %, you have to add %% - break - default: - break - } - } - } - - if methodsParameters.isEmpty { - return nil - } - - var inputParameters = [String]() - var translationArguments = [String]() - for (index, paramType) in methodsParameters.enumerated() { - let paramName = "arg\(index)" - translationArguments.append(paramName) - inputParameters.append("\(paramName): \(paramType)") - } - - return (inputParameters: inputParameters, translationArguments: translationArguments) - } - - private func getFuncName() -> String { - var pascalCaseTitle: String = "" - name.components(separatedBy: " ").forEach { word in - pascalCaseTitle.append(contentsOf: word.uppercasedFirst()) - } - - return "log\(type == TagType.screen.value ? "Screen" : "Event")\(pascalCaseTitle)" - } - - private func getlogFunction() -> String { - if type == TagType.screen.value { - "logScreen(name: \"\(name)\", path: \"\(path)\")" - } else { - "logEvent(name: \"\(name)\")" - } - } - - // MARK: - Raw strings - - func getProperty() -> String { - """ - func \(getFuncName())() { - \(getlogFunction()) - } - """ - } - - func getStaticProperty() -> String { - """ - static func \(getFuncName())() { - \(getlogFunction()) - } - """ - } -} - -extension TagDefinition { - enum TagType { - case screen - case event - - var value: String { - switch self { - case .screen: - "screen" - case .event: - "event" - } - } - } -} diff --git a/Sources/ResgenSwift/Tags/Model/TagSection.swift b/Sources/ResgenSwift/Tags/Model/TagSection.swift deleted file mode 100644 index afabacd..0000000 --- a/Sources/ResgenSwift/Tags/Model/TagSection.swift +++ /dev/null @@ -1,39 +0,0 @@ -// -// TagSection.swift -// -// -// Created by Loris Perret on 05/12/2023. -// - -import Foundation - -class TagSection { - let name: String // OnBoarding - var definitions = [TagDefinition]() - - init(name: String) { - self.name = name - } - - static func match(_ line: String) -> TagSection? { - guard line.range(of: "\\[\\[(.*?)]]$", options: .regularExpression, range: nil, locale: nil) != nil else { - return nil - } - - let sectionName = line - .replacingOccurrences(of: ["[", "]"], with: "") - .removeLeadingTrailingWhitespace() - return TagSection(name: sectionName) - } - - func hasOneOrMoreMatchingTags(tags: [String]) -> Bool { - let allTags = definitions.flatMap { $0.tags } - let allTagsSet = Set(allTags) - - let intersection = Set(tags).intersection(allTagsSet) - if intersection.isEmpty { - return false - } - return true - } -} diff --git a/Sources/ResgenSwift/Tags/Parser/TagFileParser.swift b/Sources/ResgenSwift/Tags/Parser/TagFileParser.swift deleted file mode 100644 index c8e93e2..0000000 --- a/Sources/ResgenSwift/Tags/Parser/TagFileParser.swift +++ /dev/null @@ -1,93 +0,0 @@ -// -// TagFileParser.swift -// -// -// Created by Loris Perret on 05/12/2023. -// - -import Foundation - -class TagFileParser { - static func parse(_ inputFile: String) -> [TagSection] { - let inputFileContent = try! String(contentsOfFile: inputFile, encoding: .utf8) - let stringsByLines = inputFileContent.components(separatedBy: .newlines) - - var sections = [TagSection]() - - // Parse file - stringsByLines.forEach { - // TagSection - if let section = TagSection.match($0) { - sections.append(section) - return - } - - // Definition - if let definition = TagDefinition.match($0) { - sections.last?.definitions.append(definition) - return - } - - // Definition content - if $0.isEmpty == false { - // name = Test => ["name ", " Test"] - let splitLine = $0 - .removeLeadingTrailingWhitespace() - .split(separator: "=") - - guard let lastDefinition = sections.last?.definitions.last, - let leftElement = splitLine.first else { - return - } - - let rightElement: String = splitLine.dropFirst().joined(separator: "=") - - // "name " => "name" - let leftHand = String(leftElement).removeTrailingWhitespace() - // " Test" => "Test" - let rightHand = String(rightElement).removeLeadingWhitespace() - - // Handle comments, tags and translation - switch leftHand { - case "comments": - lastDefinition.comment = rightHand - - case "tags": - lastDefinition.tags = rightHand - .split(separator: ",") - .map { String($0) } - - case "path": - lastDefinition.path = rightHand - - case "name": - lastDefinition.name = rightHand - - case "type": - lastDefinition.type = rightHand - - default: - break - } - } - } - - // Keep only valid definition - var invalidDefinitionNames = [String]() - sections.forEach { section in - section.definitions = section.definitions - .filter { - if $0.isValid == false { - invalidDefinitionNames.append($0.name) - return false - } - return true - } - } - if invalidDefinitionNames.count > 0 { - print("warning: [\(Stringium.toolName)] Found \(invalidDefinitionNames.count) definition (\(invalidDefinitionNames.joined(separator: ", "))") - } - - return sections - } -} diff --git a/Sources/ResgenSwift/Tags/Tags.swift b/Sources/ResgenSwift/Tags/Tags.swift deleted file mode 100644 index 971392f..0000000 --- a/Sources/ResgenSwift/Tags/Tags.swift +++ /dev/null @@ -1,95 +0,0 @@ -// -// Tag.swift -// -// -// Created by Thibaut Schmitt on 10/01/2022. -// - -import ToolCore -import Foundation -import ArgumentParser - -struct Tags: ParsableCommand { - - // MARK: - Command Configuration - - static var configuration = CommandConfiguration( - abstract: "Generate tags extension file.", - version: ResgenSwiftVersion - ) - - - // MARK: - Static - - static let toolName = "Tags" - static let defaultExtensionName = "Tags" - static let noTranslationTag: String = "notranslation" - - // MARK: - Command Options - - @OptionGroup var options: TagsOptions - - // MARK: - Run - - mutating func run() { - print("[\(Self.toolName)] Starting tags generation") - print("[\(Self.toolName)] Will use inputFile \(options.inputFile) to generate tags for target: \(options.target)") - - // Check requirements - guard checkRequirements() else { return } - - print("[\(Self.toolName)] Will generate tags") - - // Parse input file - let sections = TagFileParser.parse(options.inputFile) - - // Generate extension - TagsGenerator.writeExtensionFiles(sections: sections, - target: options.target, - tags: ["ios", "iosonly", Self.noTranslationTag], - staticVar: options.staticMembers, - extensionName: options.extensionName, - extensionFilePath: options.extensionFilePath) - - print("[\(Self.toolName)] Tags generated") - } - - // MARK: - Requirements - - private func checkRequirements() -> Bool { - let fileManager = FileManager() - - // Input file - guard fileManager.fileExists(atPath: options.inputFile) else { - let error = StringiumError.fileNotExists(options.inputFile) - print(error.description) - Stringium.exit(withError: error) - } - - // Check if needed to regenerate - guard GeneratorChecker.shouldGenerate(force: options.forceGeneration, - inputFilePath: options.inputFile, - extensionFilePath: options.extensionFilePath) else { - print("[\(Self.toolName)] Tags are already up to date :) ") - return false - } - - return true - } -} - -extension Tags { - enum TargetType: CaseIterable { - case matomo - case firebase - - var value: String { - switch self { - case .matomo: - "matomo" - case .firebase: - "firebase" - } - } - } -} diff --git a/Tests/ResgenSwiftTests/Tags/TagDefinitionTests.swift b/Tests/ResgenSwiftTests/Analytics/AnalyticsDefinitionTests.swift similarity index 61% rename from Tests/ResgenSwiftTests/Tags/TagDefinitionTests.swift rename to Tests/ResgenSwiftTests/Analytics/AnalyticsDefinitionTests.swift index 9a9d116..bbf451d 100644 --- a/Tests/ResgenSwiftTests/Tags/TagDefinitionTests.swift +++ b/Tests/ResgenSwiftTests/Analytics/AnalyticsDefinitionTests.swift @@ -1,5 +1,5 @@ // -// TagDefinitionTests.swift +// AnalyticsDefinitionTests.swift // // // Created by Loris Perret on 06/12/2023. @@ -10,44 +10,13 @@ import XCTest @testable import ResgenSwift -final class TagDefinitionTests: XCTestCase { - - // MARK: - Match line - - func testMatchingTagDefinition() { - // Given - let line = "[definition_name]" - - // When - let definition = TagDefinition.match(line) - - // Expect - XCTAssertNotNil(definition) - XCTAssertEqual(definition?.title, "definition_name") - } - - func testNotMatchingTagDefinition() { - // Given - let line1 = "definition_name" - let line2 = "[definition_name" - let line3 = "definition_name]" - - // When - let definition1 = TagDefinition.match(line1) - let definition2 = TagDefinition.match(line2) - let definition3 = TagDefinition.match(line3) - - // Expect - XCTAssertNil(definition1) - XCTAssertNil(definition2) - XCTAssertNil(definition3) - } +final class AnalyticsDefinitionTests: XCTestCase { // MARK: - Matching tags - func testMatchingTags() { + func testMatchingAnalyticss() { // Given - let definition = TagDefinition(title: "definition_name") + let definition = AnalyticsDefinition(id: "definition_name", name: "", type: .screen) definition.tags = ["ios","iosonly","notranslation"] // When @@ -62,9 +31,9 @@ final class TagDefinitionTests: XCTestCase { XCTAssertTrue(match3) } - func testNotMatchingTags() { + func testNotMatchingAnalyticss() { // Given - let definition = TagDefinition(title: "definition_name") + let definition = AnalyticsDefinition(id: "definition_name", name: "", type: .screen) definition.tags = ["ios","iosonly","notranslation"] // When @@ -82,10 +51,8 @@ final class TagDefinitionTests: XCTestCase { func testGeneratedRawPropertyScreen() { // Given - let definition = TagDefinition(title: "definition_name") - definition.path = "ecran_un/" - definition.name = "Ecran un" - definition.type = "screen" + let definition = AnalyticsDefinition(id: "definition_name", name: "Ecran un", type: .screen) + // When let propertyScreen = definition.getProperty() @@ -102,10 +69,7 @@ final class TagDefinitionTests: XCTestCase { func testGeneratedRawPropertyEvent() { // Given - let definition = TagDefinition(title: "definition_name") - definition.path = "ecran_un/" - definition.name = "Ecran un" - definition.type = "event" + let definition = AnalyticsDefinition(id: "definition_name", name: "Ecran un", type: .screen) // When let propertyEvent = definition.getProperty() @@ -122,10 +86,7 @@ final class TagDefinitionTests: XCTestCase { func testGeneratedRawStaticPropertyScreen() { // Given - let definition = TagDefinition(title: "definition_name") - definition.path = "ecran_un/" - definition.name = "Ecran un" - definition.type = "screen" + let definition = AnalyticsDefinition(id: "definition_name", name: "Ecran un", type: .screen) // When let propertyScreen = definition.getStaticProperty() @@ -142,10 +103,7 @@ final class TagDefinitionTests: XCTestCase { func testGeneratedRawStaticPropertyEvent() { // Given - let definition = TagDefinition(title: "definition_name") - definition.path = "ecran_un/" - definition.name = "Ecran un" - definition.type = "event" + let definition = AnalyticsDefinition(id: "definition_name", name: "Ecran un", type: .screen) // When let propertyEvent = definition.getStaticProperty() diff --git a/Tests/ResgenSwiftTests/Tags/TagsGeneratorTests.swift b/Tests/ResgenSwiftTests/Analytics/AnalyticsGeneratorTests.swift similarity index 72% rename from Tests/ResgenSwiftTests/Tags/TagsGeneratorTests.swift rename to Tests/ResgenSwiftTests/Analytics/AnalyticsGeneratorTests.swift index b10dfb5..3978476 100644 --- a/Tests/ResgenSwiftTests/Tags/TagsGeneratorTests.swift +++ b/Tests/ResgenSwiftTests/Analytics/AnalyticsGeneratorTests.swift @@ -1,5 +1,5 @@ // -// TagsGeneratorTests.swift +// AnalyticsGeneratorTests.swift // // // Created by Thibaut Schmitt on 06/09/2022. @@ -11,46 +11,43 @@ import ToolCore @testable import ResgenSwift -final class TagsGeneratorTests: XCTestCase { +final class AnalyticsGeneratorTests: XCTestCase { - private func getTagDefinition(title: String, path: String, name: String, type: String, tags: [String]) -> TagDefinition { - let definition = TagDefinition(title: title) - definition.path = path - definition.name = name - definition.type = type + private func getAnalyticsDefinition(id: String, path: String, name: String, type: AnalyticsDefinition.TagType, tags: [String]) -> AnalyticsDefinition { + let definition = AnalyticsDefinition(id: id, name: name, type: type) definition.tags = tags return definition } func testGeneratedExtensionContentFirebase() { // Given - let sectionOne = TagSection(name: "section_one") + let sectionOne = AnalyticsCategory(id: "section_one") sectionOne.definitions = [ - getTagDefinition(title: "s1_def_one", path: "s1_def_one/", name: "s1 def one", type: "screen", tags: ["ios", "iosonly"]), - getTagDefinition(title: "s1_def_two", path: "s1_def_two/", name: "s1 def two", type: "event", tags: ["ios", "iosonly"]), + getAnalyticsDefinition(id: "s1_def_one", path: "s1_def_one/", name: "s1 def one", type: AnalyticsDefinition.TagType.screen, tags: ["ios", "iosonly"]), + getAnalyticsDefinition(id: "s1_def_two", path: "s1_def_two/", name: "s1 def two", type: AnalyticsDefinition.TagType.event, tags: ["ios", "iosonly"]), ] - let sectionTwo = TagSection(name: "section_two") + let sectionTwo = AnalyticsCategory(id: "section_two") sectionTwo.definitions = [ - getTagDefinition(title: "s2_def_one", path: "s2_def_one/", name: "s2 def one", type: "screen", tags: ["ios","iosonly"]), - getTagDefinition(title: "s2_def_two", path: "s2_def_two/", name: "s2 def two", type: "event", tags: ["droid","droidonly"]), + getAnalyticsDefinition(id: "s2_def_one", path: "s2_def_one/", name: "s2 def one", type: AnalyticsDefinition.TagType.screen, tags: ["ios","iosonly"]), + getAnalyticsDefinition(id: "s2_def_two", path: "s2_def_two/", name: "s2 def two", type: AnalyticsDefinition.TagType.event, tags: ["droid","droidonly"]), ] - let sectionThree = TagSection(name: "section_three") + let sectionThree = AnalyticsCategory(id: "section_three") sectionThree.definitions = [ - getTagDefinition(title: "s3_def_one", path: "s3_def_one/", name: "s3 def one", type: "screen", tags: ["droid","droidonly"]), - getTagDefinition(title: "s3_def_two", path: "s3_def_two/", name: "s3 def two", type: "event", tags: ["droid","droidonly"]), + getAnalyticsDefinition(id: "s3_def_one", path: "s3_def_one/", name: "s3 def one", type: AnalyticsDefinition.TagType.screen, tags: ["droid","droidonly"]), + getAnalyticsDefinition(id: "s3_def_two", path: "s3_def_two/", name: "s3 def two", type: AnalyticsDefinition.TagType.event, tags: ["droid","droidonly"]), ] // When - TagsGenerator.targets = [Tags.TargetType.firebase] - let extensionContent = TagsGenerator.getExtensionContent(sections: [sectionOne, sectionTwo, sectionThree], + AnalyticsGenerator.targets = [Analytics.TargetType.firebase] + let extensionContent = AnalyticsGenerator.getExtensionContent(sections: [sectionOne, sectionTwo, sectionThree], tags: ["ios", "iosonly"], staticVar: false, - extensionName: "GenTags") - // Expect Tags + extensionName: "GenAnalytics") + // Expect Analytics let expect = """ - // Generated by ResgenSwift.Tags 1.2 + // Generated by ResgenSwift.Analytics 1.2 import UIKit import Firebase @@ -138,33 +135,33 @@ final class TagsGeneratorTests: XCTestCase { func testGeneratedExtensionContentMatomo() { // Given - let sectionOne = TagSection(name: "section_one") + let sectionOne = AnalyticsCategory(id: "section_one") sectionOne.definitions = [ - getTagDefinition(title: "s1_def_one", path: "s1_def_one/", name: "s1 def one", type: "screen", tags: ["ios", "iosonly"]), - getTagDefinition(title: "s1_def_two", path: "s1_def_two/", name: "s1 def two", type: "event", tags: ["ios", "iosonly"]), + getAnalyticsDefinition(id: "s1_def_one", path: "s1_def_one/", name: "s1 def one", type: AnalyticsDefinition.TagType.screen, tags: ["ios", "iosonly"]), + getAnalyticsDefinition(id: "s1_def_two", path: "s1_def_two/", name: "s1 def two", type: AnalyticsDefinition.TagType.event, tags: ["ios", "iosonly"]), ] - let sectionTwo = TagSection(name: "section_two") + let sectionTwo = AnalyticsCategory(id: "section_two") sectionTwo.definitions = [ - getTagDefinition(title: "s2_def_one", path: "s2_def_one/", name: "s2 def one", type: "screen", tags: ["ios","iosonly"]), - getTagDefinition(title: "s2_def_two", path: "s2_def_two/", name: "s2 def two", type: "event", tags: ["droid","droidonly"]), + getAnalyticsDefinition(id: "s2_def_one", path: "s2_def_one/", name: "s2 def one", type: AnalyticsDefinition.TagType.screen, tags: ["ios","iosonly"]), + getAnalyticsDefinition(id: "s2_def_two", path: "s2_def_two/", name: "s2 def two", type: AnalyticsDefinition.TagType.event, tags: ["droid","droidonly"]), ] - let sectionThree = TagSection(name: "section_three") + let sectionThree = AnalyticsCategory(id: "section_three") sectionThree.definitions = [ - getTagDefinition(title: "s3_def_one", path: "s3_def_one/", name: "s3 def one", type: "screen", tags: ["droid","droidonly"]), - getTagDefinition(title: "s3_def_two", path: "s3_def_two/", name: "s3 def two", type: "event", tags: ["droid","droidonly"]), + getAnalyticsDefinition(id: "s3_def_one", path: "s3_def_one/", name: "s3 def one", type: AnalyticsDefinition.TagType.screen, tags: ["droid","droidonly"]), + getAnalyticsDefinition(id: "s3_def_two", path: "s3_def_two/", name: "s3 def two", type: AnalyticsDefinition.TagType.event, tags: ["droid","droidonly"]), ] // When - TagsGenerator.targets = [Tags.TargetType.matomo] - let extensionContent = TagsGenerator.getExtensionContent(sections: [sectionOne, sectionTwo, sectionThree], + AnalyticsGenerator.targets = [Analytics.TargetType.matomo] + let extensionContent = AnalyticsGenerator.getExtensionContent(sections: [sectionOne, sectionTwo, sectionThree], tags: ["ios", "iosonly"], staticVar: false, - extensionName: "GenTags") - // Expect Tags + extensionName: "GenAnalytics") + // Expect Analytics let expect = """ - // Generated by ResgenSwift.Tags 1.2 + // Generated by ResgenSwift.Analytics 1.2 import UIKit import MatomoTracker @@ -287,33 +284,33 @@ final class TagsGeneratorTests: XCTestCase { func testGeneratedExtensionContentMatomoAndFirebase() { // Given - let sectionOne = TagSection(name: "section_one") + let sectionOne = AnalyticsCategory(id: "section_one") sectionOne.definitions = [ - getTagDefinition(title: "s1_def_one", path: "s1_def_one/", name: "s1 def one", type: "screen", tags: ["ios", "iosonly"]), - getTagDefinition(title: "s1_def_two", path: "s1_def_two/", name: "s1 def two", type: "event", tags: ["ios", "iosonly"]), + getAnalyticsDefinition(id: "s1_def_one", path: "s1_def_one/", name: "s1 def one", type: AnalyticsDefinition.TagType.screen, tags: ["ios", "iosonly"]), + getAnalyticsDefinition(id: "s1_def_two", path: "s1_def_two/", name: "s1 def two", type: AnalyticsDefinition.TagType.event, tags: ["ios", "iosonly"]), ] - let sectionTwo = TagSection(name: "section_two") + let sectionTwo = AnalyticsCategory(id: "section_two") sectionTwo.definitions = [ - getTagDefinition(title: "s2_def_one", path: "s2_def_one/", name: "s2 def one", type: "screen", tags: ["ios","iosonly"]), - getTagDefinition(title: "s2_def_two", path: "s2_def_two/", name: "s2 def two", type: "event", tags: ["droid","droidonly"]), + getAnalyticsDefinition(id: "s2_def_one", path: "s2_def_one/", name: "s2 def one", type: AnalyticsDefinition.TagType.screen, tags: ["ios","iosonly"]), + getAnalyticsDefinition(id: "s2_def_two", path: "s2_def_two/", name: "s2 def two", type: AnalyticsDefinition.TagType.event, tags: ["droid","droidonly"]), ] - let sectionThree = TagSection(name: "section_three") + let sectionThree = AnalyticsCategory(id: "section_three") sectionThree.definitions = [ - getTagDefinition(title: "s3_def_one", path: "s3_def_one/", name: "s3 def one", type: "screen", tags: ["droid","droidonly"]), - getTagDefinition(title: "s3_def_two", path: "s3_def_two/", name: "s3 def two", type: "event", tags: ["droid","droidonly"]), + getAnalyticsDefinition(id: "s3_def_one", path: "s3_def_one/", name: "s3 def one", type: AnalyticsDefinition.TagType.screen, tags: ["droid","droidonly"]), + getAnalyticsDefinition(id: "s3_def_two", path: "s3_def_two/", name: "s3 def two", type: AnalyticsDefinition.TagType.event, tags: ["droid","droidonly"]), ] // When - TagsGenerator.targets = [Tags.TargetType.matomo, Tags.TargetType.firebase] - let extensionContent = TagsGenerator.getExtensionContent(sections: [sectionOne, sectionTwo, sectionThree], + AnalyticsGenerator.targets = [Analytics.TargetType.matomo, Analytics.TargetType.firebase] + let extensionContent = AnalyticsGenerator.getExtensionContent(sections: [sectionOne, sectionTwo, sectionThree], tags: ["ios", "iosonly"], staticVar: false, - extensionName: "GenTags") - // Expect Tags + extensionName: "GenAnalytics") + // Expect Analytics let expect = """ - // Generated by ResgenSwift.Tags 1.2 + // Generated by ResgenSwift.Analytics 1.2 import UIKit import MatomoTracker diff --git a/Tests/ResgenSwiftTests/Tags/TagSectionTests.swift b/Tests/ResgenSwiftTests/Analytics/AnalyticsSectionTests.swift similarity index 52% rename from Tests/ResgenSwiftTests/Tags/TagSectionTests.swift rename to Tests/ResgenSwiftTests/Analytics/AnalyticsSectionTests.swift index 979fe38..47447e7 100644 --- a/Tests/ResgenSwiftTests/Tags/TagSectionTests.swift +++ b/Tests/ResgenSwiftTests/Analytics/AnalyticsSectionTests.swift @@ -1,5 +1,5 @@ // -// TagSectionTests.swift +// AnalyticsSectionTests.swift // // // Created by Loris Perret on 06/12/2023. @@ -10,53 +10,21 @@ import XCTest @testable import ResgenSwift -final class TagSectionTests: XCTestCase { - - // MARK: - Match line - - func testMatchingTagSection() { - // Given - let line = "[[section_name]]" - - // When - let section = TagSection.match(line) - - // Expect - XCTAssertNotNil(section) - XCTAssertEqual(section?.name, "section_name") - } - - func testNotMatchingTagSection() { - // Given - let lines = ["section_name", - "[section_name]", - "[section_name", - "[[section_name", - "[[section_name]", - "section_name]", - "section_name]]", - "[section_name]]"] - - // When - let matches = lines.compactMap { TagSection.match($0) } - - // Expect - XCTAssertEqual(matches.isEmpty, true) - } +final class AnalyticsSectionTests: XCTestCase { // MARK: - Matching tags - func testMatchingTags() { + func testMatchingAnalytics() { // Given - let section = TagSection(name: "section_name") + let section = AnalyticsCategory(id: "section_name") section.definitions = [ { - let def = TagDefinition(title: "definition_name") + let def = AnalyticsDefinition(id: "definition_name", name: "", type: .screen) def.tags = ["ios","iosonly"] return def }(), { - let def = TagDefinition(title: "definition_name_two") + let def = AnalyticsDefinition(id: "definition_name_two", name: "", type: .screen) def.tags = ["droid","droidonly"] return def }() @@ -75,17 +43,17 @@ final class TagSectionTests: XCTestCase { XCTAssertTrue(match4) } - func testNotMatchingTags() { + func testNotMatchingAnalytics() { // Given - let section = TagSection(name: "section_name") + let section = AnalyticsCategory(id: "section_name") section.definitions = [ { - let def = TagDefinition(title: "definition_name") + let def = AnalyticsDefinition(id: "definition_name", name: "", type: .screen) def.tags = ["ios","iosonly"] return def }(), { - let def = TagDefinition(title: "definition_name_two") + let def = AnalyticsDefinition(id: "definition_name_two", name: "", type: .screen) def.tags = ["droid","droidonly"] return def }() diff --git a/Tests/ResgenSwiftTests/Tags/DiffString.swift b/Tests/ResgenSwiftTests/Analytics/DiffString.swift similarity index 100% rename from Tests/ResgenSwiftTests/Tags/DiffString.swift rename to Tests/ResgenSwiftTests/Analytics/DiffString.swift -- 2.39.5 From ca763cd5d0d6226c03eb02f8c1f93de0c70f8f6a Mon Sep 17 00:00:00 2001 From: Loris Perret Date: Fri, 8 Dec 2023 11:29:48 +0100 Subject: [PATCH 10/21] fix: Rebase tags --- .../Tags/Generated/Tags+GenAllScript.swift | 125 ++---------------- SampleFiles/Tags/sampleTags.txt | 10 +- SampleFiles/genAllRessources.sh | 11 +- SampleFiles/resgenConfiguration.yml | 16 ++- Sources/ResgenSwift/Generate/Generate.swift | 1 + .../ResgenSwift/Generate/GenerateError.swift | 11 ++ .../Generate/Model/ConfigurationFile.swift | 55 +++++++- .../Runnable/TagsConfiguration+Runnable.swift | 4 +- .../Strings/Generator/TagsGenerator.swift | 86 ++++++++++++ Sources/ResgenSwift/Strings/Tag/Tags.swift | 79 +++++++++++ .../ResgenSwift/Strings/Tag/TagsOptions.swift | 47 +++++++ Sources/ResgenSwift/main.swift | 2 + .../Strings/TagsGeneratorTests.swift | 82 ++++++++++++ 13 files changed, 396 insertions(+), 133 deletions(-) create mode 100644 Sources/ResgenSwift/Strings/Generator/TagsGenerator.swift create mode 100644 Sources/ResgenSwift/Strings/Tag/Tags.swift create mode 100644 Sources/ResgenSwift/Strings/Tag/TagsOptions.swift create mode 100644 Tests/ResgenSwiftTests/Strings/TagsGeneratorTests.swift diff --git a/SampleFiles/Tags/Generated/Tags+GenAllScript.swift b/SampleFiles/Tags/Generated/Tags+GenAllScript.swift index e906ace..45fc4ea 100644 --- a/SampleFiles/Tags/Generated/Tags+GenAllScript.swift +++ b/SampleFiles/Tags/Generated/Tags+GenAllScript.swift @@ -1,125 +1,20 @@ -// Generated by ResgenSwift.Tags 1.2 +// Generated by ResgenSwift.Strings.Tags 1.2 import UIKit -import MatomoTracker -import Firebase -// MARK: - Protocol - -protocol AnalyticsManagerProtocol { - func logScreen(name: String, path: String) - func logEvent(name: 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) { - 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) { - 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) { - Analytics.logEvent(AnalyticsEventScreenView, parameters: [AnalyticsParameterScreenName: name]) - } - - func logEvent(name: String) { - var parameters = [ - AnalyticsParameterValue: name - ] - - Analytics.logEvent(AnalyticsEventSelectContent, parameters: parameters) - } -} - -// MARK: - Manager - -class AnalyticsManager { - static var shared = AnalyticsManager() - - // MARK: - Properties - - var managers: [AnalyticsManagerProtocol] = [] - - private var isEnabled: Bool = true - - // MARK: - Methods - - func setAnalyticsEnabled(_ enable: Bool) { isEnabled = enable } - - func configure(sideId: String, url: String) { - managers.append(MatomoAnalyticsManager(siteId: sideId, url: url)) - - managers.append(FirebaseAnalyticsManager()) - } - - private func logScreen(name: String, path: String) { - guard isEnabled else { return } - managers.forEach { manager in - manager.logScreen(name: name, path: path) - } - } - - private func logEvent(name: String) { - guard isEnabled else { return } - managers.forEach { manager in - manager.logEvent(name: name) - } - } +extension Tags { // MARK: - ScreenTag - func logScreenEcranUn() { - logScreen(name: "Ecran un", path: "ecran_un/") + /// Translation in ium : + /// Ecran un + var screen_one: String { + "Ecran un" } - func logEventEcranDeux() { - logEvent(name: "Ecran deux") + /// Translation in ium : + /// Ecran deux + var screen_two: String { + "Ecran deux" } } diff --git a/SampleFiles/Tags/sampleTags.txt b/SampleFiles/Tags/sampleTags.txt index 5ace993..98f67d4 100644 --- a/SampleFiles/Tags/sampleTags.txt +++ b/SampleFiles/Tags/sampleTags.txt @@ -1,13 +1,7 @@ [[ScreenTag]] [screen_one] - path = ecran_un/ - name = Ecran un - type = screen + ium = Ecran un tags = droid,ios - comments = [screen_two] - path = ecran_deux/ - name = Ecran deux - type = event + ium = Ecran deux tags = droid,ios - comments = diff --git a/SampleFiles/genAllRessources.sh b/SampleFiles/genAllRessources.sh index ddab6b1..bd96019 100755 --- a/SampleFiles/genAllRessources.sh +++ b/SampleFiles/genAllRessources.sh @@ -45,10 +45,19 @@ echo "\n-------------------------\n" ## Tags swift run -c release ResgenSwift strings tags $FORCE_FLAG "./Tags/sampleTags.txt" \ - --target "matomo firebase" \ + --lang "ium" \ --extension-output-path "./Tags/Generated" \ --extension-name "Tags" \ --extension-suffix "GenAllScript" +# +#echo "\n-------------------------\n" + +## Analytics +#swift run -c release ResgenSwift analytics $FORCE_FLAG "./Tags/sampleTags.yml" \ +# --target "matomo firebase" \ +# --extension-output-path "./Tags/Generated" \ +# --extension-name "Analytics" \ +# --extension-suffix "GenAllScript" echo "\n-------------------------\n" # diff --git a/SampleFiles/resgenConfiguration.yml b/SampleFiles/resgenConfiguration.yml index fbc512a..512fc67 100644 --- a/SampleFiles/resgenConfiguration.yml +++ b/SampleFiles/resgenConfiguration.yml @@ -70,14 +70,26 @@ colors: # Tags # tags: -- +- inputFile: ./Tags/sampleTags.txt - target: "matomo firebase" + lang: ium extensionOutputPath: ./Tags/Generated extensionName: Tags extensionSuffix: GenAllScript +# +# Analytics +# +analytics: +- + inputFile: ./Tags/sampleTags.yml + target: "matomo firebase" + extensionOutputPath: ./Tags/Generated + extensionName: Analytics + extensionSuffix: GenAllScript + + # # Fonts # diff --git a/Sources/ResgenSwift/Generate/Generate.swift b/Sources/ResgenSwift/Generate/Generate.swift index 4ac6685..a051046 100644 --- a/Sources/ResgenSwift/Generate/Generate.swift +++ b/Sources/ResgenSwift/Generate/Generate.swift @@ -34,6 +34,7 @@ struct Generate: ParsableCommand { // Parse let configuration = ConfigurationFileParser.parse(options.configurationFile) print("Found configurations :") + print(" - \(configuration.analytics.count) analytics configuration(s)") print(" - \(configuration.colors.count) colors configuration(s)") print(" - \(configuration.fonts.count) fonts configuration(s)") print(" - \(configuration.images.count) images configuration(s)") diff --git a/Sources/ResgenSwift/Generate/GenerateError.swift b/Sources/ResgenSwift/Generate/GenerateError.swift index 2ebc045..e6bab8c 100644 --- a/Sources/ResgenSwift/Generate/GenerateError.swift +++ b/Sources/ResgenSwift/Generate/GenerateError.swift @@ -13,6 +13,11 @@ enum GenerateError: Error { case commandError([String], String) case writeFile(String, String) + // Analytics + + case missingElement(String) + case invalidParameter(String) + var description: String { switch self { case .fileNotExists(let filename): @@ -29,6 +34,12 @@ enum GenerateError: Error { case .writeFile(let filename, let info): return "error: [\(Generate.toolName)] An error occured while writing file in \(filename): \(info)" + + case .missingElement(let element): + return "error: [\(Generate.toolName)] Missing \(element) for Matomo" + + case .invalidParameter(let reason): + return "error: [\(Generate.toolName)] Invalid parameter \(reason)" } } } diff --git a/Sources/ResgenSwift/Generate/Model/ConfigurationFile.swift b/Sources/ResgenSwift/Generate/Model/ConfigurationFile.swift index 47ddb79..a631426 100644 --- a/Sources/ResgenSwift/Generate/Model/ConfigurationFile.swift +++ b/Sources/ResgenSwift/Generate/Model/ConfigurationFile.swift @@ -9,6 +9,7 @@ import Foundation struct ConfigurationFile: Codable, CustomDebugStringConvertible { var architecture: ConfigurationArchitecture? + var analytics: [AnalyticsConfiguration] var colors: [ColorsConfiguration] var fonts: [FontsConfiguration] var images: [ImagesConfiguration] @@ -16,12 +17,15 @@ struct ConfigurationFile: Codable, CustomDebugStringConvertible { var tags: [TagsConfiguration] var runnableConfigurations: [Runnable] { - let runnables: [[Runnable]] = [colors, fonts, images, strings, tags] + let runnables: [[Runnable]] = [analytics, colors, fonts, images, strings, tags] return Array(runnables.joined()) } var debugDescription: String { """ + \(analytics) + ----------- + ----------- \(colors) ----------- ----------- @@ -76,6 +80,47 @@ struct ConfigurationArchitecture: Codable { } } +struct AnalyticsConfiguration: Codable, CustomDebugStringConvertible { + let inputFile: String + let target: String + let extensionOutputPath: String + let extensionName: String? + let extensionSuffix: String? + private let staticMembers: Bool? + + var staticMembersOptions: Bool { + if let staticMembers = staticMembers { + return staticMembers + } + return false + } + + internal init(inputFile: String, + target: String, + extensionOutputPath: String, + extensionName: String?, + extensionSuffix: String?, + staticMembers: Bool?) { + self.inputFile = inputFile + self.target = target + self.extensionOutputPath = extensionOutputPath + self.extensionName = extensionName + self.extensionSuffix = extensionSuffix + self.staticMembers = staticMembers + } + + var debugDescription: String { + """ + Analytics configuration: + - Input file: \(inputFile) + - Target: \(target) + - Extension output path: \(extensionOutputPath) + - Extension name: \(extensionName ?? "-") + - Extension suffix: \(extensionSuffix ?? "-") + """ + } +} + struct ColorsConfiguration: Codable, CustomDebugStringConvertible { let inputFile: String let style: String @@ -266,7 +311,7 @@ struct StringsConfiguration: Codable, CustomDebugStringConvertible { struct TagsConfiguration: Codable, CustomDebugStringConvertible { let inputFile: String - let target: String + let lang: String let extensionOutputPath: String let extensionName: String? let extensionSuffix: String? @@ -280,13 +325,13 @@ struct TagsConfiguration: Codable, CustomDebugStringConvertible { } internal init(inputFile: String, - target: String, + lang: String, extensionOutputPath: String, extensionName: String?, extensionSuffix: String?, staticMembers: Bool?) { self.inputFile = inputFile - self.target = target + self.lang = lang self.extensionOutputPath = extensionOutputPath self.extensionName = extensionName self.extensionSuffix = extensionSuffix @@ -297,7 +342,7 @@ struct TagsConfiguration: Codable, CustomDebugStringConvertible { """ Tags configuration: - Input file: \(inputFile) - - Target: \(target) + - Lang: \(lang) - Extension output path: \(extensionOutputPath) - Extension name: \(extensionName ?? "-") - Extension suffix: \(extensionSuffix ?? "-") diff --git a/Sources/ResgenSwift/Generate/Runnable/TagsConfiguration+Runnable.swift b/Sources/ResgenSwift/Generate/Runnable/TagsConfiguration+Runnable.swift index 198f5ea..04c6648 100644 --- a/Sources/ResgenSwift/Generate/Runnable/TagsConfiguration+Runnable.swift +++ b/Sources/ResgenSwift/Generate/Runnable/TagsConfiguration+Runnable.swift @@ -17,8 +17,8 @@ extension TagsConfiguration: Runnable { args += [ inputFile.prependIfRelativePath(projectDirectory), - "--target", - target, + "--lang", + lang, "--extension-output-path", extensionOutputPath.prependIfRelativePath(projectDirectory), "--static-members", diff --git a/Sources/ResgenSwift/Strings/Generator/TagsGenerator.swift b/Sources/ResgenSwift/Strings/Generator/TagsGenerator.swift new file mode 100644 index 0000000..ee09d73 --- /dev/null +++ b/Sources/ResgenSwift/Strings/Generator/TagsGenerator.swift @@ -0,0 +1,86 @@ +// +// TagsGenerator.swift +// +// +// Created by Thibaut Schmitt on 10/01/2022. +// + +import Foundation +import ToolCore +import CoreVideo + +class 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) + + // Write content + let extensionFilePathURL = URL(fileURLWithPath: extensionFilePath) + do { + try extensionFileContent.write(to: extensionFilePathURL, atomically: false, encoding: .utf8) + } catch (let error) { + let error = StringiumError.writeFile(extensionFilePath, error.localizedDescription) + print(error.description) + Stringium.exit(withError: error) + } + } + + // MARK: - Extension content + + static func getExtensionContent(sections: [Section], lang: String, tags: [String], staticVar: Bool, extensionName: String) -> String { + [ + Self.getHeader(extensionClassname: extensionName, staticVar: staticVar), + Self.getProperties(sections: sections, lang: lang, tags: tags, staticVar: staticVar), + Self.getFooter() + ] + .joined(separator: "\n") + } + + // MARK: - Extension part + + private static func getHeader(extensionClassname: String, staticVar: Bool) -> String { + """ + // Generated by ResgenSwift.Strings.\(Tags.toolName) \(ResgenSwiftVersion) + + \(staticVar ? "typelias Tags = String\n\n" : "")import UIKit + + extension \(extensionClassname) { + """ + } + + private static func getProperties(sections: [Section], lang: String, tags: [String], staticVar: Bool) -> String { + sections + .compactMap { section in + // Check that at least one string will be generated + guard section.hasOneOrMoreMatchingTags(tags: tags) else { + return nil// Go to next section + } + + var res = "\n // MARK: - \(section.name)" + section.definitions.forEach { definition in + guard definition.hasOneOrMoreMatchingTags(inputTags: tags) == true else { + return // Go to next definition + } + + if staticVar { + res += "\n\n\(definition.getStaticProperty(forLang: lang))" + } else { + res += "\n\n\(definition.getProperty(forLang: lang))" + } + } + return res + } + .joined(separator: "\n") + } + + private static func getFooter() -> String { + """ + } + + """ + } +} diff --git a/Sources/ResgenSwift/Strings/Tag/Tags.swift b/Sources/ResgenSwift/Strings/Tag/Tags.swift new file mode 100644 index 0000000..7fedafa --- /dev/null +++ b/Sources/ResgenSwift/Strings/Tag/Tags.swift @@ -0,0 +1,79 @@ +// +// Tag.swift +// +// +// Created by Thibaut Schmitt on 10/01/2022. +// + +import ToolCore +import Foundation +import ArgumentParser + +struct Tags: ParsableCommand { + + // MARK: - Command Configuration + + static var configuration = CommandConfiguration( + abstract: "Generate tags extension file.", + version: ResgenSwiftVersion + ) + + + // MARK: - Static + + static let toolName = "Tags" + static let defaultExtensionName = "Tags" + static let noTranslationTag: String = "notranslation" + + // MARK: - Command Options + + @OptionGroup var options: TagsOptions + + // MARK: - Run + + mutating func run() { + print("[\(Self.toolName)] Starting tags generation") + print("[\(Self.toolName)] Will use inputFile \(options.inputFile) to generate strings for lang: \(options.lang)") + + // Check requirements + guard checkRequirements() else { return } + + print("[\(Self.toolName)] Will generate tags") + + // Parse input file + let sections = TwineFileParser.parse(options.inputFile) + + // Generate extension + TagsGenerator.writeExtensionFiles(sections: sections, + lang: options.lang, + tags: ["ios", "iosonly", Self.noTranslationTag], + staticVar: options.staticMembers, + extensionName: options.extensionName, + extensionFilePath: options.extensionFilePath) + + print("[\(Self.toolName)] Tags generated") + } + + // MARK: - Requirements + + private func checkRequirements() -> Bool { + let fileManager = FileManager() + + // Input file + guard fileManager.fileExists(atPath: options.inputFile) else { + let error = StringiumError.fileNotExists(options.inputFile) + print(error.description) + Stringium.exit(withError: error) + } + + // Check if needed to regenerate + guard GeneratorChecker.shouldGenerate(force: options.forceGeneration, + inputFilePath: options.inputFile, + extensionFilePath: options.extensionFilePath) else { + print("[\(Self.toolName)] Tags are already up to date :) ") + return false + } + + return true + } +} diff --git a/Sources/ResgenSwift/Strings/Tag/TagsOptions.swift b/Sources/ResgenSwift/Strings/Tag/TagsOptions.swift new file mode 100644 index 0000000..e5372b9 --- /dev/null +++ b/Sources/ResgenSwift/Strings/Tag/TagsOptions.swift @@ -0,0 +1,47 @@ +// +// TagOptions.swift +// +// +// Created by Thibaut Schmitt on 10/01/2022. +// + +import Foundation +import ArgumentParser + +struct TagsOptions: ParsableArguments { + @Flag(name: [.customShort("f"), .customShort("F")], help: "Should force generation") + var forceGeneration = false + + @Argument(help: "Input files where tags ared defined.", transform: { $0.replaceTiltWithHomeDirectoryPath() }) + var inputFile: String + + @Option(help: "Lang to generate. (\"ium\" by default)") + var lang: String = "ium" + + @Option(help: "Path where to generate the extension.", transform: { $0.replaceTiltWithHomeDirectoryPath() }) + var extensionOutputPath: String + + @Option(help: "Tell if it will generate static properties or not") + var staticMembers: Bool = false + + @Option(help: "Extension name. If not specified, it will generate a Tag extension.") + var extensionName: String = Tags.defaultExtensionName + + @Option(help: "Extension suffix. Ex: MyApp, it will generate {extensionName}+Tag{extensionSuffix}.swift") + var extensionSuffix: String? +} + +// MARK: - Computed var + +extension TagsOptions { + var extensionFileName: String { + if let extensionSuffix = extensionSuffix { + return "\(extensionName)+\(extensionSuffix).swift" + } + return "\(extensionName).swift" + } + + var extensionFilePath: String { + "\(extensionOutputPath)/\(extensionFileName)" + } +} diff --git a/Sources/ResgenSwift/main.swift b/Sources/ResgenSwift/main.swift index 996161e..01bd9c2 100644 --- a/Sources/ResgenSwift/main.swift +++ b/Sources/ResgenSwift/main.swift @@ -19,10 +19,12 @@ struct ResgenSwift: ParsableCommand { // With language support for type-level introspection, this could be // provided by automatically finding nested `ParsableCommand` types. subcommands: [ + Analytics.self, Colors.self, Fonts.self, Images.self, Strings.self, + Tags.self, Generate.self ] diff --git a/Tests/ResgenSwiftTests/Strings/TagsGeneratorTests.swift b/Tests/ResgenSwiftTests/Strings/TagsGeneratorTests.swift new file mode 100644 index 0000000..561c70c --- /dev/null +++ b/Tests/ResgenSwiftTests/Strings/TagsGeneratorTests.swift @@ -0,0 +1,82 @@ +// +// TagsGeneratorTests.swift +// +// +// Created by Thibaut Schmitt on 06/09/2022. +// + +import Foundation +import XCTest +import ToolCore + +@testable import ResgenSwift + +final class TagsGeneratorTests: XCTestCase { + + private func getDefinition(name: String, lang: String, tags: [String]) -> Definition { + let definition = Definition(name: name) + definition.tags = tags + definition.translations = [lang: "Some translation"] + return definition + } + + func testGeneratedExtensionContent() { + // Given + let sectionOne = Section(name: "section_one") + sectionOne.definitions = [ + getDefinition(name: "s1_def_one", lang: "ium", tags: ["ios","iosonly"]), + getDefinition(name: "s1_def_two", lang: "ium", tags: ["ios","iosonly"]), + ] + + let sectionTwo = Section(name: "section_two") + sectionTwo.definitions = [ + getDefinition(name: "s2_def_one", lang: "ium", tags: ["ios","iosonly"]), + getDefinition(name: "s2_def_two", lang: "ium", tags: ["droid","droidonly"]) + ] + + let sectionThree = Section(name: "section_three") + sectionThree.definitions = [ + getDefinition(name: "s3_def_one", lang: "ium", tags: ["droid","droidonly"]), + getDefinition(name: "s3_def_two", lang: "ium", tags: ["droid","droidonly"]) + ] + + // When + let extensionContent = TagsGenerator.getExtensionContent(sections: [sectionOne, sectionTwo, sectionThree], + lang: "ium", + tags: ["ios", "iosonly"], + staticVar: false, + extensionName: "GenTags") + // Expect Tags + let expect = """ + // Generated by ResgenSwift.Strings.Tags \(ResgenSwiftVersion) + + import UIKit + + extension GenTags { + // MARK: - section_one + + /// Translation in ium : + /// Some translation + var s1_def_one: String { + "Some translation" + } + + /// Translation in ium : + /// Some translation + var s1_def_two: String { + "Some translation" + } + + // MARK: - section_two + + /// Translation in ium : + /// Some translation + var s2_def_one: String { + "Some translation" + } + } + """ + + XCTAssertEqual(extensionContent.adaptForXCTest(), expect.adaptForXCTest()) + } +} -- 2.39.5 From 1ee4998ec63849e7ae4a83f1bbb3680f8cc40c9f Mon Sep 17 00:00:00 2001 From: Loris Perret Date: Fri, 8 Dec 2023 14:22:17 +0100 Subject: [PATCH 11/21] test: Edit test --- .../Generated/Analytics+GenAllScript.swift | 32 +- SampleFiles/Tags/sampleTags.yml | 57 ++-- SampleFiles/genAllRessources.sh | 28 +- .../Generator/AnalyticsGenerator.swift | 4 +- .../Parser/AnalyticsFileParser.swift | 52 +-- .../Analytics/AnalyticsDefinitionTests.swift | 39 ++- .../Analytics/AnalyticsGeneratorTests.swift | 304 ++++++++++++++---- 7 files changed, 364 insertions(+), 152 deletions(-) diff --git a/SampleFiles/Tags/Generated/Analytics+GenAllScript.swift b/SampleFiles/Tags/Generated/Analytics+GenAllScript.swift index bf18797..23b104a 100644 --- a/SampleFiles/Tags/Generated/Analytics+GenAllScript.swift +++ b/SampleFiles/Tags/Generated/Analytics+GenAllScript.swift @@ -127,7 +127,7 @@ class AnalyticsManager { // MARK: - Methods - func setAnalyticsEnabled(_ enable: Bool) { + func setAnalyticsEnabled(_ enable: Bool) { isEnabled = enable } @@ -167,24 +167,30 @@ class AnalyticsManager { } } - // MARK: - Introduction + // MARK: - section_one - func logScreenIntroductionScreen(title: String) { + func logScreenS1DefOne() { logScreen( - name: "Bienvenue \(title)", - path: "introduction/" + name: "s1 def one", + path: "s1_def_one/" ) } - func logEventIntroductionScreen(test: String, data: Int) { + func logEventS1DefTwo() { logEvent( - name: "Bienvenue", - action: "action", - category: "category", - params: [ - "test": test, - "data": data - ] + name: "s1 def two", + action: "test", + category: "test", + params: [] + ) + } + + // MARK: - section_two + + func logScreenS2DefOne() { + logScreen( + name: "s2 def one", + path: "s2_def_one/" ) } } diff --git a/SampleFiles/Tags/sampleTags.yml b/SampleFiles/Tags/sampleTags.yml index 4933909..310acb4 100644 --- a/SampleFiles/Tags/sampleTags.yml +++ b/SampleFiles/Tags/sampleTags.yml @@ -1,24 +1,43 @@ --- categories: - - id: Introduction + - id: section_one screens: - - id: introduction_screen - name: Bienvenue _TITLE_ - path: introduction/ - tags: droid,ios - parameters: - - name: title - type: String - replaceIn: name + - id: s1_def_one + name: s1 def one + path: s1_def_one/ + tags: ios events: - - id: introduction_screen - name: Bienvenue - category: category - action: action - tags: droid,ios - parameters: - - name: test - type: String - - name: data - type: Int + - id: s1_def_two + name: s1 def two + action: test + category: test + tags: ios + + - id: section_two + screens: + - id: s2_def_one + name: s2 def one + path: s2_def_one/ + tags: ios + + events: + - id: s2_def_two + name: s2 def two + action: test + category: test + tags: droid + + - id: section_three + screens: + - id: s3_def_one + name: s3 def one + path: s3_def_one/ + tags: droid + + events: + - id: s3_def_two + name: s3 def two + action: test + category: test + tags: droid diff --git a/SampleFiles/genAllRessources.sh b/SampleFiles/genAllRessources.sh index bd96019..fc001fd 100755 --- a/SampleFiles/genAllRessources.sh +++ b/SampleFiles/genAllRessources.sh @@ -32,7 +32,7 @@ FORCE_FLAG="$1" #echo "\n-------------------------\n" -# Strings +## Strings #swift run -c release ResgenSwift strings stringium $FORCE_FLAG "./Strings/sampleStrings.txt" \ # --output-path "./Strings/Generated" \ # --langs "fr en en-us" \ @@ -41,23 +41,23 @@ FORCE_FLAG="$1" # --extension-name "String" \ # --extension-suffix "GenAllScript" -echo "\n-------------------------\n" - -## Tags -swift run -c release ResgenSwift strings tags $FORCE_FLAG "./Tags/sampleTags.txt" \ - --lang "ium" \ - --extension-output-path "./Tags/Generated" \ - --extension-name "Tags" \ - --extension-suffix "GenAllScript" -# #echo "\n-------------------------\n" -## Analytics -#swift run -c release ResgenSwift analytics $FORCE_FLAG "./Tags/sampleTags.yml" \ -# --target "matomo firebase" \ +## Tags +#swift run -c release ResgenSwift strings tags $FORCE_FLAG "./Tags/sampleTags.txt" \ +# --lang "ium" \ # --extension-output-path "./Tags/Generated" \ -# --extension-name "Analytics" \ +# --extension-name "Tags" \ # --extension-suffix "GenAllScript" +# +echo "\n-------------------------\n" + +# Analytics +swift run -c release ResgenSwift analytics $FORCE_FLAG "./Tags/sampleTags.yml" \ + --target "matomo firebase" \ + --extension-output-path "./Tags/Generated" \ + --extension-name "Analytics" \ + --extension-suffix "GenAllScript" echo "\n-------------------------\n" # diff --git a/Sources/ResgenSwift/Analytics/Generator/AnalyticsGenerator.swift b/Sources/ResgenSwift/Analytics/Generator/AnalyticsGenerator.swift index f16f2ef..3da67c4 100644 --- a/Sources/ResgenSwift/Analytics/Generator/AnalyticsGenerator.swift +++ b/Sources/ResgenSwift/Analytics/Generator/AnalyticsGenerator.swift @@ -22,8 +22,6 @@ class AnalyticsGenerator { } } - - // Get extension content let extensionFileContent = Self.getExtensionContent(sections: sections, tags: tags, @@ -84,7 +82,7 @@ class AnalyticsGenerator { // MARK: - Methods - func setAnalyticsEnabled(_ enable: Bool) { + func setAnalyticsEnabled(_ enable: Bool) { isEnabled = enable } """ diff --git a/Sources/ResgenSwift/Analytics/Parser/AnalyticsFileParser.swift b/Sources/ResgenSwift/Analytics/Parser/AnalyticsFileParser.swift index 0992aed..7b57d17 100644 --- a/Sources/ResgenSwift/Analytics/Parser/AnalyticsFileParser.swift +++ b/Sources/ResgenSwift/Analytics/Parser/AnalyticsFileParser.swift @@ -91,16 +91,16 @@ class AnalyticsFileParser { parameters: screen.parameters ) - guard target.contains(Analytics.TargetType.matomo.value) else { continue } + if target.contains(Analytics.TargetType.matomo.value) { + // Path + + guard let path = screen.path else { + let error = GenerateError.missingElement("screen path") + Generate.exit(withError: error) + } - // Path - - guard let path = screen.path else { - let error = GenerateError.missingElement("screen path") - Generate.exit(withError: error) + definition.path = path } - - definition.path = path definitions.append(definition) } @@ -121,25 +121,25 @@ class AnalyticsFileParser { parameters: event.parameters ) - guard target.contains(Analytics.TargetType.matomo.value) else { continue } - - // Category - - guard let category = event.category else { - let error = GenerateError.missingElement("event category") - Generate.exit(withError: error) + if target.contains(Analytics.TargetType.matomo.value) { + // Category + + guard let category = event.category else { + let error = GenerateError.missingElement("event category") + Generate.exit(withError: error) + } + + definition.category = category + + // Action + + guard let action = event.action else { + let error = GenerateError.missingElement("event action") + Generate.exit(withError: error) + } + + definition.action = action } - - definition.category = category - - // Action - - guard let action = event.action else { - let error = GenerateError.missingElement("event action") - Generate.exit(withError: error) - } - - definition.action = action definitions.append(definition) } diff --git a/Tests/ResgenSwiftTests/Analytics/AnalyticsDefinitionTests.swift b/Tests/ResgenSwiftTests/Analytics/AnalyticsDefinitionTests.swift index bbf451d..756f79e 100644 --- a/Tests/ResgenSwiftTests/Analytics/AnalyticsDefinitionTests.swift +++ b/Tests/ResgenSwiftTests/Analytics/AnalyticsDefinitionTests.swift @@ -52,15 +52,18 @@ final class AnalyticsDefinitionTests: XCTestCase { func testGeneratedRawPropertyScreen() { // Given let definition = AnalyticsDefinition(id: "definition_name", name: "Ecran un", type: .screen) - + definition.path = "ecran_un/" // When let propertyScreen = definition.getProperty() // Expect let expectScreen = """ - func logScreenEcranUn() { - logScreen(name: "Ecran un", path: "ecran_un/") + func logScreenDefinitionName() { + logScreen( + name: "Ecran un", + path: "ecran_un/" + ) } """ @@ -69,15 +72,20 @@ final class AnalyticsDefinitionTests: XCTestCase { func testGeneratedRawPropertyEvent() { // Given - let definition = AnalyticsDefinition(id: "definition_name", name: "Ecran un", type: .screen) + let definition = AnalyticsDefinition(id: "definition_name", name: "Ecran un", type: .event) // When let propertyEvent = definition.getProperty() // Expect let expectEvent = """ - func logEventEcranUn() { - logEvent(name: "Ecran un") + func logEventDefinitionName() { + logEvent( + name: "Ecran un", + action: "", + category: "", + params: [] + ) } """ @@ -87,14 +95,18 @@ final class AnalyticsDefinitionTests: XCTestCase { func testGeneratedRawStaticPropertyScreen() { // Given let definition = AnalyticsDefinition(id: "definition_name", name: "Ecran un", type: .screen) + definition.path = "ecran_un/" // When let propertyScreen = definition.getStaticProperty() // Expect let expectScreen = """ - static func logScreenEcranUn() { - logScreen(name: "Ecran un", path: "ecran_un/") + static func logScreenDefinitionName() { + logScreen( + name: "Ecran un", + path: "ecran_un/" + ) } """ @@ -103,15 +115,20 @@ final class AnalyticsDefinitionTests: XCTestCase { func testGeneratedRawStaticPropertyEvent() { // Given - let definition = AnalyticsDefinition(id: "definition_name", name: "Ecran un", type: .screen) + let definition = AnalyticsDefinition(id: "definition_name", name: "Ecran un", type: .event) // When let propertyEvent = definition.getStaticProperty() // Expect let expectEvent = """ - static func logEventEcranUn() { - logEvent(name: "Ecran un") + static func logEventDefinitionName() { + logEvent( + name: "Ecran un", + action: "", + category: "", + params: [] + ) } """ diff --git a/Tests/ResgenSwiftTests/Analytics/AnalyticsGeneratorTests.swift b/Tests/ResgenSwiftTests/Analytics/AnalyticsGeneratorTests.swift index 3978476..88f9883 100644 --- a/Tests/ResgenSwiftTests/Analytics/AnalyticsGeneratorTests.swift +++ b/Tests/ResgenSwiftTests/Analytics/AnalyticsGeneratorTests.swift @@ -13,9 +13,20 @@ import ToolCore final class AnalyticsGeneratorTests: XCTestCase { - private func getAnalyticsDefinition(id: String, path: String, name: String, type: AnalyticsDefinition.TagType, tags: [String]) -> AnalyticsDefinition { + private func getAnalyticsDefinition( + id: String, + path: String = "", + action: String = "", + category: String = "", + name: String, + type: AnalyticsDefinition.TagType, + tags: [String] + ) -> AnalyticsDefinition { let definition = AnalyticsDefinition(id: id, name: name, type: type) definition.tags = tags + definition.path = path + definition.action = action + definition.category = category return definition } @@ -23,20 +34,20 @@ final class AnalyticsGeneratorTests: XCTestCase { // Given let sectionOne = AnalyticsCategory(id: "section_one") sectionOne.definitions = [ - getAnalyticsDefinition(id: "s1_def_one", path: "s1_def_one/", name: "s1 def one", type: AnalyticsDefinition.TagType.screen, tags: ["ios", "iosonly"]), - getAnalyticsDefinition(id: "s1_def_two", path: "s1_def_two/", name: "s1 def two", type: AnalyticsDefinition.TagType.event, tags: ["ios", "iosonly"]), + 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", path: "s2_def_one/", name: "s2 def one", type: AnalyticsDefinition.TagType.screen, tags: ["ios","iosonly"]), - getAnalyticsDefinition(id: "s2_def_two", path: "s2_def_two/", name: "s2 def two", type: AnalyticsDefinition.TagType.event, tags: ["droid","droidonly"]), + 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", path: "s3_def_one/", name: "s3 def one", type: AnalyticsDefinition.TagType.screen, tags: ["droid","droidonly"]), - getAnalyticsDefinition(id: "s3_def_two", path: "s3_def_two/", name: "s3 def two", type: AnalyticsDefinition.TagType.event, tags: ["droid","droidonly"]), + 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 @@ -49,29 +60,55 @@ final class AnalyticsGeneratorTests: XCTestCase { let expect = """ // Generated by ResgenSwift.Analytics 1.2 - import UIKit import Firebase // MARK: - Protocol protocol AnalyticsManagerProtocol { func logScreen(name: String, path: String) - func logEvent(name: String) + func logEvent( + name: String, + action: String, + category: String, + params: [String: Any]? + ) } // MARK: - Firebase class FirebaseAnalyticsManager: AnalyticsManagerProtocol { func logScreen(name: String, path: String) { - Analytics.logEvent(AnalyticsEventScreenView, parameters: [AnalyticsParameterScreenName: name]) + var parameters = [ + AnalyticsParameterScreenName: name + ] + + Analytics.logEvent( + AnalyticsEventScreenView, + parameters: parameters + ) } - func logEvent(name: String) { - var parameters = [ - AnalyticsParameterValue: name + func logEvent( + name: String, + action: String, + category: String, + params: [String: Any]? + ) { + var parameters: [String:Any] = [ + "action": action, + "category": category, ] + + if let supplementaryParameters = params { + parameters.merge(supplementaryParameters) { (origin, new) -> Any in + return origin + } + } - Analytics.logEvent(AnalyticsEventSelectContent, parameters: parameters) + Analytics.logEvent( + name, + parameters: parameters + ) } } @@ -88,7 +125,9 @@ final class AnalyticsGeneratorTests: XCTestCase { // MARK: - Methods - func setAnalyticsEnabled(_ enable: Bool) { isEnabled = enable } + func setAnalyticsEnabled(_ enable: Bool) { + isEnabled = enable + } func configure() { managers.append(FirebaseAnalyticsManager()) @@ -96,32 +135,55 @@ final class AnalyticsGeneratorTests: XCTestCase { private func logScreen(name: String, path: String) { guard isEnabled else { return } + managers.forEach { manager in manager.logScreen(name: name, path: path) } } - private func logEvent(name: String) { + private func logEvent( + name: String, + action: String, + category: String, + params: [String: Any]? + ) { guard isEnabled else { return } + managers.forEach { manager in - manager.logEvent(name: name) + manager.logEvent( + name: name, + action: action, + category: category, + params: params + ) } } // MARK: - section_one func logScreenS1DefOne() { - logScreen(name: "s1 def one", path: "s1_def_one/") + logScreen( + name: "s1 def one", + path: "" + ) } func logEventS1DefTwo() { - logEvent(name: "s1 def two") + logEvent( + name: "s1 def two", + action: "", + category: "", + params: [] + ) } - + // MARK: - section_two func logScreenS2DefOne() { - logScreen(name: "s2 def one", path: "s2_def_one/") + logScreen( + name: "s2 def one", + path: "" + ) } } @@ -137,20 +199,20 @@ final class AnalyticsGeneratorTests: XCTestCase { // Given let sectionOne = AnalyticsCategory(id: "section_one") sectionOne.definitions = [ - getAnalyticsDefinition(id: "s1_def_one", path: "s1_def_one/", name: "s1 def one", type: AnalyticsDefinition.TagType.screen, tags: ["ios", "iosonly"]), - getAnalyticsDefinition(id: "s1_def_two", path: "s1_def_two/", name: "s1 def two", type: AnalyticsDefinition.TagType.event, tags: ["ios", "iosonly"]), + 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: AnalyticsDefinition.TagType.screen, tags: ["ios","iosonly"]), - getAnalyticsDefinition(id: "s2_def_two", path: "s2_def_two/", name: "s2 def two", type: AnalyticsDefinition.TagType.event, tags: ["droid","droidonly"]), + 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: AnalyticsDefinition.TagType.screen, tags: ["droid","droidonly"]), - getAnalyticsDefinition(id: "s3_def_two", path: "s3_def_two/", name: "s3 def two", type: AnalyticsDefinition.TagType.event, tags: ["droid","droidonly"]), + 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 @@ -163,14 +225,18 @@ final class AnalyticsGeneratorTests: XCTestCase { let expect = """ // Generated by ResgenSwift.Analytics 1.2 - import UIKit import MatomoTracker // MARK: - Protocol protocol AnalyticsManagerProtocol { func logScreen(name: String, path: String) - func logEvent(name: String) + func logEvent( + name: String, + action: String, + category: String, + params: [String: Any]? + ) } // MARK: - Matomo @@ -186,7 +252,10 @@ final class AnalyticsGeneratorTests: XCTestCase { 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)!) + tracker = MatomoTracker( + siteId: siteId, + baseURL: URL(string: url)! + ) #if DEBUG tracker.dispatchInterval = 5 @@ -205,6 +274,7 @@ final class AnalyticsGeneratorTests: XCTestCase { 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], @@ -212,11 +282,17 @@ final class AnalyticsGeneratorTests: XCTestCase { ) } - func logEvent(name: String) { + func logEvent( + name: String, + action: String, + category: String, + params: [String: Any]? + ) { guard !tracker.isOptedOut else { return } + tracker.track( - eventWithCategory: "category", - action: "action", + eventWithCategory: category, + action: action, name: name, number: nil, url: nil @@ -237,40 +313,70 @@ final class AnalyticsGeneratorTests: XCTestCase { // MARK: - Methods - func setAnalyticsEnabled(_ enable: Bool) { isEnabled = enable } + func setAnalyticsEnabled(_ enable: Bool) { + isEnabled = enable + } func configure(siteId: String, url: String) { - managers.append(MatomoAnalyticsManager(siteId: siteId, url: url)) + managers.append( + MatomoAnalyticsManager( + siteId: siteId, + url: url + ) + ) } private func logScreen(name: String, path: String) { guard isEnabled else { return } + managers.forEach { manager in manager.logScreen(name: name, path: path) } } - private func logEvent(name: String) { + private func logEvent( + name: String, + action: String, + category: String, + params: [String: Any]? + ) { guard isEnabled else { return } + managers.forEach { manager in - manager.logEvent(name: name) + manager.logEvent( + name: name, + action: action, + category: category, + params: params + ) } } // MARK: - section_one func logScreenS1DefOne() { - logScreen(name: "s1 def one", path: "s1_def_one/") + logScreen( + name: "s1 def one", + path: "s1_def_one/" + ) } func logEventS1DefTwo() { - logEvent(name: "s1 def two") + logEvent( + name: "s1 def two", + action: "test", + category: "test", + params: [] + ) } - + // MARK: - section_two func logScreenS2DefOne() { - logScreen(name: "s2 def one", path: "s2_def_one/") + logScreen( + name: "s2 def one", + path: "s2_def_one/" + ) } } @@ -286,20 +392,20 @@ final class AnalyticsGeneratorTests: XCTestCase { // Given let sectionOne = AnalyticsCategory(id: "section_one") sectionOne.definitions = [ - getAnalyticsDefinition(id: "s1_def_one", path: "s1_def_one/", name: "s1 def one", type: AnalyticsDefinition.TagType.screen, tags: ["ios", "iosonly"]), - getAnalyticsDefinition(id: "s1_def_two", path: "s1_def_two/", name: "s1 def two", type: AnalyticsDefinition.TagType.event, tags: ["ios", "iosonly"]), + 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: AnalyticsDefinition.TagType.screen, tags: ["ios","iosonly"]), - getAnalyticsDefinition(id: "s2_def_two", path: "s2_def_two/", name: "s2 def two", type: AnalyticsDefinition.TagType.event, tags: ["droid","droidonly"]), + 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: AnalyticsDefinition.TagType.screen, tags: ["droid","droidonly"]), - getAnalyticsDefinition(id: "s3_def_two", path: "s3_def_two/", name: "s3 def two", type: AnalyticsDefinition.TagType.event, tags: ["droid","droidonly"]), + 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 @@ -312,7 +418,6 @@ final class AnalyticsGeneratorTests: XCTestCase { let expect = """ // Generated by ResgenSwift.Analytics 1.2 - import UIKit import MatomoTracker import Firebase @@ -320,7 +425,12 @@ final class AnalyticsGeneratorTests: XCTestCase { protocol AnalyticsManagerProtocol { func logScreen(name: String, path: String) - func logEvent(name: String) + func logEvent( + name: String, + action: String, + category: String, + params: [String: Any]? + ) } // MARK: - Matomo @@ -336,7 +446,10 @@ final class AnalyticsGeneratorTests: XCTestCase { 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)!) + tracker = MatomoTracker( + siteId: siteId, + baseURL: URL(string: url)! + ) #if DEBUG tracker.dispatchInterval = 5 @@ -355,6 +468,7 @@ final class AnalyticsGeneratorTests: XCTestCase { 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], @@ -362,11 +476,17 @@ final class AnalyticsGeneratorTests: XCTestCase { ) } - func logEvent(name: String) { + func logEvent( + name: String, + action: String, + category: String, + params: [String: Any]? + ) { guard !tracker.isOptedOut else { return } + tracker.track( - eventWithCategory: "category", - action: "action", + eventWithCategory: category, + action: action, name: name, number: nil, url: nil @@ -378,15 +498,37 @@ final class AnalyticsGeneratorTests: XCTestCase { class FirebaseAnalyticsManager: AnalyticsManagerProtocol { func logScreen(name: String, path: String) { - Analytics.logEvent(AnalyticsEventScreenView, parameters: [AnalyticsParameterScreenName: name]) + var parameters = [ + AnalyticsParameterScreenName: name + ] + + Analytics.logEvent( + AnalyticsEventScreenView, + parameters: parameters + ) } - func logEvent(name: String) { - var parameters = [ - AnalyticsParameterValue: name + func logEvent( + name: String, + action: String, + category: String, + params: [String: Any]? + ) { + var parameters: [String:Any] = [ + "action": action, + "category": category, ] + + if let supplementaryParameters = params { + parameters.merge(supplementaryParameters) { (origin, new) -> Any in + return origin + } + } - Analytics.logEvent(AnalyticsEventSelectContent, parameters: parameters) + Analytics.logEvent( + name, + parameters: parameters + ) } } @@ -403,41 +545,71 @@ final class AnalyticsGeneratorTests: XCTestCase { // MARK: - Methods - func setAnalyticsEnabled(_ enable: Bool) { isEnabled = enable } + func setAnalyticsEnabled(_ enable: Bool) { + isEnabled = enable + } func configure(siteId: String, url: String) { - managers.append(MatomoAnalyticsManager(siteId: siteId, url: url)) + managers.append( + MatomoAnalyticsManager( + siteId: siteId, + url: url + ) + ) managers.append(FirebaseAnalyticsManager()) } private func logScreen(name: String, path: String) { guard isEnabled else { return } + managers.forEach { manager in manager.logScreen(name: name, path: path) } } - private func logEvent(name: String) { + private func logEvent( + name: String, + action: String, + category: String, + params: [String: Any]? + ) { guard isEnabled else { return } + managers.forEach { manager in - manager.logEvent(name: name) + manager.logEvent( + name: name, + action: action, + category: category, + params: params + ) } } // MARK: - section_one func logScreenS1DefOne() { - logScreen(name: "s1 def one", path: "s1_def_one/") + logScreen( + name: "s1 def one", + path: "s1_def_one/" + ) } func logEventS1DefTwo() { - logEvent(name: "s1 def two") + logEvent( + name: "s1 def two", + action: "test", + category: "test", + params: [] + ) } - + // MARK: - section_two func logScreenS2DefOne() { - logScreen(name: "s2 def one", path: "s2_def_one/") + logScreen( + name: "s2 def one", + path: "s2_def_one/" + ) } } -- 2.39.5 From 3b90387e104c91f0739e702f0ee5752ab20f7eb9 Mon Sep 17 00:00:00 2001 From: Loris Perret Date: Fri, 8 Dec 2023 15:03:28 +0100 Subject: [PATCH 12/21] fix: Import + empty parameters --- .../ResgenSwift/Analytics/Generator/AnalyticsGenerator.swift | 2 +- Sources/ResgenSwift/Analytics/Model/AnalyticsDefinition.swift | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Sources/ResgenSwift/Analytics/Generator/AnalyticsGenerator.swift b/Sources/ResgenSwift/Analytics/Generator/AnalyticsGenerator.swift index 3da67c4..93fc1e8 100644 --- a/Sources/ResgenSwift/Analytics/Generator/AnalyticsGenerator.swift +++ b/Sources/ResgenSwift/Analytics/Generator/AnalyticsGenerator.swift @@ -95,7 +95,7 @@ class AnalyticsGenerator { result.append("import MatomoTracker") } if targets.contains(Analytics.TargetType.firebase) { - result.append("import Firebase") + result.append("import FirebaseAnalytics") } return result.joined(separator: "\n") diff --git a/Sources/ResgenSwift/Analytics/Model/AnalyticsDefinition.swift b/Sources/ResgenSwift/Analytics/Model/AnalyticsDefinition.swift index e0bdbe1..9c7cadb 100644 --- a/Sources/ResgenSwift/Analytics/Model/AnalyticsDefinition.swift +++ b/Sources/ResgenSwift/Analytics/Model/AnalyticsDefinition.swift @@ -103,10 +103,12 @@ class AnalyticsDefinition { \(params.joined(separator: ",\n\t\t\t\t")) ] """ - } else { + } else if params.count == 1 { result = """ [\(params.joined(separator: ", "))] """ + } else { + result = "[:]" } if type == .screen { -- 2.39.5 From 1f2933950bf14d0296d2bf17981222b54e7ad87d Mon Sep 17 00:00:00 2001 From: Loris Perret Date: Fri, 8 Dec 2023 15:16:50 +0100 Subject: [PATCH 13/21] Add \n at the end of R.swift --- .../Generate/Generator/ArchitectureGenerator.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Sources/ResgenSwift/Generate/Generator/ArchitectureGenerator.swift b/Sources/ResgenSwift/Generate/Generator/ArchitectureGenerator.swift index ba1413c..df81bf8 100644 --- a/Sources/ResgenSwift/Generate/Generator/ArchitectureGenerator.swift +++ b/Sources/ResgenSwift/Generate/Generator/ArchitectureGenerator.swift @@ -11,12 +11,14 @@ import Foundation struct ArchitectureGenerator { static func writeArchitecture(_ architecture: ConfigurationArchitecture, projectDirectory: String) { // Create extension content - let architectureContent = [ + var architectureContent = [ "// Generated by ResgenSwift.\(Generate.toolName) \(ResgenSwiftVersion)", architecture.getClass() ] .joined(separator: "\n\n") + architectureContent += "\n" + let filename = "\(architecture.classname).swift" guard let filePath = architecture.path?.prependIfRelativePath(projectDirectory) else { let error = GenerateError.writeFile(filename, "Path of file is not defined.") -- 2.39.5 From 6f8e3b66642dd2164ba65b6d805392b0299a8b90 Mon Sep 17 00:00:00 2001 From: Loris Perret Date: Mon, 11 Dec 2023 10:09:24 +0100 Subject: [PATCH 14/21] Add error handling --- .../Generated/Analytics+GenAllScript.swift | 4 +-- Sources/ResgenSwift/Analytics/Analytics.swift | 26 +++++++++++++++ .../Analytics/AnalyticsError.swift | 32 +++++++++++++++++++ .../Generator/AnalyticsGenerator.swift | 4 +-- .../Parser/AnalyticsFileParser.swift | 22 ++++++------- .../ResgenSwift/Generate/GenerateError.swift | 11 ------- 6 files changed, 73 insertions(+), 26 deletions(-) create mode 100644 Sources/ResgenSwift/Analytics/AnalyticsError.swift diff --git a/SampleFiles/Tags/Generated/Analytics+GenAllScript.swift b/SampleFiles/Tags/Generated/Analytics+GenAllScript.swift index 23b104a..95f5601 100644 --- a/SampleFiles/Tags/Generated/Analytics+GenAllScript.swift +++ b/SampleFiles/Tags/Generated/Analytics+GenAllScript.swift @@ -1,7 +1,7 @@ // Generated by ResgenSwift.Analytics 1.2 import MatomoTracker -import Firebase +import FirebaseAnalytics // MARK: - Protocol @@ -181,7 +181,7 @@ class AnalyticsManager { name: "s1 def two", action: "test", category: "test", - params: [] + params: [:] ) } diff --git a/Sources/ResgenSwift/Analytics/Analytics.swift b/Sources/ResgenSwift/Analytics/Analytics.swift index dd9a61c..ef42c9c 100644 --- a/Sources/ResgenSwift/Analytics/Analytics.swift +++ b/Sources/ResgenSwift/Analytics/Analytics.swift @@ -35,6 +35,9 @@ struct Analytics: ParsableCommand { print("[\(Self.toolName)] Will use inputFile \(options.inputFile) to generate analytics for target: \(options.target)") print("[\(Self.toolName)] Will generate analytics") + // Check requirements + guard checkRequirements() else { return } + // Parse input file let sections = AnalyticsFileParser.parse(options.inputFile, target: options.target) @@ -48,6 +51,29 @@ struct Analytics: ParsableCommand { print("[\(Self.toolName)] Analytics generated") } + + // MARK: - Requirements + + private func checkRequirements() -> Bool { + let fileManager = FileManager() + + // Input file + guard fileManager.fileExists(atPath: options.inputFile) else { + let error = AnalyticsError.fileNotExists(options.inputFile) + print(error.description) + Analytics.exit(withError: error) + } + + // Check if needed to regenerate + guard GeneratorChecker.shouldGenerate(force: options.forceGeneration, + inputFilePath: options.inputFile, + extensionFilePath: options.extensionFilePath) else { + print("[\(Self.toolName)] Analytics are already up to date :) ") + return false + } + + return true + } } extension Analytics { diff --git a/Sources/ResgenSwift/Analytics/AnalyticsError.swift b/Sources/ResgenSwift/Analytics/AnalyticsError.swift new file mode 100644 index 0000000..9674522 --- /dev/null +++ b/Sources/ResgenSwift/Analytics/AnalyticsError.swift @@ -0,0 +1,32 @@ +// +// AnalyticsError.swift +// +// +// Created by Loris Perret on 11/12/2023. +// + +import Foundation + +enum AnalyticsError: Error { + case fileNotExists(String) + case missingElement(String) + case invalidParameter(String) + + case writeFile(String, String) + + var description: String { + switch self { + case .fileNotExists(let filename): + return "error: [\(Analytics.toolName)] File \(filename) does not exists " + + case .missingElement(let element): + return "error: [\(Analytics.toolName)] Missing \(element) for Matomo" + + case .invalidParameter(let reason): + return "error: [\(Analytics.toolName)] Invalid parameter \(reason)" + + case .writeFile(let subErrorDescription, let filename): + return "error: [\(Analytics.toolName)] An error occured while writing content to \(filename): \(subErrorDescription)" + } + } +} diff --git a/Sources/ResgenSwift/Analytics/Generator/AnalyticsGenerator.swift b/Sources/ResgenSwift/Analytics/Generator/AnalyticsGenerator.swift index 93fc1e8..cd7adb3 100644 --- a/Sources/ResgenSwift/Analytics/Generator/AnalyticsGenerator.swift +++ b/Sources/ResgenSwift/Analytics/Generator/AnalyticsGenerator.swift @@ -33,9 +33,9 @@ class AnalyticsGenerator { do { try extensionFileContent.write(to: extensionFilePathURL, atomically: false, encoding: .utf8) } catch (let error) { - let error = StringiumError.writeFile(extensionFilePath, error.localizedDescription) + let error = AnalyticsError.writeFile(extensionFilePath, error.localizedDescription) print(error.description) - Stringium.exit(withError: error) + Analytics.exit(withError: error) } } diff --git a/Sources/ResgenSwift/Analytics/Parser/AnalyticsFileParser.swift b/Sources/ResgenSwift/Analytics/Parser/AnalyticsFileParser.swift index 7b57d17..0ad912e 100644 --- a/Sources/ResgenSwift/Analytics/Parser/AnalyticsFileParser.swift +++ b/Sources/ResgenSwift/Analytics/Parser/AnalyticsFileParser.swift @@ -14,15 +14,15 @@ class AnalyticsFileParser { private static func parseYaml() -> AnalyticsFile { guard let data = FileManager().contents(atPath: inputFile) else { - let error = GenerateError.fileNotExists(inputFile) - Generate.exit(withError: error) + let error = AnalyticsError.fileNotExists(inputFile) + Analytics.exit(withError: error) } do { let tagFile = try YAMLDecoder().decode(AnalyticsFile.self, from: data) return tagFile } catch let error { - Generate.exit(withError: error) + Analytics.exit(withError: error) } } @@ -40,8 +40,8 @@ class AnalyticsFileParser { type == "Double" || type == "Bool" else { - let error = GenerateError.invalidParameter("type of \(value.name)") - Generate.exit(withError: error) + let error = AnalyticsError.invalidParameter("type of \(value.name)") + Analytics.exit(withError: error) } let parameter: AnalyticsParameter = AnalyticsParameter(name: value.name, type: type) @@ -95,8 +95,8 @@ class AnalyticsFileParser { // Path guard let path = screen.path else { - let error = GenerateError.missingElement("screen path") - Generate.exit(withError: error) + let error = AnalyticsError.missingElement("screen path") + Analytics.exit(withError: error) } definition.path = path @@ -125,8 +125,8 @@ class AnalyticsFileParser { // Category guard let category = event.category else { - let error = GenerateError.missingElement("event category") - Generate.exit(withError: error) + let error = AnalyticsError.missingElement("event category") + Analytics.exit(withError: error) } definition.category = category @@ -134,8 +134,8 @@ class AnalyticsFileParser { // Action guard let action = event.action else { - let error = GenerateError.missingElement("event action") - Generate.exit(withError: error) + let error = AnalyticsError.missingElement("event action") + Analytics.exit(withError: error) } definition.action = action diff --git a/Sources/ResgenSwift/Generate/GenerateError.swift b/Sources/ResgenSwift/Generate/GenerateError.swift index e6bab8c..2ebc045 100644 --- a/Sources/ResgenSwift/Generate/GenerateError.swift +++ b/Sources/ResgenSwift/Generate/GenerateError.swift @@ -13,11 +13,6 @@ enum GenerateError: Error { case commandError([String], String) case writeFile(String, String) - // Analytics - - case missingElement(String) - case invalidParameter(String) - var description: String { switch self { case .fileNotExists(let filename): @@ -34,12 +29,6 @@ enum GenerateError: Error { case .writeFile(let filename, let info): return "error: [\(Generate.toolName)] An error occured while writing file in \(filename): \(info)" - - case .missingElement(let element): - return "error: [\(Generate.toolName)] Missing \(element) for Matomo" - - case .invalidParameter(let reason): - return "error: [\(Generate.toolName)] Invalid parameter \(reason)" } } } -- 2.39.5 From ee5055efa5578dc56e3c98bfa4f8e9afe546200c Mon Sep 17 00:00:00 2001 From: Thibaut Schmitt Date: Fri, 8 Dec 2023 17:37:21 +0100 Subject: [PATCH 15/21] Retours sur la structure du code --- Sources/ResgenSwift/Analytics/Analytics.swift | 23 ++-- .../Generator/AnalyticsGenerator.swift | 30 ++--- .../Generator/FirebaseGenerator.swift | 23 ++-- .../Analytics/Generator/MatomoGenerator.swift | 27 +++-- .../Analytics/Model/AnalyticsCategory.swift | 4 + .../Analytics/Model/AnalyticsDefinition.swift | 35 ++---- .../Analytics/Model/AnalyticsFile.swift | 6 +- .../Analytics/Model/AnalyticsParameter.swift | 2 + .../ResgenSwift/Analytics/Model/TagType.swift | 16 +++ .../Analytics/Model/TargetType.swift | 22 ++++ .../Parser/AnalyticsFileParser.swift | 112 +++++++++--------- Sources/ToolCore/StringExtensions.swift | 17 ++- .../Analytics/DiffString.swift | 3 +- 13 files changed, 177 insertions(+), 143 deletions(-) create mode 100644 Sources/ResgenSwift/Analytics/Model/TagType.swift create mode 100644 Sources/ResgenSwift/Analytics/Model/TargetType.swift diff --git a/Sources/ResgenSwift/Analytics/Analytics.swift b/Sources/ResgenSwift/Analytics/Analytics.swift index ef42c9c..d245e39 100644 --- a/Sources/ResgenSwift/Analytics/Analytics.swift +++ b/Sources/ResgenSwift/Analytics/Analytics.swift @@ -33,6 +33,10 @@ struct Analytics: ParsableCommand { mutating func run() { print("[\(Self.toolName)] Starting analytics generation") print("[\(Self.toolName)] Will use inputFile \(options.inputFile) to generate analytics for target: \(options.target)") + + // Check requirements + guard checkRequirements() else { return } + print("[\(Self.toolName)] Will generate analytics") // Check requirements @@ -76,18 +80,11 @@ struct Analytics: ParsableCommand { } } -extension Analytics { - enum TargetType: CaseIterable { - case matomo - case firebase - - var value: String { - switch self { - case .matomo: - "matomo" - case .firebase: - "firebase" - } - } + // MARK: - Requirements + + private func checkRequirements() -> Bool { + // Check les requirements et gestion des erreurs + // Il faut que toutes les erreurs générées par Analytics soit des AnalyticsError + true } } diff --git a/Sources/ResgenSwift/Analytics/Generator/AnalyticsGenerator.swift b/Sources/ResgenSwift/Analytics/Generator/AnalyticsGenerator.swift index cd7adb3..a4a9e1f 100644 --- a/Sources/ResgenSwift/Analytics/Generator/AnalyticsGenerator.swift +++ b/Sources/ResgenSwift/Analytics/Generator/AnalyticsGenerator.swift @@ -10,13 +10,13 @@ import ToolCore import CoreVideo class AnalyticsGenerator { - static var targets: [Analytics.TargetType] = [] - + static var targets: [TrackerType] = [] + 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: " ") - Analytics.TargetType.allCases.forEach { enumTarget in + TrackerType.allCases.forEach { enumTarget in if targetsString.contains(enumTarget.value) { targets.append(enumTarget) } @@ -58,7 +58,7 @@ class AnalyticsGenerator { \(Self.getImport()) - \(Self.getAnalytics()) + \(Self.getAnalyticsProtocol()) // MARK: - Manager class AnalyticsManager { @@ -91,10 +91,10 @@ class AnalyticsGenerator { private static func getImport() -> String { var result: [String] = [] - if targets.contains(Analytics.TargetType.matomo) { + if targets.contains(TrackerType.matomo) { result.append("import MatomoTracker") } - if targets.contains(Analytics.TargetType.firebase) { + if targets.contains(TrackerType.firebase) { result.append("import FirebaseAnalytics") } @@ -136,13 +136,13 @@ class AnalyticsGenerator { var content: [String] = [] let footer = " }" - if targets.contains(Analytics.TargetType.matomo) { + if targets.contains(TrackerType.matomo) { header = "func configure(siteId: String, url: String) {" - } else if targets.contains(Analytics.TargetType.firebase) { + } else if targets.contains(TrackerType.firebase) { header = "func configure() {" } - if targets.contains(Analytics.TargetType.matomo) { + if targets.contains(TrackerType.matomo) { content.append(""" managers.append( MatomoAnalyticsManager( @@ -152,7 +152,7 @@ class AnalyticsGenerator { ) """) } - if targets.contains(Analytics.TargetType.firebase) { + if targets.contains(TrackerType.firebase) { content.append(" managers.append(FirebaseAnalyticsManager())") } @@ -164,7 +164,7 @@ class AnalyticsGenerator { .joined(separator: "\n") } - private static func getAnalytics() -> String { + private static func getAnalyticsProtocol() -> String { let proto = """ // MARK: - Protocol @@ -182,12 +182,12 @@ class AnalyticsGenerator { var result: [String] = [proto] - if targets.contains(Analytics.TargetType.matomo) { - result.append(MatomoGenerator.service.content) + if targets.contains(TrackerType.matomo) { + result.append(MatomoGenerator.service) } - if targets.contains(Analytics.TargetType.firebase) { - result.append(FirebaseGenerator.service.content) + if targets.contains(TrackerType.firebase) { + result.append(FirebaseGenerator.service) } return result.joined(separator: "\n") diff --git a/Sources/ResgenSwift/Analytics/Generator/FirebaseGenerator.swift b/Sources/ResgenSwift/Analytics/Generator/FirebaseGenerator.swift index 4352b0e..95ab7f4 100644 --- a/Sources/ResgenSwift/Analytics/Generator/FirebaseGenerator.swift +++ b/Sources/ResgenSwift/Analytics/Generator/FirebaseGenerator.swift @@ -8,19 +8,20 @@ import Foundation enum FirebaseGenerator { - case service - - var content: String { + + static var service: String { [ - FirebaseGenerator.service.header, - FirebaseGenerator.service.logScreen, - FirebaseGenerator.service.logEvent, - FirebaseGenerator.service.footer + FirebaseGenerator.header, + FirebaseGenerator.logScreen, + FirebaseGenerator.logEvent, + FirebaseGenerator.footer ] .joined(separator: "\n") } - private var header: String { + // MARK: - Private vars + + private static var header: String { """ // MARK: - Firebase @@ -28,7 +29,7 @@ enum FirebaseGenerator { """ } - private var logScreen: String { + private static var logScreen: String { """ func logScreen(name: String, path: String) { var parameters = [ @@ -44,7 +45,7 @@ enum FirebaseGenerator { """ } - private var logEvent: String { + private static var logEvent: String { """ func logEvent( name: String, @@ -71,7 +72,7 @@ enum FirebaseGenerator { """ } - private var footer: String { + private static var footer: String { """ } diff --git a/Sources/ResgenSwift/Analytics/Generator/MatomoGenerator.swift b/Sources/ResgenSwift/Analytics/Generator/MatomoGenerator.swift index 739e8cb..740cf95 100644 --- a/Sources/ResgenSwift/Analytics/Generator/MatomoGenerator.swift +++ b/Sources/ResgenSwift/Analytics/Generator/MatomoGenerator.swift @@ -8,20 +8,21 @@ import Foundation enum MatomoGenerator { - case service - - var content: String { + + static var service: String { [ - MatomoGenerator.service.header, - MatomoGenerator.service.setup, - MatomoGenerator.service.logScreen, - MatomoGenerator.service.logEvent, - MatomoGenerator.service.footer + MatomoGenerator.header, + MatomoGenerator.setup, + MatomoGenerator.logScreen, + MatomoGenerator.logEvent, + MatomoGenerator.footer ] .joined(separator: "\n") } - private var header: String { + // MARK: - Private vars + + private static var header: String { """ // MARK: - Matomo @@ -34,7 +35,7 @@ enum MatomoGenerator { """ } - private var setup: String { + private static var setup: String { """ // MARK: - Init @@ -63,7 +64,7 @@ enum MatomoGenerator { """ } - private var logScreen: String { + private static var logScreen: String { """ func logScreen(name: String, path: String) { guard !tracker.isOptedOut else { return } @@ -79,7 +80,7 @@ enum MatomoGenerator { """ } - private var logEvent: String { + private static var logEvent: String { """ func logEvent( name: String, @@ -100,7 +101,7 @@ enum MatomoGenerator { """ } - private var footer: String { + private static var footer: String { """ } diff --git a/Sources/ResgenSwift/Analytics/Model/AnalyticsCategory.swift b/Sources/ResgenSwift/Analytics/Model/AnalyticsCategory.swift index 9cec892..9451d99 100644 --- a/Sources/ResgenSwift/Analytics/Model/AnalyticsCategory.swift +++ b/Sources/ResgenSwift/Analytics/Model/AnalyticsCategory.swift @@ -11,10 +11,14 @@ class AnalyticsCategory { let id: String // OnBoarding var definitions = [AnalyticsDefinition]() + // MARK: - Init + init(id: String) { self.id = id } + // MARK: - Methods + func hasOneOrMoreMatchingTags(tags: [String]) -> Bool { let allTags = definitions.flatMap { $0.tags } let allTagsSet = Set(allTags) diff --git a/Sources/ResgenSwift/Analytics/Model/AnalyticsDefinition.swift b/Sources/ResgenSwift/Analytics/Model/AnalyticsDefinition.swift index 9c7cadb..acef609 100644 --- a/Sources/ResgenSwift/Analytics/Model/AnalyticsDefinition.swift +++ b/Sources/ResgenSwift/Analytics/Model/AnalyticsDefinition.swift @@ -6,6 +6,7 @@ // import Foundation +import ToolCore class AnalyticsDefinition { let id: String @@ -18,12 +19,16 @@ class AnalyticsDefinition { var parameters: [AnalyticsParameter] = [] var type: TagType + // MARK: - Init + init(id: String, name: String, type: TagType) { self.id = id self.name = name self.type = type } + // MARK: - Methods + func hasOneOrMoreMatchingTags(inputTags: [String]) -> Bool { if Set(inputTags).intersection(Set(self.tags)).isEmpty { return false @@ -31,8 +36,8 @@ class AnalyticsDefinition { return true } - // MARK: - Methods - + // MARK: - Private Methods + private func getFuncName() -> String { var pascalCaseTitle: String = "" id.components(separatedBy: "_").forEach { word in @@ -96,7 +101,7 @@ class AnalyticsDefinition { supplementaryParams.forEach { param in params.append("\"\(param.name)\": \(param.name)") } - + if params.count > 1 { result = """ [ @@ -150,27 +155,3 @@ class AnalyticsDefinition { """ } } - -extension AnalyticsDefinition { - enum TagType { - case screen - case event - } -} - -extension String { - func replacingFirstOccurrence(of: String, with: String) -> Self { - if let range = self.range(of: of) { - let tmp = self.replacingOccurrences( - of: of, - with: with, - options: .literal, - range: range - ) - - return tmp - } - - return self - } -} diff --git a/Sources/ResgenSwift/Analytics/Model/AnalyticsFile.swift b/Sources/ResgenSwift/Analytics/Model/AnalyticsFile.swift index 5c4f690..d775768 100644 --- a/Sources/ResgenSwift/Analytics/Model/AnalyticsFile.swift +++ b/Sources/ResgenSwift/Analytics/Model/AnalyticsFile.swift @@ -17,9 +17,7 @@ struct AnalyticsCategoryDTO: Codable { var events: [AnalyticsDefinitionEventDTO]? } -protocol AnalyticsDefinitionDTO: Codable {} - -struct AnalyticsDefinitionScreenDTO: AnalyticsDefinitionDTO { +struct AnalyticsDefinitionScreenDTO: Codable { var id: String var name: String var tags: String @@ -29,7 +27,7 @@ struct AnalyticsDefinitionScreenDTO: AnalyticsDefinitionDTO { var path: String? } -struct AnalyticsDefinitionEventDTO: AnalyticsDefinitionDTO { +struct AnalyticsDefinitionEventDTO: Codable { var id: String var name: String var tags: String diff --git a/Sources/ResgenSwift/Analytics/Model/AnalyticsParameter.swift b/Sources/ResgenSwift/Analytics/Model/AnalyticsParameter.swift index e7f1079..9c659c3 100644 --- a/Sources/ResgenSwift/Analytics/Model/AnalyticsParameter.swift +++ b/Sources/ResgenSwift/Analytics/Model/AnalyticsParameter.swift @@ -12,6 +12,8 @@ class AnalyticsParameter { var type: String var replaceIn: [String] = [] + // MARK: - Init + init(name: String, type: String) { self.name = name self.type = type diff --git a/Sources/ResgenSwift/Analytics/Model/TagType.swift b/Sources/ResgenSwift/Analytics/Model/TagType.swift new file mode 100644 index 0000000..62d3d28 --- /dev/null +++ b/Sources/ResgenSwift/Analytics/Model/TagType.swift @@ -0,0 +1,16 @@ +// +// TagType.swift +// +// +// Created by Thibaut Schmitt on 08/12/2023. +// + +import Foundation + +extension AnalyticsDefinition { + + enum TagType { + case screen + case event + } +} diff --git a/Sources/ResgenSwift/Analytics/Model/TargetType.swift b/Sources/ResgenSwift/Analytics/Model/TargetType.swift new file mode 100644 index 0000000..c9b5649 --- /dev/null +++ b/Sources/ResgenSwift/Analytics/Model/TargetType.swift @@ -0,0 +1,22 @@ +// +// TargetType.swift +// +// +// Created by Thibaut Schmitt on 08/12/2023. +// + +import Foundation + +enum TrackerType: CaseIterable { + case matomo + case firebase + + var value: String { + switch self { + case .matomo: + "matomo" + case .firebase: + "firebase" + } + } +} diff --git a/Sources/ResgenSwift/Analytics/Parser/AnalyticsFileParser.swift b/Sources/ResgenSwift/Analytics/Parser/AnalyticsFileParser.swift index 0ad912e..9a5b429 100644 --- a/Sources/ResgenSwift/Analytics/Parser/AnalyticsFileParser.swift +++ b/Sources/ResgenSwift/Analytics/Parser/AnalyticsFileParser.swift @@ -25,11 +25,9 @@ class AnalyticsFileParser { Analytics.exit(withError: error) } } - - private static func getParameters(fromData data: [AnalyticsParameterDTO]) -> [AnalyticsParameter] { - var parameters: [AnalyticsParameter] = [] - - data.forEach { value in + + private static func getParameters(from parameters: [AnalyticsParameterDTO]) -> [AnalyticsParameter] { + parameters.map { dtoParameter in // Type let type = value.type.uppercasedFirst() @@ -43,17 +41,18 @@ class AnalyticsFileParser { let error = AnalyticsError.invalidParameter("type of \(value.name)") Analytics.exit(withError: error) } - - let parameter: AnalyticsParameter = AnalyticsParameter(name: value.name, type: type) - - if let replaceIn = value.replaceIn { + + let parameter = AnalyticsParameter( + name: dtoParameter.name, + type: type + ) + + if let replaceIn = dtoParameter.replaceIn { parameter.replaceIn = replaceIn.components(separatedBy: ",") } - - parameters.append(parameter) + + return parameter } - - return parameters } private static func getTagDefinition( @@ -64,24 +63,24 @@ class AnalyticsFileParser { comments: String?, parameters: [AnalyticsParameterDTO]? ) -> AnalyticsDefinition { - let definition: AnalyticsDefinition = AnalyticsDefinition(id: id, name: name, type: type) - definition.tags = tags.components(separatedBy: ",") - + let definition = AnalyticsDefinition(id: id, name: name, type: type) + definition.tags = tags + .components(separatedBy: ",") + .map { $0.removeLeadingTrailingWhitespace() } + if let comments = comments { definition.comments = comments } if let parameters = parameters { - definition.parameters = Self.getParameters(fromData: parameters) + definition.parameters = Self.getParameters(from: parameters) } return definition } - private static func getTagDefinitionScreen(fromData screens: [AnalyticsDefinitionScreenDTO]) -> [AnalyticsDefinition] { - var definitions: [AnalyticsDefinition] = [] - - for screen in screens { + private static func getTagDefinitionScreen(from screens: [AnalyticsDefinitionScreenDTO]) -> [AnalyticsDefinition] { + screens.map { screen in let definition: AnalyticsDefinition = Self.getTagDefinition( id: screen.id, name: screen.name, @@ -90,10 +89,10 @@ class AnalyticsFileParser { comments: screen.comments, parameters: screen.parameters ) - + if target.contains(Analytics.TargetType.matomo.value) { // Path - + guard let path = screen.path else { let error = AnalyticsError.missingElement("screen path") Analytics.exit(withError: error) @@ -101,17 +100,13 @@ class AnalyticsFileParser { definition.path = path } - - definitions.append(definition) + + return definition } - - return definitions } - private static func getTagDefinitionEvent(fromData events: [AnalyticsDefinitionEventDTO]) -> [AnalyticsDefinition] { - var definitions: [AnalyticsDefinition] = [] - - for event in events { + private static func getTagDefinitionEvent(from events: [AnalyticsDefinitionEventDTO]) -> [AnalyticsDefinition] { + events.map { event in let definition: AnalyticsDefinition = Self.getTagDefinition( id: event.id, name: event.name, @@ -120,54 +115,57 @@ class AnalyticsFileParser { comments: event.comments, parameters: event.parameters ) - + if target.contains(Analytics.TargetType.matomo.value) { // Category - guard let category = event.category else { let error = AnalyticsError.missingElement("event category") Analytics.exit(withError: error) } - + definition.category = category - + // Action - guard let action = event.action else { let error = AnalyticsError.missingElement("event action") Analytics.exit(withError: error) } - + definition.action = action } - - definitions.append(definition) + + return definition } - - return definitions } static func parse(_ inputFile: String, target: String) -> [AnalyticsCategory] { self.inputFile = inputFile self.target = target - let tagFile: AnalyticsFile = Self.parseYaml() - var sections: [AnalyticsCategory] = [] + let tagFile = Self.parseYaml() - tagFile.categories.forEach { categorie in - let section: AnalyticsCategory = AnalyticsCategory(id: categorie.id) - - if let screens = categorie.screens { - section.definitions.append(contentsOf: Self.getTagDefinitionScreen(fromData: screens)) + 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 } - - if let events = categorie.events { - section.definitions.append(contentsOf: Self.getTagDefinitionEvent(fromData: events)) - } - - sections.append(section) - } - - return sections } } diff --git a/Sources/ToolCore/StringExtensions.swift b/Sources/ToolCore/StringExtensions.swift index 52ea186..401f7a7 100644 --- a/Sources/ToolCore/StringExtensions.swift +++ b/Sources/ToolCore/StringExtensions.swift @@ -63,7 +63,7 @@ public extension String { replacingOccurrences(of: "~", with: "\(FileManager.default.homeDirectoryForCurrentUser.relativePath)") } - func colorComponent() -> (alpha: String, red: String, green: String, blue: String) { + func colorComponent() -> (alpha: String, red: String, green: String, blue: String) { var alpha: String = "FF" var red: String var green: String @@ -89,4 +89,19 @@ public extension String { func uppercasedFirst() -> String { prefix(1).uppercased() + dropFirst() } + + func replacingFirstOccurrence(of: String, with: String) -> Self { + if let range = self.range(of: of) { + let tmp = self.replacingOccurrences( + of: of, + with: with, + options: .literal, + range: range + ) + + return tmp + } + + return self + } } diff --git a/Tests/ResgenSwiftTests/Analytics/DiffString.swift b/Tests/ResgenSwiftTests/Analytics/DiffString.swift index 60f41cf..182eb23 100644 --- a/Tests/ResgenSwiftTests/Analytics/DiffString.swift +++ b/Tests/ResgenSwiftTests/Analytics/DiffString.swift @@ -1,5 +1,5 @@ // -// File.swift +// DiffString.swift // // // Created by Loris Perret on 06/12/2023. @@ -7,7 +7,6 @@ import Foundation - /// Find first differing character between two strings /// /// :param: s1 First String -- 2.39.5 From f1b62d83c495c10d4bff26c4efb17df798465c7b Mon Sep 17 00:00:00 2001 From: Thibaut Schmitt Date: Mon, 11 Dec 2023 10:29:19 +0100 Subject: [PATCH 16/21] Add missing print error --- .../ResgenSwift/Analytics/Parser/AnalyticsFileParser.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Sources/ResgenSwift/Analytics/Parser/AnalyticsFileParser.swift b/Sources/ResgenSwift/Analytics/Parser/AnalyticsFileParser.swift index 9a5b429..f4e18ce 100644 --- a/Sources/ResgenSwift/Analytics/Parser/AnalyticsFileParser.swift +++ b/Sources/ResgenSwift/Analytics/Parser/AnalyticsFileParser.swift @@ -15,6 +15,7 @@ class AnalyticsFileParser { private static func parseYaml() -> AnalyticsFile { guard let data = FileManager().contents(atPath: inputFile) else { let error = AnalyticsError.fileNotExists(inputFile) + print(error.description) Analytics.exit(withError: error) } @@ -22,6 +23,7 @@ class AnalyticsFileParser { let tagFile = try YAMLDecoder().decode(AnalyticsFile.self, from: data) return tagFile } catch let error { + print(error.description) Analytics.exit(withError: error) } } @@ -39,6 +41,7 @@ class AnalyticsFileParser { type == "Bool" else { let error = AnalyticsError.invalidParameter("type of \(value.name)") + print(error.description) Analytics.exit(withError: error) } @@ -95,6 +98,7 @@ class AnalyticsFileParser { guard let path = screen.path else { let error = AnalyticsError.missingElement("screen path") + print(error.description) Analytics.exit(withError: error) } @@ -120,6 +124,7 @@ class AnalyticsFileParser { // Category guard let category = event.category else { let error = AnalyticsError.missingElement("event category") + print(error.description) Analytics.exit(withError: error) } @@ -128,6 +133,7 @@ class AnalyticsFileParser { // Action guard let action = event.action else { let error = AnalyticsError.missingElement("event action") + print(error.description) Analytics.exit(withError: error) } -- 2.39.5 From f6c49bf626dae431b076866a46a7e4565c3b5cbd Mon Sep 17 00:00:00 2001 From: Thibaut Schmitt Date: Mon, 11 Dec 2023 11:19:19 +0100 Subject: [PATCH 17/21] Add parse error --- Sources/ResgenSwift/Analytics/Analytics.swift | 17 +++++++---------- .../ResgenSwift/Analytics/AnalyticsError.swift | 11 +++++++++-- .../Analytics/Model/TargetType.swift | 7 +++++++ .../Analytics/Parser/AnalyticsFileParser.swift | 13 +++++++------ .../Analytics/AnalyticsGeneratorTests.swift | 6 +++--- 5 files changed, 33 insertions(+), 21 deletions(-) diff --git a/Sources/ResgenSwift/Analytics/Analytics.swift b/Sources/ResgenSwift/Analytics/Analytics.swift index d245e39..e9c0e13 100644 --- a/Sources/ResgenSwift/Analytics/Analytics.swift +++ b/Sources/ResgenSwift/Analytics/Analytics.swift @@ -67,7 +67,13 @@ struct Analytics: ParsableCommand { print(error.description) Analytics.exit(withError: error) } - + + guard TrackerType.hasValidTarget(in: options.target) else { + let error = AnalyticsError.noValidTracker(options.target) + print(error.description) + Analytics.exit(withError: error) + } + // Check if needed to regenerate guard GeneratorChecker.shouldGenerate(force: options.forceGeneration, inputFilePath: options.inputFile, @@ -79,12 +85,3 @@ struct Analytics: ParsableCommand { return true } } - - // MARK: - Requirements - - private func checkRequirements() -> Bool { - // Check les requirements et gestion des erreurs - // Il faut que toutes les erreurs générées par Analytics soit des AnalyticsError - true - } -} diff --git a/Sources/ResgenSwift/Analytics/AnalyticsError.swift b/Sources/ResgenSwift/Analytics/AnalyticsError.swift index 9674522..b32198d 100644 --- a/Sources/ResgenSwift/Analytics/AnalyticsError.swift +++ b/Sources/ResgenSwift/Analytics/AnalyticsError.swift @@ -8,16 +8,20 @@ import Foundation enum AnalyticsError: Error { + case noValidTracker(String) case fileNotExists(String) case missingElement(String) case invalidParameter(String) - + case parseFailed(String) case writeFile(String, String) var description: String { switch self { + case .noValidTracker(let inputTargets): + return "error: [\(Analytics.toolName)] '\(inputTargets)' ne contient aucun tracker valid" + case .fileNotExists(let filename): - return "error: [\(Analytics.toolName)] File \(filename) does not exists " + return "error: [\(Analytics.toolName)] File \(filename) does not exists" case .missingElement(let element): return "error: [\(Analytics.toolName)] Missing \(element) for Matomo" @@ -25,6 +29,9 @@ enum AnalyticsError: Error { case .invalidParameter(let reason): return "error: [\(Analytics.toolName)] Invalid parameter \(reason)" + case .parseFailed(let baseError): + return "error: [\(Analytics.toolName)] Parse input file failed: \(baseError)" + case .writeFile(let subErrorDescription, let filename): return "error: [\(Analytics.toolName)] An error occured while writing content to \(filename): \(subErrorDescription)" } diff --git a/Sources/ResgenSwift/Analytics/Model/TargetType.swift b/Sources/ResgenSwift/Analytics/Model/TargetType.swift index c9b5649..412d5c3 100644 --- a/Sources/ResgenSwift/Analytics/Model/TargetType.swift +++ b/Sources/ResgenSwift/Analytics/Model/TargetType.swift @@ -19,4 +19,11 @@ enum TrackerType: CaseIterable { "firebase" } } + + static func hasValidTarget(in targets: String) -> Bool { + for tracker in Self.allCases where targets.contains(tracker.value) { + return true + } + return false + } } diff --git a/Sources/ResgenSwift/Analytics/Parser/AnalyticsFileParser.swift b/Sources/ResgenSwift/Analytics/Parser/AnalyticsFileParser.swift index f4e18ce..1137f8e 100644 --- a/Sources/ResgenSwift/Analytics/Parser/AnalyticsFileParser.swift +++ b/Sources/ResgenSwift/Analytics/Parser/AnalyticsFileParser.swift @@ -22,7 +22,8 @@ class AnalyticsFileParser { do { let tagFile = try YAMLDecoder().decode(AnalyticsFile.self, from: data) return tagFile - } catch let error { + } catch { + let error = AnalyticsError.parseFailed(error.localizedDescription) print(error.description) Analytics.exit(withError: error) } @@ -32,15 +33,15 @@ class AnalyticsFileParser { parameters.map { dtoParameter in // Type - let type = value.type.uppercasedFirst() - + let type = dtoParameter.type.uppercasedFirst() + guard type == "String" || type == "Int" || type == "Double" || type == "Bool" else { - let error = AnalyticsError.invalidParameter("type of \(value.name)") + let error = AnalyticsError.invalidParameter("type of \(dtoParameter.name)") print(error.description) Analytics.exit(withError: error) } @@ -93,7 +94,7 @@ class AnalyticsFileParser { parameters: screen.parameters ) - if target.contains(Analytics.TargetType.matomo.value) { + if target.contains(TrackerType.matomo.value) { // Path guard let path = screen.path else { @@ -120,7 +121,7 @@ class AnalyticsFileParser { parameters: event.parameters ) - if target.contains(Analytics.TargetType.matomo.value) { + if target.contains(TrackerType.matomo.value) { // Category guard let category = event.category else { let error = AnalyticsError.missingElement("event category") diff --git a/Tests/ResgenSwiftTests/Analytics/AnalyticsGeneratorTests.swift b/Tests/ResgenSwiftTests/Analytics/AnalyticsGeneratorTests.swift index 88f9883..7abe69f 100644 --- a/Tests/ResgenSwiftTests/Analytics/AnalyticsGeneratorTests.swift +++ b/Tests/ResgenSwiftTests/Analytics/AnalyticsGeneratorTests.swift @@ -51,7 +51,7 @@ final class AnalyticsGeneratorTests: XCTestCase { ] // When - AnalyticsGenerator.targets = [Analytics.TargetType.firebase] + AnalyticsGenerator.targets = [TrackerType.firebase] let extensionContent = AnalyticsGenerator.getExtensionContent(sections: [sectionOne, sectionTwo, sectionThree], tags: ["ios", "iosonly"], staticVar: false, @@ -216,7 +216,7 @@ final class AnalyticsGeneratorTests: XCTestCase { ] // When - AnalyticsGenerator.targets = [Analytics.TargetType.matomo] + AnalyticsGenerator.targets = [TrackerType.matomo] let extensionContent = AnalyticsGenerator.getExtensionContent(sections: [sectionOne, sectionTwo, sectionThree], tags: ["ios", "iosonly"], staticVar: false, @@ -409,7 +409,7 @@ final class AnalyticsGeneratorTests: XCTestCase { ] // When - AnalyticsGenerator.targets = [Analytics.TargetType.matomo, Analytics.TargetType.firebase] + AnalyticsGenerator.targets = [TrackerType.matomo, TrackerType.firebase] let extensionContent = AnalyticsGenerator.getExtensionContent(sections: [sectionOne, sectionTwo, sectionThree], tags: ["ios", "iosonly"], staticVar: false, -- 2.39.5 From 1d58fd5510c0ca68fdcc82105537c1be52716e61 Mon Sep 17 00:00:00 2001 From: Loris Perret Date: Tue, 12 Dec 2023 16:58:25 +0100 Subject: [PATCH 18/21] Edit sample file --- .../Tags/Generated/Analytics+GenAllScript.swift | 13 ++++++++----- SampleFiles/Tags/sampleTags.yml | 15 ++++++++++++--- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/SampleFiles/Tags/Generated/Analytics+GenAllScript.swift b/SampleFiles/Tags/Generated/Analytics+GenAllScript.swift index 95f5601..1b5bd44 100644 --- a/SampleFiles/Tags/Generated/Analytics+GenAllScript.swift +++ b/SampleFiles/Tags/Generated/Analytics+GenAllScript.swift @@ -169,19 +169,22 @@ class AnalyticsManager { // MARK: - section_one - func logScreenS1DefOne() { + func logScreenS1DefOne(title: String) { logScreen( - name: "s1 def one", - path: "s1_def_one/" + name: "s1 def one \(title)", + path: "s1_def_one/\(title)" ) } - func logEventS1DefTwo() { + func logEventS1DefTwo(title: String, count: String) { logEvent( name: "s1 def two", action: "test", category: "test", - params: [:] + params: [ + "title": title, + "count": count + ] ) } diff --git a/SampleFiles/Tags/sampleTags.yml b/SampleFiles/Tags/sampleTags.yml index 310acb4..4c91dd0 100644 --- a/SampleFiles/Tags/sampleTags.yml +++ b/SampleFiles/Tags/sampleTags.yml @@ -3,9 +3,13 @@ categories: - id: section_one screens: - id: s1_def_one - name: s1 def one - path: s1_def_one/ - tags: ios + name: s1 def one _TITLE_ + path: s1_def_one/_TITLE_ + tags: ios,droid + parameters: + - name: title + type: String + replaceIn: name,path events: - id: s1_def_two @@ -13,6 +17,11 @@ categories: action: test category: test tags: ios + parameters: + - name: title + type: String + - name: count + type: String - id: section_two screens: -- 2.39.5 From 9b27f241973115c14668b9b65f0dbabedff92eac Mon Sep 17 00:00:00 2001 From: Loris Perret Date: Wed, 13 Dec 2023 10:20:53 +0100 Subject: [PATCH 19/21] docs: Add Analytics section in README --- README.md | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/README.md b/README.md index eaf249e..1fcd5af 100644 --- a/README.md +++ b/README.md @@ -133,6 +133,71 @@ swift run -c release ResgenSwift strings tags $FORCE_FLAG "./Tags/tags.txt" \ > ⚠️ If extension name is not set or is `Tags`, it will generate the following typaloas `typealias Tags = String`. + +## Analytics + +Analytics will generate all you need to analyze UX with Matomo or Firebase Analytics. Input files are formatted in YAML. This command will generate a manager for each target and an AnalyticsManager. This is this one you will need to use. And it will generate a method for all tags you have declared in the YAML file. If you want to use Matomo, you will need to use the `configure()` method of AnalyticsManager to set up the `siteId` and the `url` of the site. + +```sh +swift run -c release ResgenSwift strings tags $FORCE_FLAG "./Tags/tags.txt" \ + --target "matomo firebase" \ + --extension-output-path "./Analytics/Generated" \ + --extension-name "AppAnalytics" \ + --extension-suffix "GreatApp" \ + --static-members true +``` + + **Parameters** + +1. `-f`: force generation +2. Input tags file (must be YAML formatted) +3. `--target`: target with you will log UX +4. `--extension-output-path`: path where to generate generated extension +5. `--extension-name` *(optional)* : name of class to add the extension +6. `--extension-suffix` *(optional)* : additional text which is added to filename (ex: `AppAnalytics+GreatApp.swift`) +7. `--static-members` *(optional)*: generate static properties or not + +> ⚠️ If extension name is not set or is `Analytics`, it will generate the following typaloas `typealias Analytics = String`. + +### YAML + +``` + - id: s1_def_one + name: s1 def one _TITLE_ + path: s1_def_one/_TITLE_ + action: Tap + category: User + tags: ios,droid + comments: + parameters: + - name: title + type: String + replaceIn: name,path +``` + +1. `id`: name of the method (method name will be composed of `log` + `Event|Screen` + `id`) +2. `name`: name of the tag +3. `path` *(optional with firebase)* : needed for matomo but not with firebase (log screen) +4. `action` *(optional with firebase)* : needed for matomo but not with firebase (log event) +5. `category` *(optional with firebase)* : needed for matomo but not with firebase (log event) +6. `tags`: which platform target +7. `comments` *(optional)* +8. `parameters` *(optional)* + + **Parameters** + +You can use parameters in generate methods. + +1. `name`: name of the parameter +2. `type`: type of the parameter (Int, String, Bool, Double) +3. `replaceIn` *(optional)* + +**Replace in** + +This is section is equivalent of `%s | %d | %f | %@`. You can put the content of the parameter in *name*, *path*, *action*, *category*. +You need to put `_` + `name of the parameter` + `_` in the target and which target you want in the value of `replaceIn`. + + ## Images Images generator will generate images assets along with extensions to access those images easily. -- 2.39.5 From d8937f2de62a0d26b11f86c7c2770428dad2fc9f Mon Sep 17 00:00:00 2001 From: Loris Perret Date: Wed, 13 Dec 2023 10:23:31 +0100 Subject: [PATCH 20/21] docs: Add Analytics section in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1fcd5af..028a5c1 100644 --- a/README.md +++ b/README.md @@ -195,7 +195,7 @@ You can use parameters in generate methods. **Replace in** This is section is equivalent of `%s | %d | %f | %@`. You can put the content of the parameter in *name*, *path*, *action*, *category*. -You need to put `_` + `name of the parameter` + `_` in the target and which target you want in the value of `replaceIn`. +You need to put `_` + `NAME OF THE PARAMETER` + `_` in the target and which target you want in the value of `replaceIn`. (name need to be in uppercase) ## Images -- 2.39.5 From d79af06c38b2c30c829100e2a0494e8da7d8b089 Mon Sep 17 00:00:00 2001 From: Loris Perret Date: Wed, 13 Dec 2023 10:27:35 +0100 Subject: [PATCH 21/21] docs: Add Analytics section in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 028a5c1..904878a 100644 --- a/README.md +++ b/README.md @@ -136,7 +136,7 @@ swift run -c release ResgenSwift strings tags $FORCE_FLAG "./Tags/tags.txt" \ ## Analytics -Analytics will generate all you need to analyze UX with Matomo or Firebase Analytics. Input files are formatted in YAML. This command will generate a manager for each target and an AnalyticsManager. This is this one you will need to use. And it will generate a method for all tags you have declared in the YAML file. If you want to use Matomo, you will need to use the `configure()` method of AnalyticsManager to set up the `siteId` and the `url` of the site. +Analytics will generate all you need to analyze UX with Matomo or Firebase Analytics. Input files are formatted in YAML. This command will generate a manager for each target and an AnalyticsManager. This is this one you will need to use. And it will generate a method for all tags you have declared in the YAML file. Next, you will need to use the `configure()` method of AnalyticsManager and if you want to use matomo to set up the `siteId` and the `url` of the site. ```sh swift run -c release ResgenSwift strings tags $FORCE_FLAG "./Tags/tags.txt" \ -- 2.39.5