From ce274219fc0e5b930c0c4e7c42034fa588296390 Mon Sep 17 00:00:00 2001 From: Loris Perret Date: Tue, 5 Dec 2023 16:56:44 +0100 Subject: [PATCH] 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")