17 Commits

Author SHA1 Message Date
239deb2b91 RES-32 Ajouter la possibilité désactiver des traqueurs spécifiques
Some checks failed
gitea-openium/resgen.swift/pipeline/pr-master There was a failure building this commit
2025-02-12 10:49:06 +01:00
9a05ce29b8 Disable tag for previews
Some checks failed
gitea-openium/resgen.swift/pipeline/pr-master There was a failure building this commit
2024-12-17 16:30:22 +01:00
2ec4ebcc66 Fix plugin swiftlint
Some checks failed
gitea-openium/resgen.swift/pipeline/pr-master There was a failure building this commit
2024-11-04 14:00:27 +01:00
6ea31a8030 Fix some error when value and replaceIn
Some checks failed
gitea-openium/resgen.swift/pipeline/pr-master There was a failure building this commit
2024-07-18 11:36:21 +02:00
df173406d4 Update README.md
Some checks failed
gitea-openium/resgen.swift/pipeline/pr-master There was a failure building this commit
2024-07-18 11:36:11 +02:00
922ed56959 Add parameter defaultValue and value and add replaceIn parameter in another parameter
Some checks failed
gitea-openium/resgen.swift/pipeline/pr-master There was a failure building this commit
2024-07-18 08:24:23 +02:00
ab91c1c277 Update README.md
Some checks failed
gitea-openium/resgen.swift/pipeline/pr-master There was a failure building this commit
2024-07-17 15:27:10 +02:00
aa64ce5cf7 fix: Static function 2024-07-17 15:26:39 +02:00
23bf3c3a82 fix: Use category and action if not matomo 2024-07-17 15:26:39 +02:00
09556ba6e0 [DEVTOOLS-186] Exporter les images de resgen en svg
Some checks failed
gitea-openium/resgen.swift/pipeline/head There was a failure building this commit
Reviewed-on: #12
Reviewed-by: Thibaut Schmitt <t.schmitt@openium.fr>
2024-07-17 15:18:13 +02:00
dea57dc1e2 Maj du readme
Some checks failed
gitea-openium/resgen.swift/pipeline/pr-master There was a failure building this commit
gitea-openium/resgen.swift/pipeline/head There was a failure building this commit
2024-07-11 10:29:32 +02:00
07575bd2bf DEVTOOLS-195 Ne pas générer les svg en template par défaut
Some checks failed
gitea-openium/resgen.swift/pipeline/pr-master There was a failure building this commit
2024-07-11 10:08:47 +02:00
8686ae974c Suppression des anciens assets si svg
Some checks failed
gitea-openium/resgen.swift/pipeline/pr-master There was a failure building this commit
2024-06-21 14:54:20 +02:00
be4c561ea8 DEVTOOLS-192 Resgen iOS vector
Some checks failed
gitea-openium/resgen.swift/pipeline/pr-master There was a failure building this commit
2024-06-21 09:18:51 +02:00
2357a40fff DEVTOOLS-186 Exporter les images de resgen en svg
Some checks failed
gitea-openium/resgen.swift/pipeline/head There was a failure building this commit
gitea-openium/resgen.swift/pipeline/pr-master There was a failure building this commit
2024-04-22 12:05:24 +02:00
d4afa9c9e9 Maj du Readme suite aux devs sur le fichier string catalog
Some checks failed
gitea-openium/resgen.swift/pipeline/head There was a failure building this commit
2024-04-22 09:48:21 +02:00
76ef0a2d59 Merge pull request 'DEVTOOLS-185 Remplacer le json en dur des images resgen' (#11) from DEVTOOLS-186/SVG_resgen into master
Some checks failed
gitea-openium/resgen.swift/pipeline/head There was a failure building this commit
Reviewed-on: #11
Reviewed-by: Thibaut Schmitt <t.schmitt@openium.fr>
2024-04-19 17:02:26 +02:00
21 changed files with 688 additions and 218 deletions

2
Jenkinsfile vendored
View File

@ -1,6 +1,6 @@
library "openiumpipeline"
env.DEVELOPER_DIR="/Applications/Xcode-15.3.0.app/Contents/Developer"
env.DEVELOPER_DIR="/Applications/Xcode-15.4.0.app/Contents/Developer"
//env.SIMULATOR_DEVICE_TYPES="iPad--7th-generation-"
env.IS_PACKAGE_SWIFT=1
env.TARGETS_MACOS=1

View File

@ -1,32 +1,5 @@
{
"pins" : [
{
"identity" : "collectionconcurrencykit",
"kind" : "remoteSourceControl",
"location" : "https://github.com/JohnSundell/CollectionConcurrencyKit.git",
"state" : {
"revision" : "b4f23e24b5a1bff301efc5e70871083ca029ff95",
"version" : "0.2.0"
}
},
{
"identity" : "cryptoswift",
"kind" : "remoteSourceControl",
"location" : "https://github.com/krzyzanowskim/CryptoSwift.git",
"state" : {
"revision" : "c9c3df6ab812de32bae61fc0cd1bf6d45170ebf0",
"version" : "1.8.2"
}
},
{
"identity" : "sourcekitten",
"kind" : "remoteSourceControl",
"location" : "https://github.com/jpsim/SourceKitten.git",
"state" : {
"revision" : "b6dc09ee51dfb0c66e042d2328c017483a1a5d56",
"version" : "0.34.1"
}
},
{
"identity" : "swift-argument-parser",
"kind" : "remoteSourceControl",
@ -37,39 +10,12 @@
}
},
{
"identity" : "swift-syntax",
"identity" : "swiftlintplugin",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-syntax.git",
"location" : "https://github.com/lukepistrol/SwiftLintPlugin",
"state" : {
"revision" : "6ad4ea24b01559dde0773e3d091f1b9e36175036",
"version" : "509.0.2"
}
},
{
"identity" : "swiftlint",
"kind" : "remoteSourceControl",
"location" : "https://github.com/realm/SwiftLint.git",
"state" : {
"revision" : "f17a4f9dfb6a6afb0408426354e4180daaf49cee",
"version" : "0.54.0"
}
},
{
"identity" : "swiftytexttable",
"kind" : "remoteSourceControl",
"location" : "https://github.com/scottrhoyt/SwiftyTextTable.git",
"state" : {
"revision" : "c6df6cf533d120716bff38f8ff9885e1ce2a4ac3",
"version" : "0.9.0"
}
},
{
"identity" : "swxmlhash",
"kind" : "remoteSourceControl",
"location" : "https://github.com/drmohundro/SWXMLHash.git",
"state" : {
"revision" : "a853604c9e9a83ad9954c7e3d2a565273982471f",
"version" : "7.0.2"
"revision" : "5a65f4074975f811da666dfe31a19850950b1ea4",
"version" : "0.56.2"
}
},
{

View File

@ -10,7 +10,7 @@ let package = Package(
// Dependencies declare other packages that this package depends on.
.package(url: "https://github.com/apple/swift-argument-parser", from: "1.0.0"),
.package(url: "https://github.com/jpsim/Yams.git", from: "5.0.1"),
.package(url: "https://github.com/realm/SwiftLint.git", .upToNextMajor(from: "0.54.0")),
.package(url: "https://github.com/lukepistrol/SwiftLintPlugin", exact: "0.56.2"),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
@ -22,7 +22,9 @@ let package = Package(
.product(name: "ArgumentParser", package: "swift-argument-parser"),
"Yams"
],
plugins: [.plugin(name: "SwiftLintPlugin", package: "SwiftLint")]
plugins: [
.plugin(name: "SwiftLint", package: "SwiftLintPlugin")
]
),
// Helper targets

View File

@ -81,7 +81,7 @@ swift run -c release ResgenSwift strings twine $FORCE_FLAG "./Twine/strings.txt"
2. Input translations file (must be Twine formatted)
3. `--langs`: langs to generate (string with space between each lang)
4. `--default-lang`: default lang that will be in `Base.lproj`. It must be in `langs` as well
4. `--extension-output-path`: path where to generate generated extension
5. `--extension-output-path`: path where to generate generated extension
### Stringium (recommended)
@ -93,6 +93,7 @@ swift run -c release ResgenSwift strings stringium $FORCE_FLAG "./Strings/string
--extension-output-path "./Strings/Generated" \
--extension-name "AppString" \
--extension-suffix "GreatApp" \
--xcStrings true
--static-members true
```
@ -105,6 +106,7 @@ swift run -c release ResgenSwift strings stringium $FORCE_FLAG "./Strings/string
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: `AppString+GreatApp.swift`)
6. `--xcStrings`*(optional)* : generate string catalog
7. `--static-members` *(optional)*: generate static properties or not
@ -131,7 +133,7 @@ swift run -c release ResgenSwift strings tags $FORCE_FLAG "./Tags/tags.txt" \
6. `--extension-suffix` *(optional)* : additional text which is added to filename (ex: `AppTags+GreatApp.swift`)
7. `--static-members` *(optional)*: generate static properties or not
> ⚠️ If extension name is not set or is `Tags`, it will generate the following typaloas `typealias Tags = String`.
> ⚠️ If extension name is not set or is `Tags`, it will generate the following typealias `typealias Tags = String`.
## Analytics
@ -139,7 +141,7 @@ swift run -c release ResgenSwift strings tags $FORCE_FLAG "./Tags/tags.txt" \
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" \
swift run -c release ResgenSwift analytics $FORCE_FLAG "./Tags/analytics.yml" \
--target "matomo firebase" \
--extension-output-path "./Analytics/Generated" \
--extension-name "AppAnalytics" \
@ -157,7 +159,7 @@ swift run -c release ResgenSwift strings tags $FORCE_FLAG "./Tags/tags.txt" \
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`.
> ⚠️ If extension name is not set or is `Analytics`, it will generate the following typealias `typealias Analytics = String`.
### YAML
@ -184,23 +186,87 @@ swift run -c release ResgenSwift strings tags $FORCE_FLAG "./Tags/tags.txt" \
7. `comments` *(optional)*
8. `parameters` *(optional)*
**Parameters**
**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. `value`: value of the parameter
4. `defaultValue`: defaultValue of the parameter
3. `replaceIn` *(optional)*
**Value**
If you want to send another parameter with a static value. For example, you want to send to which screen the event is triggered.
You can add the parameter 'screenName' for example and its 'value' is 'Home'. With this, you do not need to specify the value in the function call.
**DefaultValue**
If you want ta add a parameter in the call of the function but you want to make it optionnal with a default value you need to use this property.
Example:
```
events:
id: id_of_tag
name: _TITLE_
tags: ios,droid
parameters:
- name: title
type: String
defaultValue: someTitle
```
The generated method will be:
```
logIdOfTag(title: String = "someTitle")
```
**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`. (name need to be in uppercase)
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).
You can't use `value`and `replaceIn`in thge same time.
Example:
```
events:
id: id_of_tag
name: _TITLE_
tags: ios,droid
parameters:
- name: title
type: String
replaceIn: name
```
In this sample, we want to add the parameter `title` in the field `name`. So, we need to place `_TITLE_` in the field `name`.
The generated method will be:
```
logIdOfTag(title: String)
```
You can also want to replace a parameter in an other parameter. You can do this with the `replaceIn` property. The condition is that the parameter which will use the `replaceIn`need to have the `value`property
Example:
```
events:
id: id_of_tag
name: title
tags: ios,droid
parameters:
- name: something
type: String
value: test _TEXT_
- name: text
type: String
replaceIn: something
```
## Images
Images generator will generate images assets along with extensions to access those images easily.
Images generator will generate images assets along with extensions to access those images easily.
```sh
swift run -c release ResgenSwift images $FORCE_FLAG "./Images/images.txt" \
@ -223,6 +289,7 @@ swift run -c release ResgenSwift images $FORCE_FLAG "./Images/images.txt" \
6. `--extension-suffix` *(optional)* : additional text which is added to filename (ex: `AppImage+GreatApp.swift`)
7. `--static-members` *(optional)*: generate static properties or not
> ⚠️ Svg images will be copied in the assets and rendered as "Original", however if those images are not rendered correctly you can force the png generation by adding the key word "png" like this: id arrow_back 15 ? png
## All at once
@ -284,6 +351,15 @@ tags:
extensionName: String?
extensionSuffix: String?
staticMembers: Bool?
analytics:
-
inputFile: String
target: String
extensionOutputPath: String
extensionName: String?
extensionSuffix: String?
staticMembers: Bool?
```
### Multiple configurations

View File

@ -6,13 +6,20 @@ import FirebaseAnalytics
// MARK: - Protocol
protocol AnalyticsManagerProtocol {
func logScreen(name: String, path: String)
func logScreen(
name: String,
path: String,
params: [String: Any]?
)
func logEvent(
name: String,
action: String,
category: String,
params: [String: Any]?
)
func setEnable(_ enable: Bool)
}
// MARK: - Matomo
@ -47,8 +54,11 @@ class MatomoAnalyticsManager: AnalyticsManagerProtocol {
// MARK: - Methods
func logScreen(name: String, path: String) {
guard !tracker.isOptedOut else { return }
func logScreen(
name: String,
path: String,
params: [String: Any]?
) {
guard let trackerUrl = tracker.contentBase?.absoluteString else { return }
let urlString = URL(string: "\(trackerUrl)" + "/" + "\(path)" + "iOS")
@ -64,8 +74,6 @@ class MatomoAnalyticsManager: AnalyticsManagerProtocol {
category: String,
params: [String: Any]?
) {
guard !tracker.isOptedOut else { return }
tracker.track(
eventWithCategory: category,
action: action,
@ -74,16 +82,40 @@ class MatomoAnalyticsManager: AnalyticsManagerProtocol {
url: nil
)
}
func setEnable(_ enable: Bool) {
tracker.isOptedOut = !enable
}
}
// MARK: - Firebase
class FirebaseAnalyticsManager: AnalyticsManagerProtocol {
func logScreen(name: String, path: String) {
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
@ -97,9 +129,16 @@ class FirebaseAnalyticsManager: AnalyticsManagerProtocol {
params: [String: Any]?
) {
var parameters: [String:NSObject] = [
"action": action as NSObject,
"category": category as 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 {
@ -114,10 +153,13 @@ class FirebaseAnalyticsManager: AnalyticsManagerProtocol {
}
Analytics.logEvent(
name.replacingOccurrences(of: [" "], with: "_"),
AnalyticsEventSelectContent,
parameters: parameters
)
}
func setEnable(_ enable: Bool) {
Analytics.setAnalyticsCollectionEnabled(enable)
}
}
// MARK: - Manager
@ -127,31 +169,57 @@ class AnalyticsManager {
// MARK: - Properties
var managers: [AnalyticsManagerProtocol] = []
var managers: [TargetType: AnalyticsManagerProtocol] = []
private var isEnabled: Bool = true
private var isEnabled: Bool {
if ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1" {
false
} else {
true
}
}
// MARK: - Methods
func setAnalyticsEnabled(_ enable: Bool) {
isEnabled = enable
private func setAnalytics(enable: Bool) {
managers.forEach { (key, value) in
if analytics.contains(where: { type in
type == key
}) {
value.setEnable(enable)
}
}
}
func enableAnalytics(_ analytics: [TargetType] = TargetType.allCases) {
setAnalytics(enable: true)
}
func disableAnalytics(_ analytics: [TargetType] = TargetType.allCases) {
setAnalytics(enable: false)
}
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) {
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 +231,7 @@ class AnalyticsManager {
) {
guard isEnabled else { return }
managers.forEach { manager in
managers.values.forEach { manager in
manager.logEvent(
name: name,
action: action,
@ -175,31 +243,39 @@ class AnalyticsManager {
// MARK: - section_one
func logScreenS1DefOne(title: String) {
logScreen(
static func logScreenS1DefOne(title: String) {
AnalyticsManager.shared.logScreen(
name: "s1 def one \(title)",
path: "s1_def_one/\(title)"
path: "s1_def_one/\(title)",
params: nil
)
}
func logEventS1DefTwo(title: String, count: String) {
logEvent(
static func logEventS1DefTwo(
title: String,
count: String,
test2: String = "test"
) {
AnalyticsManager.shared.logEvent(
name: "s1 def two",
action: "test",
category: "test",
params: [
"title": title,
"count": count
"count": count,
"test": "test",
"test2": test2
]
)
}
// MARK: - section_two
func logScreenS2DefOne() {
logScreen(
static func logScreenS2DefOne() {
AnalyticsManager.shared.logScreen(
name: "s2 def one",
path: "s2_def_one/"
path: "s2_def_one/",
params: nil
)
}
}

View File

@ -22,6 +22,12 @@ categories:
type: String
- name: count
type: String
- name: test
type: String
value: test
- name: test2
type: String
defaultValue: test
- id: section_two
screens:

View File

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

View File

@ -59,14 +59,18 @@ class AnalyticsGenerator {
\(Self.getImport())
\(Self.getAnalyticsProtocol())
\(Self.getTrackerTypeEnum())
// MARK: - Manager
class AnalyticsManager {
static var shared = AnalyticsManager()
// MARK: - Properties
var managers: [AnalyticsManagerProtocol] = []
var managers: [TrackerType: AnalyticsManagerProtocol] = [:]
\(Self.getEnabledContent())
@ -76,14 +80,49 @@ class AnalyticsGenerator {
"""
}
private static func getTrackerTypeEnum() -> String {
var result: [String] = []
TrackerType.allCases.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 = true
private var isEnabled: Bool {
if ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1" {
false
} else {
true
}
}
// MARK: - 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)
}
"""
}
@ -103,11 +142,19 @@ class AnalyticsGenerator {
private static func getPrivateLogFunction() -> String {
"""
private func logScreen(name: String, path: String) {
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
)
}
}
@ -119,7 +166,7 @@ class AnalyticsGenerator {
) {
guard isEnabled else { return }
managers.forEach { manager in
managers.values.forEach { manager in
manager.logEvent(
name: name,
action: action,
@ -144,16 +191,14 @@ class AnalyticsGenerator {
if targets.contains(TrackerType.matomo) {
content.append("""
managers.append(
MatomoAnalyticsManager(
siteId: siteId,
url: url
)
managers[TrackerType.matomo] = MatomoAnalyticsManager(
siteId: siteId,
url: url
)
""")
}
if targets.contains(TrackerType.firebase) {
content.append(" managers.append(FirebaseAnalyticsManager())")
content.append(" managers[TrackerType.firebase] = FirebaseAnalyticsManager()")
}
return [
@ -169,13 +214,20 @@ class AnalyticsGenerator {
// MARK: - Protocol
protocol AnalyticsManagerProtocol {
func logScreen(name: String, path: String)
func logScreen(
name: String,
path: String,
params: [String: Any]?
)
func logEvent(
name: String,
action: String,
category: String,
params: [String: Any]?
)
func setEnable(_ enable: Bool)
}
"""

View File

@ -14,6 +14,7 @@ enum FirebaseGenerator {
FirebaseGenerator.header,
FirebaseGenerator.logScreen,
FirebaseGenerator.logEvent,
FirebaseGenerator.enable,
FirebaseGenerator.footer
]
.joined(separator: "\n")
@ -31,11 +32,31 @@ enum FirebaseGenerator {
private static var logScreen: String {
"""
func logScreen(name: String, path: String) {
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
@ -54,9 +75,16 @@ enum FirebaseGenerator {
params: [String: Any]?
) {
var parameters: [String:NSObject] = [
"action": action as NSObject,
"category": category as 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 {
@ -71,13 +99,21 @@ enum FirebaseGenerator {
}
Analytics.logEvent(
name.replacingOccurrences(of: [" "], with: "_"),
AnalyticsEventSelectContent,
parameters: parameters
)
}
"""
}
private static var enable: String {
"""
func setEnable(_ enable: Bool) {
Analytics.setAnalyticsCollectionEnabled(enable)
}
"""
}
private static var footer: String {
"""
}

View File

@ -15,6 +15,7 @@ enum MatomoGenerator {
MatomoGenerator.setup,
MatomoGenerator.logScreen,
MatomoGenerator.logEvent,
MatomoGenerator.enable,
MatomoGenerator.footer
]
.joined(separator: "\n")
@ -66,8 +67,11 @@ enum MatomoGenerator {
private static var logScreen: String {
"""
func logScreen(name: String, path: String) {
guard !tracker.isOptedOut else { return }
func logScreen(
name: String,
path: String,
params: [String: Any]?
) {
guard let trackerUrl = tracker.contentBase?.absoluteString else { return }
let urlString = URL(string: "\\(trackerUrl)" + "/" + "\\(path)" + "iOS")
@ -88,8 +92,6 @@ enum MatomoGenerator {
category: String,
params: [String: Any]?
) {
guard !tracker.isOptedOut else { return }
tracker.track(
eventWithCategory: category,
action: action,
@ -98,6 +100,15 @@ enum MatomoGenerator {
url: nil
)
}
"""
}
private static var enable: String {
"""
func setEnable(_ enable: Bool) {
tracker.isOptedOut = !enable
}
"""
}

View File

@ -48,17 +48,23 @@ class AnalyticsDefinition {
}
private func getParameters() -> String {
var params = parameters
var result: String
if type == .screen {
params = params.filter { param in
!param.replaceIn.isEmpty
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 paramsString = params.map { parameter in
"\(parameter.name): \(parameter.type)"
let defaultValueString = parameter.defaultValue.isEmpty ? "" : " = \(defaultValue)"
return "\(parameter.name): \(parameter.type.rawValue)\(defaultValueString)"
}
if paramsString.count > 2 {
@ -84,7 +90,10 @@ class AnalyticsDefinition {
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
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))")
}
}
}
}
@ -99,7 +108,18 @@ class AnalyticsDefinition {
}
supplementaryParams.forEach { param in
params.append("\"\(param.name)\": \(param.name)")
if param.value.isEmpty {
params.append("\"\(param.name)\": \(param.name)")
} else {
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)\"")
}
}
}
if params.count > 1 {
@ -113,14 +133,15 @@ class AnalyticsDefinition {
[\(params.joined(separator: ", "))]
"""
} else {
result = "[:]"
result = "nil"
}
if type == .screen {
return """
logScreen(
name: "\(name)",
path: "\(path)"
path: "\(path)",
params: \(result)
)
"""
} else {
@ -150,7 +171,7 @@ class AnalyticsDefinition {
replaceIn()
return """
static func \(getFuncName())\(getParameters()) {
\(getlogFunction())
AnalyticsManager.shared.\(getlogFunction())
}
"""
}

View File

@ -41,5 +41,7 @@ struct AnalyticsDefinitionEventDTO: Codable {
struct AnalyticsParameterDTO: Codable {
var name: String
var type: String
var value: String?
var defaultValue: String?
var replaceIn: String?
}

View File

@ -9,13 +9,17 @@ import Foundation
class AnalyticsParameter {
var name: String
var type: String
var type: ParameterType
var value: String
var defaultValue: String
var replaceIn: [String] = []
// MARK: - Init
init(name: String, type: String) {
init(name: String, type: ParameterType, value: String, defaultValue: String) {
self.name = name
self.type = type
self.value = value
self.defaultValue = defaultValue
}
}

View File

@ -0,0 +1,15 @@
//
// File.swift
//
//
// Created by Loris Perret on 17/07/2024.
//
import Foundation
enum ParameterType: String {
case string = "String"
case int = "Int"
case double = "Double"
case bool = "Bool"
}

View File

@ -30,25 +30,58 @@ class AnalyticsFileParser {
}
private static func getParameters(from parameters: [AnalyticsParameterDTO]) -> [AnalyticsParameter] {
parameters.map { dtoParameter in
func verify(value: String?, for type: ParameterType) {
guard let value, value.isEmpty == false else { return }
switch type {
case .int:
if Int(value) == nil {
let error = AnalyticsError.invalidParameter("type of \(value) is not \(type)")
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()
guard
type == "String" ||
type == "Int" ||
type == "Double" ||
type == "Bool"
else {
guard let typeEnum = ParameterType(rawValue: type) else {
let error = AnalyticsError.invalidParameter("type of \(dtoParameter.name)")
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)
let parameter = AnalyticsParameter(
name: dtoParameter.name,
type: type
type: typeEnum,
value: dtoParameter.value ?? "",
defaultValue: dtoParameter.defaultValue ?? ""
)
if let replaceIn = dtoParameter.replaceIn {
@ -103,6 +136,8 @@ class AnalyticsFileParser {
Analytics.exit(withError: error)
}
definition.path = path
} else if let path = screen.path {
definition.path = path
}
@ -139,6 +174,14 @@ class AnalyticsFileParser {
}
definition.action = action
} else {
if let category = event.category {
definition.category = category
}
if let action = event.action {
definition.action = action
}
}
return definition

View File

@ -8,10 +8,13 @@
import Foundation
import ToolCore
enum OutputImageExtension: String {
case png
case svg
}
class XcassetsGenerator {
static let outputImageExtension = "png"
let forceGeneration: Bool
// MARK: - Init
@ -60,13 +63,20 @@ class XcassetsGenerator {
generatedAssetsPaths.append(imagesetName)
// Generate output images path
let output1x = "\(imagesetPath)/\(parsedImage.name).\(XcassetsGenerator.outputImageExtension)"
let output2x = "\(imagesetPath)/\(parsedImage.name)@2x.\(XcassetsGenerator.outputImageExtension)"
let output3x = "\(imagesetPath)/\(parsedImage.name)@3x.\(XcassetsGenerator.outputImageExtension)"
let output1x = "\(imagesetPath)/\(parsedImage.name).\(OutputImageExtension.png.rawValue)"
let output2x = "\(imagesetPath)/\(parsedImage.name)@2x.\(OutputImageExtension.png.rawValue)"
let output3x = "\(imagesetPath)/\(parsedImage.name)@3x.\(OutputImageExtension.png.rawValue)"
// Check if we need to convert image
guard self.shouldGenerate(inputImagePath: imageData.path, xcassetImagePath: output1x) else {
//print("\(parsedImage.name) -> Not regenerating")
var needToGenerateForSvg = false
if imageData.ext == "svg" && !parsedImage.imageExtensions.contains(.png) {
needToGenerateForSvg = true
}
guard self.shouldGenerate(inputImagePath: imageData.path, xcassetImagePath: output1x, needToGenerateForSvg: needToGenerateForSvg) else {
print("\(parsedImage.name) -> Not regenerating")
return
}
@ -80,29 +90,55 @@ class XcassetsGenerator {
print(error.description)
Images.exit(withError: error)
}
} else {
do {
let documentsDirectory = try fileManager.contentsOfDirectory(atPath: imagesetPath)
for filePath in documentsDirectory {
try fileManager.removeItem(atPath: "\(imagesetPath)/\(filePath)")
}
} catch {
print("Error deleting previous assets")
}
}
// Convert image
let convertArguments = parsedImage.convertArguments
if imageData.ext == "svg" {
// /usr/local/bin/rsvg-convert path/to/image.png -w 200 -h 300 -o path/to/output.png
// /usr/local/bin/rsvg-convert path/to/image.png -w 200 -o path/to/output.png
// /usr/local/bin/rsvg-convert path/to/image.png -h 300 -o path/to/output.png
var command1x = ["\(svgConverter)", "\(imageData.path)"]
var command2x = ["\(svgConverter)", "\(imageData.path)"]
var command3x = ["\(svgConverter)", "\(imageData.path)"]
self.addConvertArgument(command: &command1x, convertArgument: convertArguments.x1)
self.addConvertArgument(command: &command2x, convertArgument: convertArguments.x2)
self.addConvertArgument(command: &command3x, convertArgument: convertArguments.x3)
command1x.append(contentsOf: ["-o", output1x])
command2x.append(contentsOf: ["-o", output2x])
command3x.append(contentsOf: ["-o", output3x])
Shell.shell(command1x)
Shell.shell(command2x)
Shell.shell(command3x)
if parsedImage.imageExtensions.contains(.png) {
// /usr/local/bin/rsvg-convert path/to/image.png -w 200 -h 300 -o path/to/output.png
// /usr/local/bin/rsvg-convert path/to/image.png -w 200 -o path/to/output.png
// /usr/local/bin/rsvg-convert path/to/image.png -h 300 -o path/to/output.png
var command1x = ["\(svgConverter)", "\(imageData.path)"]
var command2x = ["\(svgConverter)", "\(imageData.path)"]
var command3x = ["\(svgConverter)", "\(imageData.path)"]
self.addConvertArgument(command: &command1x, convertArgument: convertArguments.x1)
self.addConvertArgument(command: &command2x, convertArgument: convertArguments.x2)
self.addConvertArgument(command: &command3x, convertArgument: convertArguments.x3)
command1x.append(contentsOf: ["-o", output1x])
command2x.append(contentsOf: ["-o", output2x])
command3x.append(contentsOf: ["-o", output3x])
Shell.shell(command1x)
Shell.shell(command2x)
Shell.shell(command3x)
} else {
let output = "\(imagesetPath)/\(parsedImage.name).\(OutputImageExtension.svg.rawValue)"
let tempURL = URL(fileURLWithPath: output)
do {
if FileManager.default.fileExists(atPath: tempURL.path) {
try FileManager.default.removeItem(atPath: tempURL.path)
}
try FileManager.default.copyItem(atPath: imageData.path, toPath: tempURL.path)
} catch {
print(error.localizedDescription)
}
}
} else {
// convert path/to/image.png -resize 200x300 path/to/output.png
// convert path/to/image.png -resize 200x path/to/output.png
@ -119,7 +155,7 @@ class XcassetsGenerator {
}
// Write Content.json
guard let imagesetContentJson = parsedImage.contentJson else { return }
guard let imagesetContentJson = parsedImage.generateContentJson(isVector: imageData.ext == "svg") else { return }
let contentJsonFilePath = "\(imagesetPath)/Contents.json"
let contentJsonFilePathURL = URL(fileURLWithPath: contentJsonFilePath)
@ -161,8 +197,8 @@ class XcassetsGenerator {
// MARK: - Helpers: bypass generation
private func shouldGenerate(inputImagePath: String, xcassetImagePath: String) -> Bool {
if forceGeneration {
private func shouldGenerate(inputImagePath: String, xcassetImagePath: String, needToGenerateForSvg: Bool) -> Bool {
if forceGeneration || needToGenerateForSvg {
return true
}

View File

@ -7,14 +7,30 @@
import Foundation
enum TemplateRenderingIntent: String, Codable {
case template
case original
}
struct AssetContent: Codable, Equatable {
let images: [AssetImageDescription]
let info: AssetInfo
let properties: AssetProperties?
init(
images: [AssetImageDescription],
info: AssetInfo,
properties: AssetProperties? = nil
) {
self.images = images
self.info = info
self.properties = properties
}
static func == (lhs: AssetContent, rhs: AssetContent) -> Bool {
guard lhs.images.count == rhs.images.count else { return false }
let lhsImages = lhs.images.sorted(by: { $0.scale < $1.scale })
let rhsImages = rhs.images.sorted(by: { $0.scale < $1.scale })
let lhsImages = lhs.images.sorted(by: { $0.filename < $1.filename })
let rhsImages = rhs.images.sorted(by: { $0.filename < $1.filename })
return lhsImages == rhsImages
}
@ -22,11 +38,39 @@ struct AssetContent: Codable, Equatable {
struct AssetImageDescription: Codable, Equatable {
let idiom: String
let scale: String
let scale: String?
let filename: String
init(
idiom: String,
scale: String? = nil,
filename: String
) {
self.idiom = idiom
self.scale = scale
self.filename = filename
}
}
struct AssetInfo: Codable, Equatable {
let version: Int
let author: String
}
struct AssetProperties: Codable, Equatable {
let preservesVectorRepresentation: Bool
let templateRenderingIntent: TemplateRenderingIntent?
init(
preservesVectorRepresentation: Bool,
templateRenderingIntent: TemplateRenderingIntent? = nil
) {
self.preservesVectorRepresentation = preservesVectorRepresentation
self.templateRenderingIntent = templateRenderingIntent
}
enum CodingKeys: String, CodingKey {
case preservesVectorRepresentation = "preserves-vector-representation"
case templateRenderingIntent = "template-rendering-intent"
}
}

View File

@ -7,12 +7,31 @@
import Foundation
enum ImageExtension: String {
case png
}
struct ParsedImage {
let name: String
let tags: String
let width: Int
let height: Int
let imageExtensions: [ImageExtension]
init(
name: String,
tags: String,
width: Int,
height: Int,
imageExtensions: [ImageExtension] = []
) {
self.name = name
self.tags = tags
self.width = width
self.height = height
self.imageExtensions = imageExtensions
}
// MARK: - Convert
var convertArguments: (x1: ConvertArgument, x2: ConvertArgument, x3: ConvertArgument) {
@ -42,10 +61,12 @@ struct ParsedImage {
// MARK: - Assets
var contentJson: String? {
func generateContentJson(isVector: Bool) -> String? {
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let imageContent = generateImageContent(isVector: isVector)
guard let data = try? encoder.encode(imageContent) else {
let error = ImagesError.writeFile("Contents.json", "Error encoding json file")
print(error.description)
@ -55,30 +76,52 @@ struct ParsedImage {
return String(data: data, encoding: .utf8)
}
var imageContent: AssetContent {
return AssetContent(
images: [
AssetImageDescription(
idiom: "universal",
scale: "1x",
filename: "\(name).\(XcassetsGenerator.outputImageExtension)"
func generateImageContent(isVector: Bool) -> AssetContent {
if !imageExtensions.contains(.png) && isVector {
//Generate svg
return AssetContent(
images: [
AssetImageDescription(
idiom: "universal",
filename: "\(name).\(OutputImageExtension.svg.rawValue)"
)
],
info: AssetInfo(
version: 1,
author: "ResgenSwift-Imagium"
),
AssetImageDescription(
idiom: "universal",
scale: "2x",
filename: "\(name)@2x.\(XcassetsGenerator.outputImageExtension)"
),
AssetImageDescription(
idiom: "universal",
scale: "3x",
filename: "\(name)@3x.\(XcassetsGenerator.outputImageExtension)"
properties: AssetProperties(
preservesVectorRepresentation: true,
templateRenderingIntent: .original
)
],
info: AssetInfo(
version: 1,
author: "ResgenSwift-Imagium"
)
)
} else {
//Generate png
return AssetContent(
images: [
AssetImageDescription(
idiom: "universal",
scale: "1x",
filename: "\(name).\(OutputImageExtension.png.rawValue)"
),
AssetImageDescription(
idiom: "universal",
scale: "2x",
filename: "\(name)@2x.\(OutputImageExtension.png.rawValue)"
),
AssetImageDescription(
idiom: "universal",
scale: "3x",
filename: "\(name)@3x.\(OutputImageExtension.png.rawValue)"
)
],
info: AssetInfo(
version: 1,
author: "ResgenSwift-Imagium"
)
)
}
}
// MARK: - Extension property

View File

@ -38,11 +38,21 @@ class ImageFileParser {
}
return Int(splittedLine[3])!
}()
let image = ParsedImage(name: String(splittedLine[1]), tags: String(splittedLine[0]), width: width, height: height)
var imageExtensions: [ImageExtension] = []
splittedLine.forEach { stringExtension in
if let imageExtension = ImageExtension(rawValue: String(stringExtension)) {
imageExtensions.append(imageExtension)
}
}
let image = ParsedImage(name: String(splittedLine[1]), tags: String(splittedLine[0]), width: width, height: height, imageExtensions: imageExtensions)
imagesToGenerate.append(image)
}
print(imagesToGenerate)
return imagesToGenerate.filter {
$0.tags.contains(platform.rawValue)
}

View File

@ -17,8 +17,8 @@ class ImageFileParserTests: XCTestCase {
#
# SMAAS Support
#
id image_one 25 ?
di image_two ? 50
id image_one 25 ? png
di image_two ? 50 webp png
d image_three 25 ?
d image_four 75 ?
"""
@ -38,13 +38,15 @@ class ImageFileParserTests: XCTestCase {
XCTAssertEqual(firstImage!.tags, "id")
XCTAssertEqual(firstImage!.width, 25)
XCTAssertEqual(firstImage!.height, -1)
XCTAssertEqual(firstImage!.imageExtensions, [.png])
let secondImage = parsedImages.first {
$0.name == "image_two"
}
XCTAssertEqual(secondImage!.name, "image_two")
XCTAssertEqual(secondImage!.tags, "di")
XCTAssertEqual(secondImage!.width, -1)
XCTAssertEqual(secondImage!.height, 50)
XCTAssertEqual(secondImage!.height, 50)
XCTAssertEqual(firstImage!.imageExtensions, [.png])
}
}

View File

@ -127,7 +127,7 @@ final class ParsedImageTests: XCTestCase {
height: 10)
// When
let property = parsedImage.imageContent
let property = parsedImage.generateImageContent(isVector: false)
// Expect
let expect = AssetContent(
@ -135,17 +135,17 @@ final class ParsedImageTests: XCTestCase {
AssetImageDescription(
idiom: "universal",
scale: "1x",
filename: "\(parsedImage.name).\(XcassetsGenerator.outputImageExtension)"
filename: "\(parsedImage.name).\(OutputImageExtension.png.rawValue)"
),
AssetImageDescription(
idiom: "universal",
scale: "2x",
filename: "\(parsedImage.name)@2x.\(XcassetsGenerator.outputImageExtension)"
filename: "\(parsedImage.name)@2x.\(OutputImageExtension.png.rawValue)"
),
AssetImageDescription(
idiom: "universal",
scale: "3x",
filename: "\(parsedImage.name)@3x.\(XcassetsGenerator.outputImageExtension)"
filename: "\(parsedImage.name)@3x.\(OutputImageExtension.png.rawValue)"
)
],
info: AssetInfo(
@ -156,4 +156,48 @@ final class ParsedImageTests: XCTestCase {
XCTAssertEqual(property, expect)
}
func testAssetPng() {
// Given
let imageName = "the_name"
let parsedImage = ParsedImage(name: imageName,
tags: "id",
width: 10,
height: 10,
imageExtensions: [.png])
// When
let property = parsedImage.generateImageContent(isVector: false)
// Expect
let expect = AssetContent(
images: [
AssetImageDescription(
idiom: "universal",
scale: "1x",
filename: "\(parsedImage.name).\(OutputImageExtension.png.rawValue)"
),
AssetImageDescription(
idiom: "universal",
scale: "2x",
filename: "\(parsedImage.name)@2x.\(OutputImageExtension.png.rawValue)"
),
AssetImageDescription(
idiom: "universal",
scale: "3x",
filename: "\(parsedImage.name)@3x.\(OutputImageExtension.png.rawValue)"
)
],
info: AssetInfo(
version: 1,
author: "ResgenSwift-Imagium"
)
)
debugPrint(property)
debugPrint(expect)
XCTAssertEqual(property, expect)
}
}