3 Commits

Author SHA1 Message Date
4426091dcb Fix Tests Analytics (#17)
All checks were successful
gitea-openium/resgen.swift/pipeline/head This commit looks good
Reviewed-on: #17
Reviewed-by: Thibaut Schmitt <t.schmitt@openium.fr>
Co-authored-by: Loris Perret <l.perret@openium.fr>
Co-committed-by: Loris Perret <l.perret@openium.fr>
2025-07-17 14:11:27 +02:00
187980933e Optional generation of Colors extension
Some checks failed
gitea-openium/resgen.swift/pipeline/head There was a failure building this commit
2025-07-17 09:40:08 +02:00
a64a956254 Optional generation of Colors extension 2025-07-17 09:39:43 +02:00
15 changed files with 536 additions and 477 deletions

View File

@@ -36,7 +36,7 @@ swift run -c release ResgenSwift fonts $FORCE_FLAG "./Fonts/fonts.txt" \
## Colors
Colors generator generates an extension of `UIColor` (or a custom class) along with colorsets in specified xcassets.
Colors generator generates colorsets in specified xcassets and an extension of `Color` (or a custom class) associated to those colorsets. If the extension name is not specified, no extension will be generated.
```sh
swift run -c release ResgenSwift colors $FORCE_FLAG "./Colors/colors.txt" \

View File

@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -1,11 +1,13 @@
// Generated by ResgenSwift.Analytics 2.1.0
import Foundation
import MatomoTracker
import FirebaseAnalytics
// MARK: - Protocol
protocol AnalyticsManagerProtocol {
func logScreen(
name: String,
path: String,
@@ -22,75 +24,12 @@ protocol AnalyticsManagerProtocol {
func setEnable(_ enable: Bool)
}
// MARK: - Matomo
class MatomoAnalyticsManager: AnalyticsManagerProtocol {
// MARK: - Properties
private var tracker: MatomoTracker
// MARK: - Init
init(siteId: String, url: String) {
debugPrint("[Matomo service] Server URL: \(url)")
debugPrint("[Matomo service] Site ID: \(siteId)")
tracker = MatomoTracker(
siteId: siteId,
baseURL: URL(string: url)!
)
#if DEBUG
tracker.dispatchInterval = 5
#endif
#if DEBUG
tracker.logger = DefaultLogger(minLevel: .verbose)
#endif
debugPrint("[Matomo service] Configured with content base: \(tracker.contentBase?.absoluteString ?? "-")")
debugPrint("[Matomo service] Opt out: \(tracker.isOptedOut)")
}
// MARK: - Methods
func logScreen(
name: String,
path: String,
params: [String: Any]?
) {
guard let trackerUrl = tracker.contentBase?.absoluteString else { return }
let urlString = URL(string: "\(trackerUrl)" + "/" + "\(path)" + "iOS")
tracker.track(
view: [name],
url: urlString
)
}
func logEvent(
name: String,
action: String,
category: String,
params: [String: Any]?
) {
tracker.track(
eventWithCategory: category,
action: action,
name: name,
number: nil,
url: nil
)
}
func setEnable(_ enable: Bool) {
tracker.isOptedOut = !enable
}
}
// MARK: - Firebase
class FirebaseAnalyticsManager: AnalyticsManagerProtocol {
// MARK: - Methods
func logScreen(
name: String,
path: String,
@@ -157,16 +96,16 @@ class FirebaseAnalyticsManager: AnalyticsManagerProtocol {
parameters: parameters
)
}
func setEnable(_ enable: Bool) {
Analytics.setAnalyticsCollectionEnabled(enable)
}
}
// MARK: - Traker Type
enum TrackerType: CaseIterable {
case matomo
case firebase
}
@@ -190,7 +129,7 @@ class AnalyticsManager {
}
}
// MARK: - Methods
// MARK: - Enable Methods
private func setAnalytics(enable: Bool, _ analytics: [TrackerType]) {
managers.forEach { (key, value) in
@@ -210,14 +149,12 @@ class AnalyticsManager {
setAnalytics(enable: false, analytics)
}
func configure(siteId: String, url: String) {
managers[TrackerType.matomo] = MatomoAnalyticsManager(
siteId: siteId,
url: url
)
func configure() {
managers[TrackerType.firebase] = FirebaseAnalyticsManager()
}
// MARK: - Private Log Methods
private func logScreen(
name: String,
path: String,
@@ -262,12 +199,12 @@ class AnalyticsManager {
)
}
static func logEventS1DefTwo(
func logEventS1DefTwo(
title: String,
count: String,
test2: String = "test"
) {
AnalyticsManager.shared.logEvent(
logEvent(
name: "s1 def two",
action: "test",
category: "test",

View File

@@ -54,7 +54,7 @@ echo "\n-------------------------\n"
# Analytics
swift run -c release ResgenSwift analytics $FORCE_FLAG "./Tags/sampleTags.yml" \
--target "firebase matomo" \
--target "firebase" \
--extension-output-path "./Tags/Generated" \
--extension-name "Analytics" \
--extension-suffix "GenAllScript" \

View File

@@ -5,11 +5,14 @@
// Created by Loris Perret on 08/12/2023.
//
// CPD-OFF
import CoreVideo
import Foundation
import ToolCore
// Disabled cause it's a pain to handle in generated string
// swiftlint:disable type_body_length
enum AnalyticsGenerator {
@@ -89,48 +92,49 @@ enum AnalyticsGenerator {
) -> String {
"""
// Generated by ResgenSwift.\(Analytics.toolName) \(ResgenSwiftVersion)
\(Self.getImport(targets: targets))
\(Self.getAnalyticsProtocol(targets: targets))
\(Self.getTrackerTypeEnum())
\(Self.getTrackerTypeEnum(targets: targets))
// MARK: - Manager
class AnalyticsManager {
static var shared = AnalyticsManager()
private init() {}
// MARK: - Properties
var managers: [TrackerType: AnalyticsManagerProtocol] = [:]
\(Self.getEnabledContent())
\(Self.getAnalyticsProperties(targets: targets))
\(Self.getPrivateLogFunction())
"""
}
private static func getTrackerTypeEnum() -> String {
private static func getTrackerTypeEnum(targets: [TrackerType]) -> String {
var result: [String] = []
TrackerType.allCases.forEach { type in
targets.forEach { type in
result.append(" case \(type)")
}
return """
// MARK: - Traker Type
enum TrackerType: CaseIterable {
\(result.joined(separator: "\n"))
}
"""
}
private static func getEnabledContent() -> String {
"""
private var isEnabled: Bool {
@@ -140,9 +144,9 @@ enum AnalyticsGenerator {
true
}
}
// MARK: - Methods
// MARK: - Enable Methods
private func setAnalytics(enable: Bool, _ analytics: [TrackerType]) {
managers.forEach { (key, value) in
if analytics.contains(where: { type in
@@ -152,11 +156,11 @@ enum AnalyticsGenerator {
}
}
}
func enableAnalytics(_ analytics: [TrackerType] = TrackerType.allCases) {
setAnalytics(enable: true, analytics)
}
func disableAnalytics(_ analytics: [TrackerType] = TrackerType.allCases) {
setAnalytics(enable: false, analytics)
}
@@ -171,6 +175,7 @@ enum AnalyticsGenerator {
if targets.contains(TrackerType.matomo) {
result.append("import MatomoTracker")
}
if targets.contains(TrackerType.firebase) {
result.append("import FirebaseAnalytics")
}
@@ -180,13 +185,15 @@ enum AnalyticsGenerator {
private static func getPrivateLogFunction() -> String {
"""
private func logScreen(
// MARK: - Private Log Methods
private func logScreen(
name: String,
path: String,
params: [String: Any]?
) {
guard isEnabled else { return }
managers.values.forEach { manager in
manager.logScreen(
name: name,
@@ -203,7 +210,7 @@ enum AnalyticsGenerator {
params: [String: Any]?
) {
guard isEnabled else { return }
managers.values.forEach { manager in
manager.logEvent(
name: name,
@@ -235,6 +242,7 @@ enum AnalyticsGenerator {
)
""")
}
if targets.contains(TrackerType.firebase) {
content.append(" managers[TrackerType.firebase] = FirebaseAnalyticsManager()")
}
@@ -252,22 +260,22 @@ enum AnalyticsGenerator {
// MARK: - Protocol
protocol AnalyticsManagerProtocol {
func logScreen(
name: String,
path: String,
params: [String: Any]?
)
func logEvent(
name: String,
action: String,
category: String,
params: [String: Any]?
)
func setEnable(_ enable: Bool)
}
"""
var result: [String] = [proto]
@@ -280,7 +288,7 @@ enum AnalyticsGenerator {
result.append(FirebaseGenerator.service)
}
return result.joined(separator: "\n")
return result.joined(separator: "\n\n")
}
private static func getProperties(
@@ -319,3 +327,5 @@ enum AnalyticsGenerator {
"""
}
}
// CPD-ON

View File

@@ -13,11 +13,11 @@ enum FirebaseGenerator {
static var service: String {
[
FirebaseGenerator.header,
FirebaseGenerator.logScreen,
FirebaseGenerator.logEvent,
FirebaseGenerator.enable,
FirebaseGenerator.footer
Self.header,
Self.logScreen,
Self.logEvent,
Self.enable,
Self.footer
]
.joined(separator: "\n")
}
@@ -29,6 +29,9 @@ enum FirebaseGenerator {
// MARK: - Firebase
class FirebaseAnalyticsManager: AnalyticsManagerProtocol {
// MARK: - Methods
"""
}
@@ -42,11 +45,11 @@ enum FirebaseGenerator {
var parameters = [
AnalyticsParameterScreenName: name as NSObject
]
if path.isEmpty == false {
parameters["path"] = path + "/iOS" as NSObject
}
if let supplementaryParameters = params {
for (newKey, newValue) in supplementaryParameters {
if parameters.contains(where: { (key: String, value: NSObject) in
@@ -54,11 +57,11 @@ enum FirebaseGenerator {
}) {
continue
}
parameters[newKey] = newValue as? NSObject
}
}
Analytics.logEvent(
AnalyticsEventScreenView,
parameters: parameters
@@ -79,15 +82,15 @@ enum FirebaseGenerator {
var parameters: [String:NSObject] = [
AnalyticsParameterItemName: name.replacingOccurrences(of: " ", with: "_") as NSObject
]
if category.isEmpty == false {
parameters["AnalyticsParameterItemCategory"] = category as NSObject
}
if action.isEmpty == false {
parameters["action"] = action as NSObject
}
if let supplementaryParameters = params {
for (newKey, newValue) in supplementaryParameters {
if parameters.contains(where: { (key: String, value: NSObject) in
@@ -105,9 +108,10 @@ enum FirebaseGenerator {
parameters: parameters
)
}
"""
}
private static var enable: String {
"""
func setEnable(_ enable: Bool) {
@@ -115,11 +119,10 @@ enum FirebaseGenerator {
}
"""
}
private static var footer: String {
"""
}
"""
}
}

View File

@@ -5,18 +5,20 @@
// Created by Loris Perret on 05/12/2023.
//
// CPD-OFF
import Foundation
enum MatomoGenerator {
static var service: String {
[
MatomoGenerator.header,
MatomoGenerator.setup,
MatomoGenerator.logScreen,
MatomoGenerator.logEvent,
MatomoGenerator.enable,
MatomoGenerator.footer
Self.header,
Self.setup,
Self.logScreen,
Self.logEvent,
Self.enable,
Self.footer
]
.joined(separator: "\n")
}
@@ -100,10 +102,10 @@ enum MatomoGenerator {
url: nil
)
}
"""
}
private static var enable: String {
"""
func setEnable(_ enable: Bool) {
@@ -115,7 +117,8 @@ enum MatomoGenerator {
private static var footer: String {
"""
}
"""
}
}
// CPD-ON

View File

@@ -52,20 +52,22 @@ class AnalyticsDefinition {
private func getParameters() -> String {
var result: String
let paramsString = parameters.compactMap { parameter -> String? in
guard parameter.value.isEmpty else { return nil }
let defaultValue: String
switch parameter.type {
case .bool:
defaultValue = "\(parameter.defaultValue.lowercased())"
case .int, .double:
defaultValue = "\(parameter.defaultValue)"
case .string:
defaultValue = "\"\(parameter.defaultValue)\""
}
let defaultValueString = parameter.defaultValue.isEmpty ? "" : " = \(defaultValue)"
return "\(parameter.name): \(parameter.type.rawValue)\(defaultValueString)"
}
@@ -90,9 +92,13 @@ class AnalyticsDefinition {
for rep in parameter.replaceIn {
switch rep {
case "name": name = name.replacingFirstOccurrence(of: "_\(parameter.name.uppercased())_", with: "\\(\(parameter.name))")
case "path": path = path.replacingFirstOccurrence(of: "_\(parameter.name.uppercased())_", with: "\\(\(parameter.name))")
case "category": category = category.replacingFirstOccurrence(of: "_\(parameter.name.uppercased())_", with: "\\(\(parameter.name))")
case "action": action = action.replacingFirstOccurrence(of: "_\(parameter.name.uppercased())_", with: "\\(\(parameter.name))")
default:
if let param = parameters.first(where: { $0.name == rep }), param.value.isEmpty == false {
param.value = param.value.replacingFirstOccurrence(of: "_\(parameter.name.uppercased())_", with: "\\(\(parameter.name))")
@@ -117,8 +123,10 @@ class AnalyticsDefinition {
switch param.type {
case .bool:
params.append("\"\(param.name)\": \(param.value.lowercased())")
case .int, .double:
params.append("\"\(param.name)\": \(param.value)")
case .string:
params.append("\"\(param.name)\": \"\(param.value)\"")
}

View File

@@ -18,7 +18,7 @@ class AnalyticsParameter {
var replaceIn: [String] = []
// MARK: - Init
init(name: String, type: ParameterType, value: String, defaultValue: String) {
self.name = name
self.type = type

View File

@@ -8,6 +8,7 @@
import Foundation
enum ParameterType: String {
case string = "String"
case int = "Int"
case double = "Double"

View File

@@ -70,7 +70,7 @@ class AnalyticsFileParser {
private static func getParameters(from parameters: [AnalyticsParameterDTO]) -> [AnalyticsParameter] {
func verify(value: String?, for type: ParameterType) {
guard let value, value.isEmpty == false else { return }
switch type {
case .int:
if Int(value) == nil {
@@ -78,23 +78,26 @@ class AnalyticsFileParser {
print(error.description)
Analytics.exit(withError: error)
}
case .bool:
if Bool(value.lowercased()) == nil {
let error = AnalyticsError.invalidParameter("type of \(value) is not \(type)")
print(error.description)
Analytics.exit(withError: error)
}
case .double:
if Double(value) == nil {
let error = AnalyticsError.invalidParameter("type of \(value) is not \(type)")
print(error.description)
Analytics.exit(withError: error)
}
case .string:
break
}
}
return parameters.map { dtoParameter in
// Type
let type = dtoParameter.type.uppercasedFirst()
@@ -104,13 +107,13 @@ class AnalyticsFileParser {
print(error.description)
Analytics.exit(withError: error)
}
if dtoParameter.value != nil, dtoParameter.replaceIn != nil {
let error = AnalyticsError.invalidParameter("you can't set 'value' and 'replaceIn' for \(dtoParameter.name)")
print(error.description)
Analytics.exit(withError: error)
}
verify(value: dtoParameter.value, for: typeEnum)
verify(value: dtoParameter.defaultValue, for: typeEnum)
@@ -147,7 +150,7 @@ class AnalyticsFileParser {
}
if let parameters {
definition.parameters = AnalyticsFileParser.getParameters(from: parameters)
definition.parameters = Self.getParameters(from: parameters)
}
return definition
@@ -215,7 +218,7 @@ class AnalyticsFileParser {
if let category = event.category {
definition.category = category
}
if let action = event.action {
definition.action = action
}

View File

@@ -21,8 +21,6 @@ struct Colors: ParsableCommand {
// MARK: - Static
static let toolName = "Color"
static let defaultExtensionName = "Color"
static let defaultExtensionNameUIKit = "UIColor"
static let assetsColorsFolderName = "Colors"
// MARK: - Command options
@@ -57,22 +55,28 @@ struct Colors: ParsableCommand {
// -> Time: 3.4505380392074585 seconds
// Generate extension
ColorExtensionGenerator.writeExtensionFile(
colors: parsedColors,
staticVar: options.staticMembers,
extensionName: options.extensionName,
extensionFilePath: options.extensionFilePath,
isSwiftUI: true
)
if let extensionName = options.extensionName,
let extensionFilePath = options.extensionFilePath {
ColorExtensionGenerator.writeExtensionFile(
colors: parsedColors,
staticVar: options.staticMembers,
extensionName: extensionName,
extensionFilePath: extensionFilePath,
isSwiftUI: true
)
}
// Generate extension
ColorExtensionGenerator.writeExtensionFile(
colors: parsedColors,
staticVar: options.staticMembers,
extensionName: options.extensionNameUIKit,
extensionFilePath: options.extensionFilePathUIKit,
isSwiftUI: false
)
if let extensionNameUIKit = options.extensionNameUIKit,
let extensionFilePathUIKit = options.extensionFilePathUIKit {
ColorExtensionGenerator.writeExtensionFile(
colors: parsedColors,
staticVar: options.staticMembers,
extensionName: extensionNameUIKit,
extensionFilePath: extensionFilePathUIKit,
isSwiftUI: false
)
}
print("[\(Self.toolName)] Colors generated")
}
@@ -96,18 +100,29 @@ struct Colors: ParsableCommand {
Self.exit(withError: error)
}
// Extension for UIKit and SwiftUI should have different name
guard options.extensionName != options.extensionNameUIKit else {
let error = ColorsToolError.extensionNamesCollision(options.extensionName)
print(error.description)
Self.exit(withError: error)
// Extension for UIKit and SwiftUI should have different name (if both are defined)
if let extensionName = options.extensionName,
let extensionNameUIKit = options.extensionNameUIKit {
guard extensionName != extensionNameUIKit else {
let error = ColorsToolError.extensionNamesCollision(extensionName)
print(error.description)
Self.exit(withError: error)
}
}
// Check if needed to regenerate
let fileToCompareToInput: String = {
// If there is no extension file to compare
// Then check the xcassets file instead
if let extensionFilePath = options.extensionFilePath {
return extensionFilePath
}
return options.xcassetsPath
}()
guard GeneratorChecker.shouldGenerate(
force: options.forceGeneration,
inputFilePath: options.inputFile,
extensionFilePath: options.extensionFilePath
extensionFilePath: fileToCompareToInput
) else {
print("[\(Self.toolName)] Colors are already up to date :) ")
return false

View File

@@ -30,11 +30,11 @@ struct ColorsToolOptions: 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 an Color extension.")
var extensionName: String = Colors.defaultExtensionName
@Option(help: "Extension name. If not specified, no extension will be generated.")
var extensionName: String?
@Option(help: "SwiftUI Extension name. If not specified, it will generate an UIColor extension.")
var extensionNameUIKit: String = Colors.defaultExtensionNameUIKit
@Option(help: "SwiftUI Extension name. If not specified, no extension will be generated.")
var extensionNameUIKit: String?
@Option(help: "Extension suffix. Ex: MyApp, it will generate {extensionName}+ColorsMyApp.swift")
var extensionSuffix: String?
@@ -46,27 +46,35 @@ extension ColorsToolOptions {
// MARK: - SwiftUI
var extensionFileName: String {
var extensionFileName: String? {
guard let extensionName else { return nil }
if let extensionSuffix {
return "\(extensionName)+\(extensionSuffix).swift"
}
return "\(extensionName).swift"
}
var extensionFilePath: String {
"\(extensionOutputPath)/\(extensionFileName)"
var extensionFilePath: String? {
guard let extensionFileName else { return nil }
return "\(extensionOutputPath)/\(extensionFileName)"
}
// MARK: - UIKit
var extensionFileNameUIKit: String {
var extensionFileNameUIKit: String? {
guard let extensionNameUIKit else { return nil }
if let extensionSuffix {
return "\(extensionNameUIKit)+\(extensionSuffix).swift"
}
return "\(extensionNameUIKit).swift"
}
var extensionFilePathUIKit: String {
"\(extensionOutputPath)/\(extensionFileNameUIKit)"
var extensionFilePathUIKit: String? {
guard let extensionFileNameUIKit else { return nil }
return "\(extensionOutputPath)/\(extensionFileNameUIKit)"
}
}

View File

@@ -62,7 +62,8 @@ final class AnalyticsDefinitionTests: XCTestCase {
func logScreenDefinitionName() {
logScreen(
name: "Ecran un",
path: "ecran_un/"
path: "ecran_un/",
params: nil
)
}
"""
@@ -84,7 +85,7 @@ final class AnalyticsDefinitionTests: XCTestCase {
name: "Ecran un",
action: "",
category: "",
params: [:]
params: nil
)
}
"""
@@ -103,9 +104,10 @@ final class AnalyticsDefinitionTests: XCTestCase {
// Expect
let expectScreen = """
static func logScreenDefinitionName() {
logScreen(
AnalyticsManager.shared.logScreen(
name: "Ecran un",
path: "ecran_un/"
path: "ecran_un/",
params: nil
)
}
"""
@@ -123,11 +125,11 @@ final class AnalyticsDefinitionTests: XCTestCase {
// Expect
let expectEvent = """
static func logEventDefinitionName() {
logEvent(
AnalyticsManager.shared.logEvent(
name: "Ecran un",
action: "",
category: "",
params: [:]
params: nil
)
}
"""

View File

@@ -1,6 +1,6 @@
//
// AnalyticsGeneratorTests.swift
//
//
//
// Created by Thibaut Schmitt on 06/09/2022.
//
@@ -14,7 +14,7 @@ import ToolCore
@testable import ResgenSwift
final class AnalyticsGeneratorTests: XCTestCase {
private func getAnalyticsDefinition(
id: String,
path: String = "",
@@ -31,7 +31,183 @@ final class AnalyticsGeneratorTests: XCTestCase {
definition.category = category
return definition
}
private func protocolString() -> String {
"""
// MARK: - Protocol
protocol AnalyticsManagerProtocol {
func logScreen(
name: String,
path: String,
params: [String: Any]?
)
func logEvent(
name: String,
action: String,
category: String,
params: [String: Any]?
)
func setEnable(_ enable: Bool)
}
"""
}
private func firebaseString() -> String {
"""
// MARK: - Firebase
class FirebaseAnalyticsManager: AnalyticsManagerProtocol {
// MARK: - Methods
func logScreen(
name: String,
path: String,
params: [String: Any]?
) {
var parameters = [
AnalyticsParameterScreenName: name as NSObject
]
if path.isEmpty == false {
parameters["path"] = path + "/iOS" as NSObject
}
if let supplementaryParameters = params {
for (newKey, newValue) in supplementaryParameters {
if parameters.contains(where: { (key: String, value: NSObject) in
key == newKey
}) {
continue
}
parameters[newKey] = newValue as? NSObject
}
}
Analytics.logEvent(
AnalyticsEventScreenView,
parameters: parameters
)
}
func logEvent(
name: String,
action: String,
category: String,
params: [String: Any]?
) {
var parameters: [String:NSObject] = [
AnalyticsParameterItemName: name.replacingOccurrences(of: " ", with: "_") as NSObject
]
if category.isEmpty == false {
parameters["AnalyticsParameterItemCategory"] = category as NSObject
}
if action.isEmpty == false {
parameters["action"] = action as NSObject
}
if let supplementaryParameters = params {
for (newKey, newValue) in supplementaryParameters {
if parameters.contains(where: { (key: String, value: NSObject) in
key == newKey
}) {
continue
}
parameters[newKey] = newValue as? NSObject
}
}
Analytics.logEvent(
AnalyticsEventSelectContent,
parameters: parameters
)
}
func setEnable(_ enable: Bool) {
Analytics.setAnalyticsCollectionEnabled(enable)
}
}
"""
}
private func matomoString() -> String {
"""
// MARK: - Matomo
class MatomoAnalyticsManager: AnalyticsManagerProtocol {
// MARK: - Properties
private var tracker: MatomoTracker
// MARK: - Init
init(siteId: String, url: String) {
debugPrint("[Matomo service] Server URL: \\(url)")
debugPrint("[Matomo service] Site ID: \\(siteId)")
tracker = MatomoTracker(
siteId: siteId,
baseURL: URL(string: url)!
)
#if DEBUG
tracker.dispatchInterval = 5
#endif
#if DEBUG
tracker.logger = DefaultLogger(minLevel: .verbose)
#endif
debugPrint("[Matomo service] Configured with content base: \\(tracker.contentBase?.absoluteString ?? "-")")
debugPrint("[Matomo service] Opt out: \\(tracker.isOptedOut)")
}
// MARK: - Methods
func logScreen(
name: String,
path: String,
params: [String: Any]?
) {
guard let trackerUrl = tracker.contentBase?.absoluteString else { return }
let urlString = URL(string: "\\(trackerUrl)" + "/" + "\\(path)" + "iOS")
tracker.track(
view: [name],
url: urlString
)
}
func logEvent(
name: String,
action: String,
category: String,
params: [String: Any]?
) {
tracker.track(
eventWithCategory: category,
action: action,
name: name,
number: nil,
url: nil
)
}
func setEnable(_ enable: Bool) {
tracker.isOptedOut = !enable
}
}
"""
}
func testGeneratedExtensionContentFirebase() {
// Given
let sectionOne = AnalyticsCategory(id: "section_one")
@@ -39,19 +215,19 @@ final class AnalyticsGeneratorTests: XCTestCase {
getAnalyticsDefinition(id: "s1_def_one", name: "s1 def one", type: .screen, tags: ["ios", "iosonly"]),
getAnalyticsDefinition(id: "s1_def_two", name: "s1 def two", type: .event, tags: ["ios", "iosonly"]),
]
let sectionTwo = AnalyticsCategory(id: "section_two")
sectionTwo.definitions = [
getAnalyticsDefinition(id: "s2_def_one", name: "s2 def one", type: .screen, tags: ["ios","iosonly"]),
getAnalyticsDefinition(id: "s2_def_two", name: "s2 def two", type: .event, tags: ["droid","droidonly"]),
]
let sectionThree = AnalyticsCategory(id: "section_three")
sectionThree.definitions = [
getAnalyticsDefinition(id: "s3_def_one", name: "s3 def one", type: .screen, tags: ["droid","droidonly"]),
getAnalyticsDefinition(id: "s3_def_two", name: "s3 def two", type: .event, tags: ["droid","droidonly"]),
]
// When
let extensionContent = AnalyticsGenerator.getExtensionContent(
targets: [TrackerType.firebase],
@@ -65,64 +241,18 @@ final class AnalyticsGeneratorTests: XCTestCase {
let expect = """
// Generated by ResgenSwift.Analytics \(ResgenSwiftVersion)
import Foundation
import FirebaseAnalytics
// MARK: - Protocol
\(protocolString())
protocol AnalyticsManagerProtocol {
\(firebaseString())
func logScreen(name: String, path: String)
func logEvent(
name: String,
action: String,
category: String,
params: [String: Any]?
)
}
// MARK: - Traker Type
// MARK: - Firebase
enum TrackerType: CaseIterable {
class FirebaseAnalyticsManager: AnalyticsManagerProtocol {
func logScreen(name: String, path: String) {
var parameters = [
AnalyticsParameterScreenName: name as NSObject
]
Analytics.logEvent(
AnalyticsEventScreenView,
parameters: parameters
)
}
func logEvent(
name: String,
action: String,
category: String,
params: [String: Any]?
) {
var parameters: [String:NSObject] = [
"action": action as NSObject,
"category": category as NSObject,
]
if let supplementaryParameters = params {
for (newKey, newValue) in supplementaryParameters {
if parameters.contains(where: { (key: String, value: NSObject) in
key == newKey
}) {
continue
}
parameters[newKey] = newValue as? NSObject
}
}
Analytics.logEvent(
name.replacingOccurrences(of: [" "], with: "_"),
parameters: parameters
)
}
case firebase
}
// MARK: - Manager
@@ -131,27 +261,59 @@ final class AnalyticsGeneratorTests: XCTestCase {
static var shared = AnalyticsManager()
private init() {}
// MARK: - Properties
var managers: [AnalyticsManagerProtocol] = []
var managers: [TrackerType: AnalyticsManagerProtocol] = [:]
private var isEnabled: Bool = true
private var isEnabled: Bool {
if ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1" {
false
} else {
true
}
}
// MARK: - Methods
// MARK: - Enable Methods
func setAnalyticsEnabled(_ enable: Bool) {
isEnabled = enable
private func setAnalytics(enable: Bool, _ analytics: [TrackerType]) {
managers.forEach { (key, value) in
if analytics.contains(where: { type in
type == key
}) {
value.setEnable(enable)
}
}
}
func enableAnalytics(_ analytics: [TrackerType] = TrackerType.allCases) {
setAnalytics(enable: true, analytics)
}
func disableAnalytics(_ analytics: [TrackerType] = TrackerType.allCases) {
setAnalytics(enable: false, analytics)
}
func configure() {
managers.append(FirebaseAnalyticsManager())
managers[TrackerType.firebase] = FirebaseAnalyticsManager()
}
private func logScreen(name: String, path: String) {
// MARK: - Private Log Methods
private func logScreen(
name: String,
path: String,
params: [String: Any]?
) {
guard isEnabled else { return }
managers.forEach { manager in
manager.logScreen(name: name, path: path)
managers.values.forEach { manager in
manager.logScreen(
name: name,
path: path,
params: params
)
}
}
@@ -163,7 +325,7 @@ final class AnalyticsGeneratorTests: XCTestCase {
) {
guard isEnabled else { return }
managers.forEach { manager in
managers.values.forEach { manager in
manager.logEvent(
name: name,
action: action,
@@ -178,7 +340,8 @@ final class AnalyticsGeneratorTests: XCTestCase {
func logScreenS1DefOne() {
logScreen(
name: "s1 def one",
path: ""
path: "",
params: nil
)
}
@@ -187,7 +350,7 @@ final class AnalyticsGeneratorTests: XCTestCase {
name: "s1 def two",
action: "",
category: "",
params: [:]
params: nil
)
}
@@ -196,19 +359,20 @@ final class AnalyticsGeneratorTests: XCTestCase {
func logScreenS2DefOne() {
logScreen(
name: "s2 def one",
path: ""
path: "",
params: nil
)
}
}
"""
if extensionContent != expect {
print(prettyFirstDifferenceBetweenStrings(s1: extensionContent, s2: expect))
}
XCTAssertEqual(extensionContent.adaptForXCTest(), expect.adaptForXCTest())
}
func testGeneratedExtensionContentMatomo() {
// Given
let sectionOne = AnalyticsCategory(id: "section_one")
@@ -216,19 +380,19 @@ final class AnalyticsGeneratorTests: XCTestCase {
getAnalyticsDefinition(id: "s1_def_one", path: "s1_def_one/", name: "s1 def one", type: .screen, tags: ["ios", "iosonly"]),
getAnalyticsDefinition(id: "s1_def_two", action: "test", category: "test", name: "s1 def two", type: .event, tags: ["ios", "iosonly"]),
]
let sectionTwo = AnalyticsCategory(id: "section_two")
sectionTwo.definitions = [
getAnalyticsDefinition(id: "s2_def_one", path: "s2_def_one/", name: "s2 def one", type: .screen, tags: ["ios","iosonly"]),
getAnalyticsDefinition(id: "s2_def_two", action: "test", category: "test", name: "s2 def two", type: .event, tags: ["droid","droidonly"]),
]
let sectionThree = AnalyticsCategory(id: "section_three")
sectionThree.definitions = [
getAnalyticsDefinition(id: "s3_def_one", path: "s3_def_one/", name: "s3 def one", type: .screen, tags: ["droid","droidonly"]),
getAnalyticsDefinition(id: "s3_def_two", action: "test", category: "test", name: "s3 def two", type: .event, tags: ["droid","droidonly"]),
]
// When
let extensionContent = AnalyticsGenerator.getExtensionContent(
targets: [TrackerType.matomo],
@@ -241,80 +405,18 @@ final class AnalyticsGeneratorTests: XCTestCase {
let expect = """
// Generated by ResgenSwift.Analytics \(ResgenSwiftVersion)
import Foundation
import MatomoTracker
// MARK: - Protocol
\(protocolString())
protocol AnalyticsManagerProtocol {
\(matomoString())
func logScreen(name: String, path: String)
func logEvent(
name: String,
action: String,
category: String,
params: [String: Any]?
)
}
// MARK: - Traker Type
// MARK: - Matomo
enum TrackerType: CaseIterable {
class MatomoAnalyticsManager: AnalyticsManagerProtocol {
// MARK: - Properties
private var tracker: MatomoTracker
// MARK: - Init
init(siteId: String, url: String) {
debugPrint("[Matomo service] Server URL: \\(url)")
debugPrint("[Matomo service] Site ID: \\(siteId)")
tracker = MatomoTracker(
siteId: siteId,
baseURL: URL(string: url)!
)
#if DEBUG
tracker.dispatchInterval = 5
#endif
#if DEBUG
tracker.logger = DefaultLogger(minLevel: .verbose)
#endif
debugPrint("[Matomo service] Configured with content base: \\(tracker.contentBase?.absoluteString ?? "-")")
debugPrint("[Matomo service] Opt out: \\(tracker.isOptedOut)")
}
// MARK: - Methods
func logScreen(name: String, path: String) {
guard !tracker.isOptedOut else { return }
guard let trackerUrl = tracker.contentBase?.absoluteString else { return }
let urlString = URL(string: "\\(trackerUrl)" + "/" + "\\(path)" + "iOS")
tracker.track(
view: [name],
url: urlString
)
}
func logEvent(
name: String,
action: String,
category: String,
params: [String: Any]?
) {
guard !tracker.isOptedOut else { return }
tracker.track(
eventWithCategory: category,
action: action,
name: name,
number: nil,
url: nil
)
}
case matomo
}
// MARK: - Manager
@@ -323,32 +425,62 @@ final class AnalyticsGeneratorTests: XCTestCase {
static var shared = AnalyticsManager()
private init() {}
// MARK: - Properties
var managers: [AnalyticsManagerProtocol] = []
var managers: [TrackerType: AnalyticsManagerProtocol] = [:]
private var isEnabled: Bool = true
private var isEnabled: Bool {
if ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1" {
false
} else {
true
}
}
// MARK: - Methods
// MARK: - Enable Methods
func setAnalyticsEnabled(_ enable: Bool) {
isEnabled = enable
private func setAnalytics(enable: Bool, _ analytics: [TrackerType]) {
managers.forEach { (key, value) in
if analytics.contains(where: { type in
type == key
}) {
value.setEnable(enable)
}
}
}
func enableAnalytics(_ analytics: [TrackerType] = TrackerType.allCases) {
setAnalytics(enable: true, analytics)
}
func disableAnalytics(_ analytics: [TrackerType] = TrackerType.allCases) {
setAnalytics(enable: false, analytics)
}
func configure(siteId: String, url: String) {
managers.append(
MatomoAnalyticsManager(
siteId: siteId,
url: url
)
managers[TrackerType.matomo] = MatomoAnalyticsManager(
siteId: siteId,
url: url
)
}
private func logScreen(name: String, path: String) {
// MARK: - Private Log Methods
private func logScreen(
name: String,
path: String,
params: [String: Any]?
) {
guard isEnabled else { return }
managers.forEach { manager in
manager.logScreen(name: name, path: path)
managers.values.forEach { manager in
manager.logScreen(
name: name,
path: path,
params: params
)
}
}
@@ -360,7 +492,7 @@ final class AnalyticsGeneratorTests: XCTestCase {
) {
guard isEnabled else { return }
managers.forEach { manager in
managers.values.forEach { manager in
manager.logEvent(
name: name,
action: action,
@@ -375,7 +507,8 @@ final class AnalyticsGeneratorTests: XCTestCase {
func logScreenS1DefOne() {
logScreen(
name: "s1 def one",
path: "s1_def_one/"
path: "s1_def_one/",
params: nil
)
}
@@ -384,7 +517,7 @@ final class AnalyticsGeneratorTests: XCTestCase {
name: "s1 def two",
action: "test",
category: "test",
params: [:]
params: nil
)
}
@@ -393,19 +526,20 @@ final class AnalyticsGeneratorTests: XCTestCase {
func logScreenS2DefOne() {
logScreen(
name: "s2 def one",
path: "s2_def_one/"
path: "s2_def_one/",
params: nil
)
}
}
"""
if extensionContent != expect {
print(prettyFirstDifferenceBetweenStrings(s1: extensionContent, s2: expect))
}
XCTAssertEqual(extensionContent.adaptForXCTest(), expect.adaptForXCTest())
}
func testGeneratedExtensionContentMatomoAndFirebase() {
// Given
let sectionOne = AnalyticsCategory(id: "section_one")
@@ -413,19 +547,19 @@ final class AnalyticsGeneratorTests: XCTestCase {
getAnalyticsDefinition(id: "s1_def_one", path: "s1_def_one/", name: "s1 def one", type: .screen, tags: ["ios", "iosonly"]),
getAnalyticsDefinition(id: "s1_def_two", action: "test", category: "test", name: "s1 def two", type: .event, tags: ["ios", "iosonly"]),
]
let sectionTwo = AnalyticsCategory(id: "section_two")
sectionTwo.definitions = [
getAnalyticsDefinition(id: "s2_def_one", path: "s2_def_one/", name: "s2 def one", type: .screen, tags: ["ios","iosonly"]),
getAnalyticsDefinition(id: "s2_def_two", action: "test", category: "test", name: "s2 def two", type: .event, tags: ["droid","droidonly"]),
]
let sectionThree = AnalyticsCategory(id: "section_three")
sectionThree.definitions = [
getAnalyticsDefinition(id: "s3_def_one", path: "s3_def_one/", name: "s3 def one", type: .screen, tags: ["droid","droidonly"]),
getAnalyticsDefinition(id: "s3_def_two", action: "test", category: "test", name: "s3 def two", type: .event, tags: ["droid","droidonly"]),
]
// When
let extensionContent = AnalyticsGenerator.getExtensionContent(
targets: [TrackerType.matomo, TrackerType.firebase],
@@ -439,125 +573,22 @@ final class AnalyticsGeneratorTests: XCTestCase {
let expect = """
// Generated by ResgenSwift.Analytics \(ResgenSwiftVersion)
import Foundation
import MatomoTracker
import FirebaseAnalytics
// MARK: - Protocol
\(protocolString())
protocol AnalyticsManagerProtocol {
\(matomoString())
func logScreen(name: String, path: String)
func logEvent(
name: String,
action: String,
category: String,
params: [String: Any]?
)
}
\(firebaseString())
// MARK: - Matomo
// MARK: - Traker Type
class MatomoAnalyticsManager: AnalyticsManagerProtocol {
enum TrackerType: CaseIterable {
// MARK: - Properties
private var tracker: MatomoTracker
// MARK: - Init
init(siteId: String, url: String) {
debugPrint("[Matomo service] Server URL: \\(url)")
debugPrint("[Matomo service] Site ID: \\(siteId)")
tracker = MatomoTracker(
siteId: siteId,
baseURL: URL(string: url)!
)
#if DEBUG
tracker.dispatchInterval = 5
#endif
#if DEBUG
tracker.logger = DefaultLogger(minLevel: .verbose)
#endif
debugPrint("[Matomo service] Configured with content base: \\(tracker.contentBase?.absoluteString ?? "-")")
debugPrint("[Matomo service] Opt out: \\(tracker.isOptedOut)")
}
// MARK: - Methods
func logScreen(name: String, path: String) {
guard !tracker.isOptedOut else { return }
guard let trackerUrl = tracker.contentBase?.absoluteString else { return }
let urlString = URL(string: "\\(trackerUrl)" + "/" + "\\(path)" + "iOS")
tracker.track(
view: [name],
url: urlString
)
}
func logEvent(
name: String,
action: String,
category: String,
params: [String: Any]?
) {
guard !tracker.isOptedOut else { return }
tracker.track(
eventWithCategory: category,
action: action,
name: name,
number: nil,
url: nil
)
}
}
// MARK: - Firebase
class FirebaseAnalyticsManager: AnalyticsManagerProtocol {
func logScreen(name: String, path: String) {
var parameters = [
AnalyticsParameterScreenName: name as NSObject
]
Analytics.logEvent(
AnalyticsEventScreenView,
parameters: parameters
)
}
func logEvent(
name: String,
action: String,
category: String,
params: [String: Any]?
) {
var parameters: [String:NSObject] = [
"action": action as NSObject,
"category": category as NSObject,
]
if let supplementaryParameters = params {
for (newKey, newValue) in supplementaryParameters {
if parameters.contains(where: { (key: String, value: NSObject) in
key == newKey
}) {
continue
}
parameters[newKey] = newValue as? NSObject
}
}
Analytics.logEvent(
name.replacingOccurrences(of: [" "], with: "_"),
parameters: parameters
)
}
case matomo
case firebase
}
// MARK: - Manager
@@ -566,33 +597,63 @@ final class AnalyticsGeneratorTests: XCTestCase {
static var shared = AnalyticsManager()
private init() {}
// MARK: - Properties
var managers: [AnalyticsManagerProtocol] = []
var managers: [TrackerType: AnalyticsManagerProtocol] = [:]
private var isEnabled: Bool = true
private var isEnabled: Bool {
if ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1" {
false
} else {
true
}
}
// MARK: - Methods
// MARK: - Enable Methods
func setAnalyticsEnabled(_ enable: Bool) {
isEnabled = enable
private func setAnalytics(enable: Bool, _ analytics: [TrackerType]) {
managers.forEach { (key, value) in
if analytics.contains(where: { type in
type == key
}) {
value.setEnable(enable)
}
}
}
func enableAnalytics(_ analytics: [TrackerType] = TrackerType.allCases) {
setAnalytics(enable: true, analytics)
}
func disableAnalytics(_ analytics: [TrackerType] = TrackerType.allCases) {
setAnalytics(enable: false, analytics)
}
func configure(siteId: String, url: String) {
managers.append(
MatomoAnalyticsManager(
siteId: siteId,
url: url
)
managers[TrackerType.matomo] = MatomoAnalyticsManager(
siteId: siteId,
url: url
)
managers.append(FirebaseAnalyticsManager())
managers[TrackerType.firebase] = FirebaseAnalyticsManager()
}
private func logScreen(name: String, path: String) {
// MARK: - Private Log Methods
private func logScreen(
name: String,
path: String,
params: [String: Any]?
) {
guard isEnabled else { return }
managers.forEach { manager in
manager.logScreen(name: name, path: path)
managers.values.forEach { manager in
manager.logScreen(
name: name,
path: path,
params: params
)
}
}
@@ -604,7 +665,7 @@ final class AnalyticsGeneratorTests: XCTestCase {
) {
guard isEnabled else { return }
managers.forEach { manager in
managers.values.forEach { manager in
manager.logEvent(
name: name,
action: action,
@@ -619,7 +680,8 @@ final class AnalyticsGeneratorTests: XCTestCase {
func logScreenS1DefOne() {
logScreen(
name: "s1 def one",
path: "s1_def_one/"
path: "s1_def_one/",
params: nil
)
}
@@ -628,7 +690,7 @@ final class AnalyticsGeneratorTests: XCTestCase {
name: "s1 def two",
action: "test",
category: "test",
params: [:]
params: nil
)
}
@@ -637,13 +699,14 @@ final class AnalyticsGeneratorTests: XCTestCase {
func logScreenS2DefOne() {
logScreen(
name: "s2 def one",
path: "s2_def_one/"
path: "s2_def_one/",
params: nil
)
}
}
"""
if extensionContent != expect {
print(prettyFirstDifferenceBetweenStrings(s1: extensionContent, s2: expect))
}