Compare commits
44 Commits
f0131e0828
...
analytics
Author | SHA1 | Date | |
---|---|---|---|
239deb2b91 | |||
9a05ce29b8 | |||
2ec4ebcc66 | |||
6ea31a8030 | |||
df173406d4 | |||
922ed56959 | |||
ab91c1c277 | |||
aa64ce5cf7 | |||
23bf3c3a82 | |||
09556ba6e0 | |||
dea57dc1e2 | |||
07575bd2bf | |||
8686ae974c | |||
be4c561ea8 | |||
2357a40fff | |||
d4afa9c9e9 | |||
76ef0a2d59 | |||
129eb135f1 | |||
4ad15fcded | |||
fb2ddb2227 | |||
27f86f5c4d | |||
209ba49e3f | |||
ba07005b13 | |||
6c3f3a8982 | |||
0d651b810f | |||
1d7fc76340 | |||
5d4e461933 | |||
55264d61ad | |||
d21ad9d1ea | |||
0bd6c3c2d4 | |||
eed20367b9 | |||
43b5111d79 | |||
2983093a9c | |||
b4bbaa3bfd | |||
498c8fa4ae | |||
2957da6233 | |||
d79af06c38 | |||
d8937f2de6 | |||
9b27f24197 | |||
1d58fd5510 | |||
f6c49bf626 | |||
f1b62d83c4 | |||
ee5055efa5 | |||
6f8e3b6664 |
2
Jenkinsfile
vendored
2
Jenkinsfile
vendored
@ -1,6 +1,6 @@
|
|||||||
library "openiumpipeline"
|
library "openiumpipeline"
|
||||||
|
|
||||||
env.DEVELOPER_DIR="/Applications/Xcode-15.0.1.app/Contents/Developer"
|
env.DEVELOPER_DIR="/Applications/Xcode-15.4.0.app/Contents/Developer"
|
||||||
//env.SIMULATOR_DEVICE_TYPES="iPad--7th-generation-"
|
//env.SIMULATOR_DEVICE_TYPES="iPad--7th-generation-"
|
||||||
env.IS_PACKAGE_SWIFT=1
|
env.IS_PACKAGE_SWIFT=1
|
||||||
env.TARGETS_MACOS=1
|
env.TARGETS_MACOS=1
|
||||||
|
@ -1,32 +1,5 @@
|
|||||||
{
|
{
|
||||||
"pins" : [
|
"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" : "db51c407d3be4a051484a141bf0bff36c43d3b1e",
|
|
||||||
"version" : "1.8.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"identity" : "sourcekitten",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/jpsim/SourceKitten.git",
|
|
||||||
"state" : {
|
|
||||||
"revision" : "b6dc09ee51dfb0c66e042d2328c017483a1a5d56",
|
|
||||||
"version" : "0.34.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"identity" : "swift-argument-parser",
|
"identity" : "swift-argument-parser",
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
@ -37,39 +10,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"identity" : "swift-syntax",
|
"identity" : "swiftlintplugin",
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
"location" : "https://github.com/apple/swift-syntax.git",
|
"location" : "https://github.com/lukepistrol/SwiftLintPlugin",
|
||||||
"state" : {
|
"state" : {
|
||||||
"revision" : "6ad4ea24b01559dde0773e3d091f1b9e36175036",
|
"revision" : "5a65f4074975f811da666dfe31a19850950b1ea4",
|
||||||
"version" : "509.0.2"
|
"version" : "0.56.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"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -10,7 +10,7 @@ let package = Package(
|
|||||||
// Dependencies declare other packages that this package depends on.
|
// 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/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/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: [
|
||||||
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
|
// 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"),
|
.product(name: "ArgumentParser", package: "swift-argument-parser"),
|
||||||
"Yams"
|
"Yams"
|
||||||
],
|
],
|
||||||
plugins: [.plugin(name: "SwiftLintPlugin", package: "SwiftLint")]
|
plugins: [
|
||||||
|
.plugin(name: "SwiftLint", package: "SwiftLintPlugin")
|
||||||
|
]
|
||||||
),
|
),
|
||||||
|
|
||||||
// Helper targets
|
// Helper targets
|
||||||
|
147
README.md
147
README.md
@ -81,7 +81,7 @@ swift run -c release ResgenSwift strings twine $FORCE_FLAG "./Twine/strings.txt"
|
|||||||
2. Input translations file (must be Twine formatted)
|
2. Input translations file (must be Twine formatted)
|
||||||
3. `--langs`: langs to generate (string with space between each lang)
|
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. `--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)
|
### Stringium (recommended)
|
||||||
|
|
||||||
@ -93,6 +93,7 @@ swift run -c release ResgenSwift strings stringium $FORCE_FLAG "./Strings/string
|
|||||||
--extension-output-path "./Strings/Generated" \
|
--extension-output-path "./Strings/Generated" \
|
||||||
--extension-name "AppString" \
|
--extension-name "AppString" \
|
||||||
--extension-suffix "GreatApp" \
|
--extension-suffix "GreatApp" \
|
||||||
|
--xcStrings true
|
||||||
--static-members 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
|
4. `--extension-output-path`: path where to generate generated extension
|
||||||
5. `--extension-name` *(optional)* : name of class to add the 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. `--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
|
7. `--static-members` *(optional)*: generate static properties or not
|
||||||
|
|
||||||
|
|
||||||
@ -131,11 +133,140 @@ 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`)
|
6. `--extension-suffix` *(optional)* : additional text which is added to filename (ex: `AppTags+GreatApp.swift`)
|
||||||
7. `--static-members` *(optional)*: generate static properties or not
|
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
|
||||||
|
|
||||||
|
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 analytics $FORCE_FLAG "./Tags/analytics.yml" \
|
||||||
|
--target "matomo firebase" \
|
||||||
|
--extension-output-path "./Analytics/Generated" \
|
||||||
|
--extension-name "AppAnalytics" \
|
||||||
|
--extension-suffix "GreatApp" \
|
||||||
|
--static-members true
|
||||||
|
```
|
||||||
|
|
||||||
|
**Parameters**
|
||||||
|
|
||||||
|
1. `-f`: force generation
|
||||||
|
2. Input tags file (must be YAML formatted)
|
||||||
|
3. `--target`: target with you will log UX
|
||||||
|
4. `--extension-output-path`: path where to generate generated extension
|
||||||
|
5. `--extension-name` *(optional)* : name of class to add the extension
|
||||||
|
6. `--extension-suffix` *(optional)* : additional text which is added to filename (ex: `AppAnalytics+GreatApp.swift`)
|
||||||
|
7. `--static-members` *(optional)*: generate static properties or not
|
||||||
|
|
||||||
|
> ⚠️ If extension name is not set or is `Analytics`, it will generate the following typealias `typealias Analytics = String`.
|
||||||
|
|
||||||
|
### YAML
|
||||||
|
|
||||||
|
```
|
||||||
|
- id: s1_def_one
|
||||||
|
name: s1 def one _TITLE_
|
||||||
|
path: s1_def_one/_TITLE_
|
||||||
|
action: Tap
|
||||||
|
category: User
|
||||||
|
tags: ios,droid
|
||||||
|
comments:
|
||||||
|
parameters:
|
||||||
|
- name: title
|
||||||
|
type: String
|
||||||
|
replaceIn: name,path
|
||||||
|
```
|
||||||
|
|
||||||
|
1. `id`: name of the method (method name will be composed of `log` + `Event|Screen` + `id`)
|
||||||
|
2. `name`: name of the tag
|
||||||
|
3. `path` *(optional with firebase)* : needed for matomo but not with firebase (log screen)
|
||||||
|
4. `action` *(optional with firebase)* : needed for matomo but not with firebase (log event)
|
||||||
|
5. `category` *(optional with firebase)* : needed for matomo but not with firebase (log event)
|
||||||
|
6. `tags`: which platform target
|
||||||
|
7. `comments` *(optional)*
|
||||||
|
8. `parameters` *(optional)*
|
||||||
|
|
||||||
|
**Parameters**
|
||||||
|
|
||||||
|
You can use parameters in generate methods.
|
||||||
|
|
||||||
|
1. `name`: name of the parameter
|
||||||
|
2. `type`: type of the parameter (Int, String, Bool, Double)
|
||||||
|
3. `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 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
|
||||||
|
|
||||||
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
|
```sh
|
||||||
swift run -c release ResgenSwift images $FORCE_FLAG "./Images/images.txt" \
|
swift run -c release ResgenSwift images $FORCE_FLAG "./Images/images.txt" \
|
||||||
@ -158,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`)
|
6. `--extension-suffix` *(optional)* : additional text which is added to filename (ex: `AppImage+GreatApp.swift`)
|
||||||
7. `--static-members` *(optional)*: generate static properties or not
|
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
|
## All at once
|
||||||
|
|
||||||
@ -219,6 +351,15 @@ tags:
|
|||||||
extensionName: String?
|
extensionName: String?
|
||||||
extensionSuffix: String?
|
extensionSuffix: String?
|
||||||
staticMembers: Bool?
|
staticMembers: Bool?
|
||||||
|
|
||||||
|
analytics:
|
||||||
|
-
|
||||||
|
inputFile: String
|
||||||
|
target: String
|
||||||
|
extensionOutputPath: String
|
||||||
|
extensionName: String?
|
||||||
|
extensionSuffix: String?
|
||||||
|
staticMembers: Bool?
|
||||||
```
|
```
|
||||||
|
|
||||||
### Multiple configurations
|
### Multiple configurations
|
||||||
|
@ -1,18 +1,25 @@
|
|||||||
// Generated by ResgenSwift.Analytics 1.2
|
// Generated by ResgenSwift.Analytics 1.2
|
||||||
|
|
||||||
import MatomoTracker
|
import MatomoTracker
|
||||||
import Firebase
|
import FirebaseAnalytics
|
||||||
|
|
||||||
// MARK: - Protocol
|
// MARK: - Protocol
|
||||||
|
|
||||||
protocol AnalyticsManagerProtocol {
|
protocol AnalyticsManagerProtocol {
|
||||||
func logScreen(name: String, path: String)
|
func logScreen(
|
||||||
|
name: String,
|
||||||
|
path: String,
|
||||||
|
params: [String: Any]?
|
||||||
|
)
|
||||||
|
|
||||||
func logEvent(
|
func logEvent(
|
||||||
name: String,
|
name: String,
|
||||||
action: String,
|
action: String,
|
||||||
category: String,
|
category: String,
|
||||||
params: [String: Any]?
|
params: [String: Any]?
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func setEnable(_ enable: Bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Matomo
|
// MARK: - Matomo
|
||||||
@ -47,8 +54,11 @@ class MatomoAnalyticsManager: AnalyticsManagerProtocol {
|
|||||||
|
|
||||||
// MARK: - Methods
|
// MARK: - Methods
|
||||||
|
|
||||||
func logScreen(name: String, path: String) {
|
func logScreen(
|
||||||
guard !tracker.isOptedOut else { return }
|
name: String,
|
||||||
|
path: String,
|
||||||
|
params: [String: Any]?
|
||||||
|
) {
|
||||||
guard let trackerUrl = tracker.contentBase?.absoluteString else { return }
|
guard let trackerUrl = tracker.contentBase?.absoluteString else { return }
|
||||||
|
|
||||||
let urlString = URL(string: "\(trackerUrl)" + "/" + "\(path)" + "iOS")
|
let urlString = URL(string: "\(trackerUrl)" + "/" + "\(path)" + "iOS")
|
||||||
@ -64,8 +74,6 @@ class MatomoAnalyticsManager: AnalyticsManagerProtocol {
|
|||||||
category: String,
|
category: String,
|
||||||
params: [String: Any]?
|
params: [String: Any]?
|
||||||
) {
|
) {
|
||||||
guard !tracker.isOptedOut else { return }
|
|
||||||
|
|
||||||
tracker.track(
|
tracker.track(
|
||||||
eventWithCategory: category,
|
eventWithCategory: category,
|
||||||
action: action,
|
action: action,
|
||||||
@ -74,16 +82,40 @@ class MatomoAnalyticsManager: AnalyticsManagerProtocol {
|
|||||||
url: nil
|
url: nil
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setEnable(_ enable: Bool) {
|
||||||
|
tracker.isOptedOut = !enable
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Firebase
|
// MARK: - Firebase
|
||||||
|
|
||||||
class FirebaseAnalyticsManager: AnalyticsManagerProtocol {
|
class FirebaseAnalyticsManager: AnalyticsManagerProtocol {
|
||||||
func logScreen(name: String, path: String) {
|
func logScreen(
|
||||||
|
name: String,
|
||||||
|
path: String,
|
||||||
|
params: [String: Any]?
|
||||||
|
) {
|
||||||
var parameters = [
|
var parameters = [
|
||||||
AnalyticsParameterScreenName: name
|
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(
|
Analytics.logEvent(
|
||||||
AnalyticsEventScreenView,
|
AnalyticsEventScreenView,
|
||||||
parameters: parameters
|
parameters: parameters
|
||||||
@ -96,22 +128,38 @@ class FirebaseAnalyticsManager: AnalyticsManagerProtocol {
|
|||||||
category: String,
|
category: String,
|
||||||
params: [String: Any]?
|
params: [String: Any]?
|
||||||
) {
|
) {
|
||||||
var parameters: [String:Any] = [
|
var parameters: [String:NSObject] = [
|
||||||
"action": action,
|
AnalyticsParameterItemName: name.replacingOccurrences(of: " ", with: "_") as NSObject
|
||||||
"category": category,
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
if category.isEmpty == false {
|
||||||
|
parameters["AnalyticsParameterItemCategory"] = category as NSObject
|
||||||
|
}
|
||||||
|
|
||||||
|
if action.isEmpty == false {
|
||||||
|
parameters["action"] = action as NSObject
|
||||||
|
}
|
||||||
|
|
||||||
if let supplementaryParameters = params {
|
if let supplementaryParameters = params {
|
||||||
parameters.merge(supplementaryParameters) { (origin, new) -> Any in
|
for (newKey, newValue) in supplementaryParameters {
|
||||||
return origin
|
if parameters.contains(where: { (key: String, value: NSObject) in
|
||||||
|
key == newKey
|
||||||
|
}) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
parameters[newKey] = newValue as? NSObject
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Analytics.logEvent(
|
Analytics.logEvent(
|
||||||
name,
|
AnalyticsEventSelectContent,
|
||||||
parameters: parameters
|
parameters: parameters
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
func setEnable(_ enable: Bool) {
|
||||||
|
Analytics.setAnalyticsCollectionEnabled(enable)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Manager
|
// MARK: - Manager
|
||||||
@ -121,31 +169,57 @@ class AnalyticsManager {
|
|||||||
|
|
||||||
// MARK: - Properties
|
// 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
|
// MARK: - Methods
|
||||||
|
|
||||||
func setAnalyticsEnabled(_ enable: Bool) {
|
private func setAnalytics(enable: Bool) {
|
||||||
isEnabled = enable
|
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) {
|
func configure(siteId: String, url: String) {
|
||||||
managers.append(
|
managers[TrackerType.matomo] = MatomoAnalyticsManager(
|
||||||
MatomoAnalyticsManager(
|
siteId: siteId,
|
||||||
siteId: siteId,
|
url: url
|
||||||
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 }
|
guard isEnabled else { return }
|
||||||
|
|
||||||
managers.forEach { manager in
|
managers.values.forEach { manager in
|
||||||
manager.logScreen(name: name, path: path)
|
manager.logScreen(
|
||||||
|
name: name,
|
||||||
|
path: path,
|
||||||
|
params: params
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,7 +231,7 @@ class AnalyticsManager {
|
|||||||
) {
|
) {
|
||||||
guard isEnabled else { return }
|
guard isEnabled else { return }
|
||||||
|
|
||||||
managers.forEach { manager in
|
managers.values.forEach { manager in
|
||||||
manager.logEvent(
|
manager.logEvent(
|
||||||
name: name,
|
name: name,
|
||||||
action: action,
|
action: action,
|
||||||
@ -169,28 +243,39 @@ class AnalyticsManager {
|
|||||||
|
|
||||||
// MARK: - section_one
|
// MARK: - section_one
|
||||||
|
|
||||||
func logScreenS1DefOne() {
|
static func logScreenS1DefOne(title: String) {
|
||||||
logScreen(
|
AnalyticsManager.shared.logScreen(
|
||||||
name: "s1 def one",
|
name: "s1 def one \(title)",
|
||||||
path: "s1_def_one/"
|
path: "s1_def_one/\(title)",
|
||||||
|
params: nil
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func logEventS1DefTwo() {
|
static func logEventS1DefTwo(
|
||||||
logEvent(
|
title: String,
|
||||||
|
count: String,
|
||||||
|
test2: String = "test"
|
||||||
|
) {
|
||||||
|
AnalyticsManager.shared.logEvent(
|
||||||
name: "s1 def two",
|
name: "s1 def two",
|
||||||
action: "test",
|
action: "test",
|
||||||
category: "test",
|
category: "test",
|
||||||
params: []
|
params: [
|
||||||
|
"title": title,
|
||||||
|
"count": count,
|
||||||
|
"test": "test",
|
||||||
|
"test2": test2
|
||||||
|
]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - section_two
|
// MARK: - section_two
|
||||||
|
|
||||||
func logScreenS2DefOne() {
|
static func logScreenS2DefOne() {
|
||||||
logScreen(
|
AnalyticsManager.shared.logScreen(
|
||||||
name: "s2 def one",
|
name: "s2 def one",
|
||||||
path: "s2_def_one/"
|
path: "s2_def_one/",
|
||||||
|
params: nil
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,13 @@ categories:
|
|||||||
- id: section_one
|
- id: section_one
|
||||||
screens:
|
screens:
|
||||||
- id: s1_def_one
|
- id: s1_def_one
|
||||||
name: s1 def one
|
name: s1 def one _TITLE_
|
||||||
path: s1_def_one/
|
path: s1_def_one/_TITLE_
|
||||||
tags: ios
|
tags: ios,droid
|
||||||
|
parameters:
|
||||||
|
- name: title
|
||||||
|
type: String
|
||||||
|
replaceIn: name,path
|
||||||
|
|
||||||
events:
|
events:
|
||||||
- id: s1_def_two
|
- id: s1_def_two
|
||||||
@ -13,6 +17,17 @@ categories:
|
|||||||
action: test
|
action: test
|
||||||
category: test
|
category: test
|
||||||
tags: ios
|
tags: ios
|
||||||
|
parameters:
|
||||||
|
- name: title
|
||||||
|
type: String
|
||||||
|
- name: count
|
||||||
|
type: String
|
||||||
|
- name: test
|
||||||
|
type: String
|
||||||
|
value: test
|
||||||
|
- name: test2
|
||||||
|
type: String
|
||||||
|
defaultValue: test
|
||||||
|
|
||||||
- id: section_two
|
- id: section_two
|
||||||
screens:
|
screens:
|
||||||
|
@ -52,19 +52,20 @@ FORCE_FLAG="$1"
|
|||||||
|
|
||||||
#echo "\n-------------------------\n"
|
#echo "\n-------------------------\n"
|
||||||
|
|
||||||
## Analytics
|
# Analytics
|
||||||
#swift run -c release ResgenSwift analytics $FORCE_FLAG "./Tags/sampleTags.yml" \
|
swift run -c release ResgenSwift analytics $FORCE_FLAG "./Tags/sampleTags.yml" \
|
||||||
# --target "matomo firebase" \
|
--target "firebase matomo" \
|
||||||
# --extension-output-path "./Tags/Generated" \
|
--extension-output-path "./Tags/Generated" \
|
||||||
# --extension-name "Analytics" \
|
--extension-name "Analytics" \
|
||||||
# --extension-suffix "GenAllScript"
|
--extension-suffix "GenAllScript" \
|
||||||
|
--static-members true
|
||||||
|
|
||||||
#echo "\n-------------------------\n"
|
#echo "\n-------------------------\n"
|
||||||
#
|
#
|
||||||
# Images
|
## Images
|
||||||
swift run -c release ResgenSwift images $FORCE_FLAG "./Images/sampleImages.txt" \
|
#swift run -c release ResgenSwift images $FORCE_FLAG "./Images/sampleImages.txt" \
|
||||||
--xcassets-path "./Images/imagium.xcassets" \
|
# --xcassets-path "./Images/imagium.xcassets" \
|
||||||
--extension-output-path "./Images/Generated" \
|
# --extension-output-path "./Images/Generated" \
|
||||||
--extension-name "ImageYolo" \
|
# --extension-name "ImageYolo" \
|
||||||
--extension-name-ui-kit "UIImageYolo" \
|
# --extension-name-ui-kit "UIImageYolo" \
|
||||||
--extension-suffix "GenAllScript"
|
# --extension-suffix "GenAllScript"
|
||||||
|
@ -32,8 +32,15 @@ struct Analytics: ParsableCommand {
|
|||||||
mutating func run() {
|
mutating func run() {
|
||||||
print("[\(Self.toolName)] Starting analytics generation")
|
print("[\(Self.toolName)] Starting analytics generation")
|
||||||
print("[\(Self.toolName)] Will use inputFile \(options.inputFile) to generate analytics for target: \(options.target)")
|
print("[\(Self.toolName)] Will use inputFile \(options.inputFile) to generate analytics for target: \(options.target)")
|
||||||
|
|
||||||
|
// Check requirements
|
||||||
|
guard checkRequirements() else { return }
|
||||||
|
|
||||||
print("[\(Self.toolName)] Will generate analytics")
|
print("[\(Self.toolName)] Will generate analytics")
|
||||||
|
|
||||||
|
// Check requirements
|
||||||
|
guard checkRequirements() else { return }
|
||||||
|
|
||||||
// Parse input file
|
// Parse input file
|
||||||
let sections = AnalyticsFileParser.parse(options.inputFile, target: options.target)
|
let sections = AnalyticsFileParser.parse(options.inputFile, target: options.target)
|
||||||
|
|
||||||
@ -47,20 +54,33 @@ struct Analytics: ParsableCommand {
|
|||||||
|
|
||||||
print("[\(Self.toolName)] Analytics generated")
|
print("[\(Self.toolName)] Analytics generated")
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
// MARK: - Requirements
|
||||||
extension Analytics {
|
|
||||||
enum TargetType: CaseIterable {
|
private func checkRequirements() -> Bool {
|
||||||
case matomo
|
let fileManager = FileManager()
|
||||||
case firebase
|
|
||||||
|
|
||||||
var value: String {
|
// Input file
|
||||||
switch self {
|
guard fileManager.fileExists(atPath: options.inputFile) else {
|
||||||
case .matomo:
|
let error = AnalyticsError.fileNotExists(options.inputFile)
|
||||||
"matomo"
|
print(error.description)
|
||||||
case .firebase:
|
Analytics.exit(withError: error)
|
||||||
"firebase"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
guard TrackerType.hasValidTarget(in: options.target) else {
|
||||||
|
let error = AnalyticsError.noValidTracker(options.target)
|
||||||
|
print(error.description)
|
||||||
|
Analytics.exit(withError: error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if needed to regenerate
|
||||||
|
guard GeneratorChecker.shouldGenerate(force: options.forceGeneration,
|
||||||
|
inputFilePath: options.inputFile,
|
||||||
|
extensionFilePath: options.extensionFilePath) else {
|
||||||
|
print("[\(Self.toolName)] Analytics are already up to date :) ")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
39
Sources/ResgenSwift/Analytics/AnalyticsError.swift
Normal file
39
Sources/ResgenSwift/Analytics/AnalyticsError.swift
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
//
|
||||||
|
// AnalyticsError.swift
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Created by Loris Perret on 11/12/2023.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
enum AnalyticsError: Error {
|
||||||
|
case noValidTracker(String)
|
||||||
|
case fileNotExists(String)
|
||||||
|
case missingElement(String)
|
||||||
|
case invalidParameter(String)
|
||||||
|
case parseFailed(String)
|
||||||
|
case writeFile(String, String)
|
||||||
|
|
||||||
|
var description: String {
|
||||||
|
switch self {
|
||||||
|
case .noValidTracker(let inputTargets):
|
||||||
|
return "error: [\(Analytics.toolName)] '\(inputTargets)' ne contient aucun tracker valid"
|
||||||
|
|
||||||
|
case .fileNotExists(let filename):
|
||||||
|
return "error: [\(Analytics.toolName)] File \(filename) does not exists"
|
||||||
|
|
||||||
|
case .missingElement(let element):
|
||||||
|
return "error: [\(Analytics.toolName)] Missing \(element) for Matomo"
|
||||||
|
|
||||||
|
case .invalidParameter(let reason):
|
||||||
|
return "error: [\(Analytics.toolName)] Invalid parameter \(reason)"
|
||||||
|
|
||||||
|
case .parseFailed(let baseError):
|
||||||
|
return "error: [\(Analytics.toolName)] Parse input file failed: \(baseError)"
|
||||||
|
|
||||||
|
case .writeFile(let subErrorDescription, let filename):
|
||||||
|
return "error: [\(Analytics.toolName)] An error occured while writing content to \(filename): \(subErrorDescription)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -10,13 +10,13 @@ import ToolCore
|
|||||||
import CoreVideo
|
import CoreVideo
|
||||||
|
|
||||||
class AnalyticsGenerator {
|
class AnalyticsGenerator {
|
||||||
static var targets: [Analytics.TargetType] = []
|
static var targets: [TrackerType] = []
|
||||||
|
|
||||||
static func writeExtensionFiles(sections: [AnalyticsCategory], target: String, tags: [String], staticVar: Bool, extensionName: String, extensionFilePath: String) {
|
static func writeExtensionFiles(sections: [AnalyticsCategory], target: String, tags: [String], staticVar: Bool, extensionName: String, extensionFilePath: String) {
|
||||||
// Get target type from enum
|
// Get target type from enum
|
||||||
let targetsString: [String] = target.components(separatedBy: " ")
|
let targetsString: [String] = target.components(separatedBy: " ")
|
||||||
|
|
||||||
Analytics.TargetType.allCases.forEach { enumTarget in
|
TrackerType.allCases.forEach { enumTarget in
|
||||||
if targetsString.contains(enumTarget.value) {
|
if targetsString.contains(enumTarget.value) {
|
||||||
targets.append(enumTarget)
|
targets.append(enumTarget)
|
||||||
}
|
}
|
||||||
@ -33,9 +33,9 @@ class AnalyticsGenerator {
|
|||||||
do {
|
do {
|
||||||
try extensionFileContent.write(to: extensionFilePathURL, atomically: false, encoding: .utf8)
|
try extensionFileContent.write(to: extensionFilePathURL, atomically: false, encoding: .utf8)
|
||||||
} catch let error {
|
} catch let error {
|
||||||
let error = StringiumError.writeFile(extensionFilePath, error.localizedDescription)
|
let error = AnalyticsError.writeFile(extensionFilePath, error.localizedDescription)
|
||||||
print(error.description)
|
print(error.description)
|
||||||
Stringium.exit(withError: error)
|
Analytics.exit(withError: error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,15 +58,19 @@ class AnalyticsGenerator {
|
|||||||
|
|
||||||
\(Self.getImport())
|
\(Self.getImport())
|
||||||
|
|
||||||
\(Self.getAnalytics())
|
\(Self.getAnalyticsProtocol())
|
||||||
|
|
||||||
|
\(Self.getTrackerTypeEnum())
|
||||||
|
|
||||||
// MARK: - Manager
|
// MARK: - Manager
|
||||||
|
|
||||||
class AnalyticsManager {
|
class AnalyticsManager {
|
||||||
|
|
||||||
static var shared = AnalyticsManager()
|
static var shared = AnalyticsManager()
|
||||||
|
|
||||||
// MARK: - Properties
|
// MARK: - Properties
|
||||||
|
|
||||||
var managers: [AnalyticsManagerProtocol] = []
|
var managers: [TrackerType: AnalyticsManagerProtocol] = [:]
|
||||||
|
|
||||||
\(Self.getEnabledContent())
|
\(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 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
|
// MARK: - Methods
|
||||||
|
|
||||||
func setAnalyticsEnabled(_ enable: Bool) {
|
private func setAnalytics(enable: Bool, _ analytics: [TrackerType]) {
|
||||||
isEnabled = enable
|
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)
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
@ -91,10 +130,10 @@ class AnalyticsGenerator {
|
|||||||
private static func getImport() -> String {
|
private static func getImport() -> String {
|
||||||
var result: [String] = []
|
var result: [String] = []
|
||||||
|
|
||||||
if targets.contains(Analytics.TargetType.matomo) {
|
if targets.contains(TrackerType.matomo) {
|
||||||
result.append("import MatomoTracker")
|
result.append("import MatomoTracker")
|
||||||
}
|
}
|
||||||
if targets.contains(Analytics.TargetType.firebase) {
|
if targets.contains(TrackerType.firebase) {
|
||||||
result.append("import FirebaseAnalytics")
|
result.append("import FirebaseAnalytics")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,11 +142,19 @@ class AnalyticsGenerator {
|
|||||||
|
|
||||||
private static func getPrivateLogFunction() -> String {
|
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 }
|
guard isEnabled else { return }
|
||||||
|
|
||||||
managers.forEach { manager in
|
managers.values.forEach { manager in
|
||||||
manager.logScreen(name: name, path: path)
|
manager.logScreen(
|
||||||
|
name: name,
|
||||||
|
path: path,
|
||||||
|
params: params
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,7 +166,7 @@ class AnalyticsGenerator {
|
|||||||
) {
|
) {
|
||||||
guard isEnabled else { return }
|
guard isEnabled else { return }
|
||||||
|
|
||||||
managers.forEach { manager in
|
managers.values.forEach { manager in
|
||||||
manager.logEvent(
|
manager.logEvent(
|
||||||
name: name,
|
name: name,
|
||||||
action: action,
|
action: action,
|
||||||
@ -136,24 +183,22 @@ class AnalyticsGenerator {
|
|||||||
var content: [String] = []
|
var content: [String] = []
|
||||||
let footer = " }"
|
let footer = " }"
|
||||||
|
|
||||||
if targets.contains(Analytics.TargetType.matomo) {
|
if targets.contains(TrackerType.matomo) {
|
||||||
header = "func configure(siteId: String, url: String) {"
|
header = "func configure(siteId: String, url: String) {"
|
||||||
} else if targets.contains(Analytics.TargetType.firebase) {
|
} else if targets.contains(TrackerType.firebase) {
|
||||||
header = "func configure() {"
|
header = "func configure() {"
|
||||||
}
|
}
|
||||||
|
|
||||||
if targets.contains(Analytics.TargetType.matomo) {
|
if targets.contains(TrackerType.matomo) {
|
||||||
content.append("""
|
content.append("""
|
||||||
managers.append(
|
managers[TrackerType.matomo] = MatomoAnalyticsManager(
|
||||||
MatomoAnalyticsManager(
|
siteId: siteId,
|
||||||
siteId: siteId,
|
url: url
|
||||||
url: url
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
""")
|
""")
|
||||||
}
|
}
|
||||||
if targets.contains(Analytics.TargetType.firebase) {
|
if targets.contains(TrackerType.firebase) {
|
||||||
content.append(" managers.append(FirebaseAnalyticsManager())")
|
content.append(" managers[TrackerType.firebase] = FirebaseAnalyticsManager()")
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
@ -164,30 +209,37 @@ class AnalyticsGenerator {
|
|||||||
.joined(separator: "\n")
|
.joined(separator: "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func getAnalytics() -> String {
|
private static func getAnalyticsProtocol() -> String {
|
||||||
let proto = """
|
let proto = """
|
||||||
// MARK: - Protocol
|
// MARK: - Protocol
|
||||||
|
|
||||||
protocol AnalyticsManagerProtocol {
|
protocol AnalyticsManagerProtocol {
|
||||||
func logScreen(name: String, path: String)
|
func logScreen(
|
||||||
|
name: String,
|
||||||
|
path: String,
|
||||||
|
params: [String: Any]?
|
||||||
|
)
|
||||||
|
|
||||||
func logEvent(
|
func logEvent(
|
||||||
name: String,
|
name: String,
|
||||||
action: String,
|
action: String,
|
||||||
category: String,
|
category: String,
|
||||||
params: [String: Any]?
|
params: [String: Any]?
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func setEnable(_ enable: Bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
var result: [String] = [proto]
|
var result: [String] = [proto]
|
||||||
|
|
||||||
if targets.contains(Analytics.TargetType.matomo) {
|
if targets.contains(TrackerType.matomo) {
|
||||||
result.append(MatomoGenerator.service.content)
|
result.append(MatomoGenerator.service)
|
||||||
}
|
}
|
||||||
|
|
||||||
if targets.contains(Analytics.TargetType.firebase) {
|
if targets.contains(TrackerType.firebase) {
|
||||||
result.append(FirebaseGenerator.service.content)
|
result.append(FirebaseGenerator.service)
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.joined(separator: "\n")
|
return result.joined(separator: "\n")
|
||||||
|
@ -8,19 +8,21 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
enum FirebaseGenerator {
|
enum FirebaseGenerator {
|
||||||
case service
|
|
||||||
|
static var service: String {
|
||||||
var content: String {
|
|
||||||
[
|
[
|
||||||
FirebaseGenerator.service.header,
|
FirebaseGenerator.header,
|
||||||
FirebaseGenerator.service.logScreen,
|
FirebaseGenerator.logScreen,
|
||||||
FirebaseGenerator.service.logEvent,
|
FirebaseGenerator.logEvent,
|
||||||
FirebaseGenerator.service.footer
|
FirebaseGenerator.enable,
|
||||||
|
FirebaseGenerator.footer
|
||||||
]
|
]
|
||||||
.joined(separator: "\n")
|
.joined(separator: "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
private var header: String {
|
// MARK: - Private vars
|
||||||
|
|
||||||
|
private static var header: String {
|
||||||
"""
|
"""
|
||||||
// MARK: - Firebase
|
// MARK: - Firebase
|
||||||
|
|
||||||
@ -28,13 +30,33 @@ enum FirebaseGenerator {
|
|||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
|
|
||||||
private var logScreen: String {
|
private static var logScreen: String {
|
||||||
"""
|
"""
|
||||||
func logScreen(name: String, path: String) {
|
func logScreen(
|
||||||
|
name: String,
|
||||||
|
path: String,
|
||||||
|
params: [String: Any]?
|
||||||
|
) {
|
||||||
var parameters = [
|
var parameters = [
|
||||||
AnalyticsParameterScreenName: name
|
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(
|
Analytics.logEvent(
|
||||||
AnalyticsEventScreenView,
|
AnalyticsEventScreenView,
|
||||||
parameters: parameters
|
parameters: parameters
|
||||||
@ -44,7 +66,7 @@ enum FirebaseGenerator {
|
|||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
|
|
||||||
private var logEvent: String {
|
private static var logEvent: String {
|
||||||
"""
|
"""
|
||||||
func logEvent(
|
func logEvent(
|
||||||
name: String,
|
name: String,
|
||||||
@ -52,26 +74,47 @@ enum FirebaseGenerator {
|
|||||||
category: String,
|
category: String,
|
||||||
params: [String: Any]?
|
params: [String: Any]?
|
||||||
) {
|
) {
|
||||||
var parameters: [String:Any] = [
|
var parameters: [String:NSObject] = [
|
||||||
"action": action,
|
AnalyticsParameterItemName: name.replacingOccurrences(of: " ", with: "_") as NSObject
|
||||||
"category": category,
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
if category.isEmpty == false {
|
||||||
|
parameters["AnalyticsParameterItemCategory"] = category as NSObject
|
||||||
|
}
|
||||||
|
|
||||||
|
if action.isEmpty == false {
|
||||||
|
parameters["action"] = action as NSObject
|
||||||
|
}
|
||||||
|
|
||||||
if let supplementaryParameters = params {
|
if let supplementaryParameters = params {
|
||||||
parameters.merge(supplementaryParameters) { (origin, new) -> Any in
|
for (newKey, newValue) in supplementaryParameters {
|
||||||
return origin
|
if parameters.contains(where: { (key: String, value: NSObject) in
|
||||||
|
key == newKey
|
||||||
|
}) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
parameters[newKey] = newValue as? NSObject
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Analytics.logEvent(
|
Analytics.logEvent(
|
||||||
name,
|
AnalyticsEventSelectContent,
|
||||||
parameters: parameters
|
parameters: parameters
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
|
|
||||||
private var footer: String {
|
private static var enable: String {
|
||||||
|
"""
|
||||||
|
func setEnable(_ enable: Bool) {
|
||||||
|
Analytics.setAnalyticsCollectionEnabled(enable)
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
|
||||||
|
private static var footer: String {
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,20 +8,22 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
enum MatomoGenerator {
|
enum MatomoGenerator {
|
||||||
case service
|
|
||||||
|
static var service: String {
|
||||||
var content: String {
|
|
||||||
[
|
[
|
||||||
MatomoGenerator.service.header,
|
MatomoGenerator.header,
|
||||||
MatomoGenerator.service.setup,
|
MatomoGenerator.setup,
|
||||||
MatomoGenerator.service.logScreen,
|
MatomoGenerator.logScreen,
|
||||||
MatomoGenerator.service.logEvent,
|
MatomoGenerator.logEvent,
|
||||||
MatomoGenerator.service.footer
|
MatomoGenerator.enable,
|
||||||
|
MatomoGenerator.footer
|
||||||
]
|
]
|
||||||
.joined(separator: "\n")
|
.joined(separator: "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
private var header: String {
|
// MARK: - Private vars
|
||||||
|
|
||||||
|
private static var header: String {
|
||||||
"""
|
"""
|
||||||
// MARK: - Matomo
|
// MARK: - Matomo
|
||||||
|
|
||||||
@ -34,7 +36,7 @@ enum MatomoGenerator {
|
|||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
|
|
||||||
private var setup: String {
|
private static var setup: String {
|
||||||
"""
|
"""
|
||||||
// MARK: - Init
|
// MARK: - Init
|
||||||
|
|
||||||
@ -63,10 +65,13 @@ enum MatomoGenerator {
|
|||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
|
|
||||||
private var logScreen: String {
|
private static var logScreen: String {
|
||||||
"""
|
"""
|
||||||
func logScreen(name: String, path: String) {
|
func logScreen(
|
||||||
guard !tracker.isOptedOut else { return }
|
name: String,
|
||||||
|
path: String,
|
||||||
|
params: [String: Any]?
|
||||||
|
) {
|
||||||
guard let trackerUrl = tracker.contentBase?.absoluteString else { return }
|
guard let trackerUrl = tracker.contentBase?.absoluteString else { return }
|
||||||
|
|
||||||
let urlString = URL(string: "\\(trackerUrl)" + "/" + "\\(path)" + "iOS")
|
let urlString = URL(string: "\\(trackerUrl)" + "/" + "\\(path)" + "iOS")
|
||||||
@ -79,7 +84,7 @@ enum MatomoGenerator {
|
|||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
|
|
||||||
private var logEvent: String {
|
private static var logEvent: String {
|
||||||
"""
|
"""
|
||||||
func logEvent(
|
func logEvent(
|
||||||
name: String,
|
name: String,
|
||||||
@ -87,8 +92,6 @@ enum MatomoGenerator {
|
|||||||
category: String,
|
category: String,
|
||||||
params: [String: Any]?
|
params: [String: Any]?
|
||||||
) {
|
) {
|
||||||
guard !tracker.isOptedOut else { return }
|
|
||||||
|
|
||||||
tracker.track(
|
tracker.track(
|
||||||
eventWithCategory: category,
|
eventWithCategory: category,
|
||||||
action: action,
|
action: action,
|
||||||
@ -97,10 +100,19 @@ enum MatomoGenerator {
|
|||||||
url: nil
|
url: nil
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
|
|
||||||
private var footer: String {
|
private static var enable: String {
|
||||||
|
"""
|
||||||
|
func setEnable(_ enable: Bool) {
|
||||||
|
tracker.isOptedOut = !enable
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
|
||||||
|
private static var footer: String {
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,10 +11,14 @@ class AnalyticsCategory {
|
|||||||
let id: String // OnBoarding
|
let id: String // OnBoarding
|
||||||
var definitions = [AnalyticsDefinition]()
|
var definitions = [AnalyticsDefinition]()
|
||||||
|
|
||||||
|
// MARK: - Init
|
||||||
|
|
||||||
init(id: String) {
|
init(id: String) {
|
||||||
self.id = id
|
self.id = id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Methods
|
||||||
|
|
||||||
func hasOneOrMoreMatchingTags(tags: [String]) -> Bool {
|
func hasOneOrMoreMatchingTags(tags: [String]) -> Bool {
|
||||||
let allTags = definitions.flatMap { $0.tags }
|
let allTags = definitions.flatMap { $0.tags }
|
||||||
let allTagsSet = Set(allTags)
|
let allTagsSet = Set(allTags)
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import ToolCore
|
||||||
|
|
||||||
class AnalyticsDefinition {
|
class AnalyticsDefinition {
|
||||||
let id: String
|
let id: String
|
||||||
@ -18,12 +19,16 @@ class AnalyticsDefinition {
|
|||||||
var parameters: [AnalyticsParameter] = []
|
var parameters: [AnalyticsParameter] = []
|
||||||
var type: TagType
|
var type: TagType
|
||||||
|
|
||||||
|
// MARK: - Init
|
||||||
|
|
||||||
init(id: String, name: String, type: TagType) {
|
init(id: String, name: String, type: TagType) {
|
||||||
self.id = id
|
self.id = id
|
||||||
self.name = name
|
self.name = name
|
||||||
self.type = type
|
self.type = type
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Methods
|
||||||
|
|
||||||
func hasOneOrMoreMatchingTags(inputTags: [String]) -> Bool {
|
func hasOneOrMoreMatchingTags(inputTags: [String]) -> Bool {
|
||||||
if Set(inputTags).isDisjoint(with: tags) {
|
if Set(inputTags).isDisjoint(with: tags) {
|
||||||
return false
|
return false
|
||||||
@ -31,8 +36,8 @@ class AnalyticsDefinition {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Methods
|
// MARK: - Private Methods
|
||||||
|
|
||||||
private func getFuncName() -> String {
|
private func getFuncName() -> String {
|
||||||
var pascalCaseTitle: String = ""
|
var pascalCaseTitle: String = ""
|
||||||
id.components(separatedBy: "_").forEach { word in
|
id.components(separatedBy: "_").forEach { word in
|
||||||
@ -43,17 +48,23 @@ class AnalyticsDefinition {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func getParameters() -> String {
|
private func getParameters() -> String {
|
||||||
var params = parameters
|
|
||||||
var result: String
|
var result: String
|
||||||
|
|
||||||
if type == .screen {
|
let paramsString = parameters.compactMap { parameter -> String? in
|
||||||
params = params.filter { param in
|
guard parameter.value.isEmpty else { return nil }
|
||||||
!param.replaceIn.isEmpty
|
|
||||||
|
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)"
|
||||||
let paramsString = params.map { parameter in
|
return "\(parameter.name): \(parameter.type.rawValue)\(defaultValueString)"
|
||||||
"\(parameter.name): \(parameter.type)"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if paramsString.count > 2 {
|
if paramsString.count > 2 {
|
||||||
@ -79,7 +90,10 @@ class AnalyticsDefinition {
|
|||||||
case "path": path = path.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 "category": category = category.replacingFirstOccurrence(of: "_\(parameter.name.uppercased())_", with: "\\(\(parameter.name))")
|
||||||
case "action": action = action.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))")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -94,9 +108,20 @@ class AnalyticsDefinition {
|
|||||||
}
|
}
|
||||||
|
|
||||||
supplementaryParams.forEach { param in
|
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 {
|
if params.count > 1 {
|
||||||
result = """
|
result = """
|
||||||
[
|
[
|
||||||
@ -108,14 +133,15 @@ class AnalyticsDefinition {
|
|||||||
[\(params.joined(separator: ", "))]
|
[\(params.joined(separator: ", "))]
|
||||||
"""
|
"""
|
||||||
} else {
|
} else {
|
||||||
result = "[:]"
|
result = "nil"
|
||||||
}
|
}
|
||||||
|
|
||||||
if type == .screen {
|
if type == .screen {
|
||||||
return """
|
return """
|
||||||
logScreen(
|
logScreen(
|
||||||
name: "\(name)",
|
name: "\(name)",
|
||||||
path: "\(path)"
|
path: "\(path)",
|
||||||
|
params: \(result)
|
||||||
)
|
)
|
||||||
"""
|
"""
|
||||||
} else {
|
} else {
|
||||||
@ -145,32 +171,8 @@ class AnalyticsDefinition {
|
|||||||
replaceIn()
|
replaceIn()
|
||||||
return """
|
return """
|
||||||
static func \(getFuncName())\(getParameters()) {
|
static func \(getFuncName())\(getParameters()) {
|
||||||
\(getlogFunction())
|
AnalyticsManager.shared.\(getlogFunction())
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension AnalyticsDefinition {
|
|
||||||
enum TagType {
|
|
||||||
case screen
|
|
||||||
case event
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension String {
|
|
||||||
func replacingFirstOccurrence(of: String, with: String) -> Self {
|
|
||||||
if let range = self.range(of: of) {
|
|
||||||
let tmp = self.replacingOccurrences(
|
|
||||||
of: of,
|
|
||||||
with: with,
|
|
||||||
options: .literal,
|
|
||||||
range: range
|
|
||||||
)
|
|
||||||
|
|
||||||
return tmp
|
|
||||||
}
|
|
||||||
|
|
||||||
return self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -17,9 +17,7 @@ struct AnalyticsCategoryDTO: Codable {
|
|||||||
var events: [AnalyticsDefinitionEventDTO]?
|
var events: [AnalyticsDefinitionEventDTO]?
|
||||||
}
|
}
|
||||||
|
|
||||||
protocol AnalyticsDefinitionDTO: Codable {}
|
struct AnalyticsDefinitionScreenDTO: Codable {
|
||||||
|
|
||||||
struct AnalyticsDefinitionScreenDTO: AnalyticsDefinitionDTO {
|
|
||||||
var id: String
|
var id: String
|
||||||
var name: String
|
var name: String
|
||||||
var tags: String
|
var tags: String
|
||||||
@ -29,7 +27,7 @@ struct AnalyticsDefinitionScreenDTO: AnalyticsDefinitionDTO {
|
|||||||
var path: String?
|
var path: String?
|
||||||
}
|
}
|
||||||
|
|
||||||
struct AnalyticsDefinitionEventDTO: AnalyticsDefinitionDTO {
|
struct AnalyticsDefinitionEventDTO: Codable {
|
||||||
var id: String
|
var id: String
|
||||||
var name: String
|
var name: String
|
||||||
var tags: String
|
var tags: String
|
||||||
@ -43,5 +41,7 @@ struct AnalyticsDefinitionEventDTO: AnalyticsDefinitionDTO {
|
|||||||
struct AnalyticsParameterDTO: Codable {
|
struct AnalyticsParameterDTO: Codable {
|
||||||
var name: String
|
var name: String
|
||||||
var type: String
|
var type: String
|
||||||
|
var value: String?
|
||||||
|
var defaultValue: String?
|
||||||
var replaceIn: String?
|
var replaceIn: String?
|
||||||
}
|
}
|
||||||
|
@ -9,11 +9,17 @@ import Foundation
|
|||||||
|
|
||||||
class AnalyticsParameter {
|
class AnalyticsParameter {
|
||||||
var name: String
|
var name: String
|
||||||
var type: String
|
var type: ParameterType
|
||||||
|
var value: String
|
||||||
|
var defaultValue: String
|
||||||
var replaceIn: [String] = []
|
var replaceIn: [String] = []
|
||||||
|
|
||||||
init(name: String, type: String) {
|
// MARK: - Init
|
||||||
|
|
||||||
|
init(name: String, type: ParameterType, value: String, defaultValue: String) {
|
||||||
self.name = name
|
self.name = name
|
||||||
self.type = type
|
self.type = type
|
||||||
|
self.value = value
|
||||||
|
self.defaultValue = defaultValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
15
Sources/ResgenSwift/Analytics/Model/ParameterType.swift
Normal file
15
Sources/ResgenSwift/Analytics/Model/ParameterType.swift
Normal 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"
|
||||||
|
}
|
16
Sources/ResgenSwift/Analytics/Model/TagType.swift
Normal file
16
Sources/ResgenSwift/Analytics/Model/TagType.swift
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
//
|
||||||
|
// TagType.swift
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Created by Thibaut Schmitt on 08/12/2023.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
extension AnalyticsDefinition {
|
||||||
|
|
||||||
|
enum TagType {
|
||||||
|
case screen
|
||||||
|
case event
|
||||||
|
}
|
||||||
|
}
|
29
Sources/ResgenSwift/Analytics/Model/TargetType.swift
Normal file
29
Sources/ResgenSwift/Analytics/Model/TargetType.swift
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
//
|
||||||
|
// TargetType.swift
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Created by Thibaut Schmitt on 08/12/2023.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
enum TrackerType: CaseIterable {
|
||||||
|
case matomo
|
||||||
|
case firebase
|
||||||
|
|
||||||
|
var value: String {
|
||||||
|
switch self {
|
||||||
|
case .matomo:
|
||||||
|
"matomo"
|
||||||
|
case .firebase:
|
||||||
|
"firebase"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static func hasValidTarget(in targets: String) -> Bool {
|
||||||
|
for tracker in Self.allCases where targets.contains(tracker.value) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
@ -14,46 +14,82 @@ class AnalyticsFileParser {
|
|||||||
|
|
||||||
private static func parseYaml() -> AnalyticsFile {
|
private static func parseYaml() -> AnalyticsFile {
|
||||||
guard let data = FileManager().contents(atPath: inputFile) else {
|
guard let data = FileManager().contents(atPath: inputFile) else {
|
||||||
let error = GenerateError.fileNotExists(inputFile)
|
let error = AnalyticsError.fileNotExists(inputFile)
|
||||||
Generate.exit(withError: error)
|
print(error.description)
|
||||||
|
Analytics.exit(withError: error)
|
||||||
}
|
}
|
||||||
|
|
||||||
do {
|
do {
|
||||||
let tagFile = try YAMLDecoder().decode(AnalyticsFile.self, from: data)
|
let tagFile = try YAMLDecoder().decode(AnalyticsFile.self, from: data)
|
||||||
return tagFile
|
return tagFile
|
||||||
} catch let error {
|
} catch {
|
||||||
Generate.exit(withError: error)
|
let error = AnalyticsError.parseFailed(error.localizedDescription)
|
||||||
|
print(error.description)
|
||||||
|
Analytics.exit(withError: error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func getParameters(fromData data: [AnalyticsParameterDTO]) -> [AnalyticsParameter] {
|
private static func getParameters(from parameters: [AnalyticsParameterDTO]) -> [AnalyticsParameter] {
|
||||||
var parameters: [AnalyticsParameter] = []
|
func verify(value: String?, for type: ParameterType) {
|
||||||
|
guard let value, value.isEmpty == false else { return }
|
||||||
data.forEach { value in
|
|
||||||
// Type
|
|
||||||
|
|
||||||
let type = value.type.uppercasedFirst()
|
switch type {
|
||||||
|
case .int:
|
||||||
guard
|
if Int(value) == nil {
|
||||||
type == "String" ||
|
let error = AnalyticsError.invalidParameter("type of \(value) is not \(type)")
|
||||||
type == "Int" ||
|
print(error.description)
|
||||||
type == "Double" ||
|
Analytics.exit(withError: error)
|
||||||
type == "Bool"
|
}
|
||||||
else {
|
case .bool:
|
||||||
let error = GenerateError.invalidParameter("type of \(value.name)")
|
if Bool(value.lowercased()) == nil {
|
||||||
Generate.exit(withError: error)
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
let parameter: AnalyticsParameter = AnalyticsParameter(name: value.name, type: type)
|
|
||||||
|
|
||||||
if let replaceIn = value.replaceIn {
|
|
||||||
parameter.replaceIn = replaceIn.components(separatedBy: ",")
|
|
||||||
}
|
|
||||||
|
|
||||||
parameters.append(parameter)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return parameters
|
return parameters.map { dtoParameter in
|
||||||
|
// Type
|
||||||
|
|
||||||
|
let type = dtoParameter.type.uppercasedFirst()
|
||||||
|
|
||||||
|
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: typeEnum,
|
||||||
|
value: dtoParameter.value ?? "",
|
||||||
|
defaultValue: dtoParameter.defaultValue ?? ""
|
||||||
|
)
|
||||||
|
|
||||||
|
if let replaceIn = dtoParameter.replaceIn {
|
||||||
|
parameter.replaceIn = replaceIn.components(separatedBy: ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
return parameter
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func getTagDefinition(
|
private static func getTagDefinition(
|
||||||
@ -64,24 +100,24 @@ class AnalyticsFileParser {
|
|||||||
comments: String?,
|
comments: String?,
|
||||||
parameters: [AnalyticsParameterDTO]?
|
parameters: [AnalyticsParameterDTO]?
|
||||||
) -> AnalyticsDefinition {
|
) -> AnalyticsDefinition {
|
||||||
let definition: AnalyticsDefinition = AnalyticsDefinition(id: id, name: name, type: type)
|
let definition = AnalyticsDefinition(id: id, name: name, type: type)
|
||||||
definition.tags = tags.components(separatedBy: ",")
|
definition.tags = tags
|
||||||
|
.components(separatedBy: ",")
|
||||||
|
.map { $0.removeLeadingTrailingWhitespace() }
|
||||||
|
|
||||||
if let comments = comments {
|
if let comments = comments {
|
||||||
definition.comments = comments
|
definition.comments = comments
|
||||||
}
|
}
|
||||||
|
|
||||||
if let parameters = parameters {
|
if let parameters = parameters {
|
||||||
definition.parameters = Self.getParameters(fromData: parameters)
|
definition.parameters = Self.getParameters(from: parameters)
|
||||||
}
|
}
|
||||||
|
|
||||||
return definition
|
return definition
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func getTagDefinitionScreen(fromData screens: [AnalyticsDefinitionScreenDTO]) -> [AnalyticsDefinition] {
|
private static func getTagDefinitionScreen(from screens: [AnalyticsDefinitionScreenDTO]) -> [AnalyticsDefinition] {
|
||||||
var definitions: [AnalyticsDefinition] = []
|
screens.map { screen in
|
||||||
|
|
||||||
for screen in screens {
|
|
||||||
let definition: AnalyticsDefinition = Self.getTagDefinition(
|
let definition: AnalyticsDefinition = Self.getTagDefinition(
|
||||||
id: screen.id,
|
id: screen.id,
|
||||||
name: screen.name,
|
name: screen.name,
|
||||||
@ -90,28 +126,27 @@ class AnalyticsFileParser {
|
|||||||
comments: screen.comments,
|
comments: screen.comments,
|
||||||
parameters: screen.parameters
|
parameters: screen.parameters
|
||||||
)
|
)
|
||||||
|
|
||||||
if target.contains(Analytics.TargetType.matomo.value) {
|
if target.contains(TrackerType.matomo.value) {
|
||||||
// Path
|
// Path
|
||||||
|
|
||||||
guard let path = screen.path else {
|
guard let path = screen.path else {
|
||||||
let error = GenerateError.missingElement("screen path")
|
let error = AnalyticsError.missingElement("screen path")
|
||||||
Generate.exit(withError: error)
|
print(error.description)
|
||||||
|
Analytics.exit(withError: error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
definition.path = path
|
||||||
|
} else if let path = screen.path {
|
||||||
definition.path = path
|
definition.path = path
|
||||||
}
|
}
|
||||||
|
|
||||||
definitions.append(definition)
|
return definition
|
||||||
}
|
}
|
||||||
|
|
||||||
return definitions
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func getTagDefinitionEvent(fromData events: [AnalyticsDefinitionEventDTO]) -> [AnalyticsDefinition] {
|
private static func getTagDefinitionEvent(from events: [AnalyticsDefinitionEventDTO]) -> [AnalyticsDefinition] {
|
||||||
var definitions: [AnalyticsDefinition] = []
|
events.map { event in
|
||||||
|
|
||||||
for event in events {
|
|
||||||
let definition: AnalyticsDefinition = Self.getTagDefinition(
|
let definition: AnalyticsDefinition = Self.getTagDefinition(
|
||||||
id: event.id,
|
id: event.id,
|
||||||
name: event.name,
|
name: event.name,
|
||||||
@ -120,54 +155,67 @@ class AnalyticsFileParser {
|
|||||||
comments: event.comments,
|
comments: event.comments,
|
||||||
parameters: event.parameters
|
parameters: event.parameters
|
||||||
)
|
)
|
||||||
|
|
||||||
if target.contains(Analytics.TargetType.matomo.value) {
|
if target.contains(TrackerType.matomo.value) {
|
||||||
// Category
|
// Category
|
||||||
|
|
||||||
guard let category = event.category else {
|
guard let category = event.category else {
|
||||||
let error = GenerateError.missingElement("event category")
|
let error = AnalyticsError.missingElement("event category")
|
||||||
Generate.exit(withError: error)
|
print(error.description)
|
||||||
|
Analytics.exit(withError: error)
|
||||||
}
|
}
|
||||||
|
|
||||||
definition.category = category
|
definition.category = category
|
||||||
|
|
||||||
// Action
|
// Action
|
||||||
|
|
||||||
guard let action = event.action else {
|
guard let action = event.action else {
|
||||||
let error = GenerateError.missingElement("event action")
|
let error = AnalyticsError.missingElement("event action")
|
||||||
Generate.exit(withError: error)
|
print(error.description)
|
||||||
|
Analytics.exit(withError: error)
|
||||||
|
}
|
||||||
|
|
||||||
|
definition.action = action
|
||||||
|
} else {
|
||||||
|
if let category = event.category {
|
||||||
|
definition.category = category
|
||||||
}
|
}
|
||||||
|
|
||||||
definition.action = action
|
if let action = event.action {
|
||||||
|
definition.action = action
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
definitions.append(definition)
|
return definition
|
||||||
}
|
}
|
||||||
|
|
||||||
return definitions
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static func parse(_ inputFile: String, target: String) -> [AnalyticsCategory] {
|
static func parse(_ inputFile: String, target: String) -> [AnalyticsCategory] {
|
||||||
self.inputFile = inputFile
|
self.inputFile = inputFile
|
||||||
self.target = target
|
self.target = target
|
||||||
|
|
||||||
let tagFile: AnalyticsFile = Self.parseYaml()
|
let tagFile = Self.parseYaml()
|
||||||
var sections: [AnalyticsCategory] = []
|
|
||||||
|
|
||||||
tagFile.categories.forEach { categorie in
|
return tagFile
|
||||||
let section: AnalyticsCategory = AnalyticsCategory(id: categorie.id)
|
.categories
|
||||||
|
.map { categorie in
|
||||||
if let screens = categorie.screens {
|
let section: AnalyticsCategory = AnalyticsCategory(id: categorie.id)
|
||||||
section.definitions.append(contentsOf: Self.getTagDefinitionScreen(fromData: screens))
|
|
||||||
|
if let screens = categorie.screens {
|
||||||
|
section
|
||||||
|
.definitions
|
||||||
|
.append(
|
||||||
|
contentsOf: Self.getTagDefinitionScreen(from: screens)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let events = categorie.events {
|
||||||
|
section
|
||||||
|
.definitions
|
||||||
|
.append(
|
||||||
|
contentsOf: Self.getTagDefinitionEvent(from: events)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return section
|
||||||
}
|
}
|
||||||
|
|
||||||
if let events = categorie.events {
|
|
||||||
section.definitions.append(contentsOf: Self.getTagDefinitionEvent(fromData: events))
|
|
||||||
}
|
|
||||||
|
|
||||||
sections.append(section)
|
|
||||||
}
|
|
||||||
|
|
||||||
return sections
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,22 +9,17 @@ import Foundation
|
|||||||
|
|
||||||
enum GenerateError: Error {
|
enum GenerateError: Error {
|
||||||
case fileNotExists(String)
|
case fileNotExists(String)
|
||||||
case invalidConfigurationFile(String)
|
case invalidConfigurationFile(String, String)
|
||||||
case commandError([String], String)
|
case commandError([String], String)
|
||||||
case writeFile(String, String)
|
case writeFile(String, String)
|
||||||
|
|
||||||
// Analytics
|
|
||||||
|
|
||||||
case missingElement(String)
|
|
||||||
case invalidParameter(String)
|
|
||||||
|
|
||||||
var description: String {
|
var description: String {
|
||||||
switch self {
|
switch self {
|
||||||
case .fileNotExists(let filename):
|
case .fileNotExists(let filename):
|
||||||
return "error: [\(Generate.toolName)] File \(filename) does not exists"
|
return "error: [\(Generate.toolName)] File \(filename) does not exists"
|
||||||
|
|
||||||
case .invalidConfigurationFile(let filename):
|
case .invalidConfigurationFile(let filename, let underneathErrorDescription):
|
||||||
return "error: [\(Generate.toolName)] File \(filename) is not a valid configuration file"
|
return "error: [\(Generate.toolName)] File \(filename) is not a valid configuration file. Underneath error: \(underneathErrorDescription)"
|
||||||
|
|
||||||
case .commandError(let command, let terminationStatus):
|
case .commandError(let command, let terminationStatus):
|
||||||
let readableCommand = command
|
let readableCommand = command
|
||||||
@ -34,12 +29,6 @@ enum GenerateError: Error {
|
|||||||
|
|
||||||
case .writeFile(let filename, let info):
|
case .writeFile(let filename, let info):
|
||||||
return "error: [\(Generate.toolName)] An error occured while writing file in \(filename): \(info)"
|
return "error: [\(Generate.toolName)] An error occured while writing file in \(filename): \(info)"
|
||||||
|
|
||||||
case .missingElement(let element):
|
|
||||||
return "error: [\(Generate.toolName)] Missing \(element) for Matomo"
|
|
||||||
|
|
||||||
case .invalidParameter(let reason):
|
|
||||||
return "error: [\(Generate.toolName)] Invalid parameter \(reason)"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -269,14 +269,22 @@ struct StringsConfiguration: Codable, CustomDebugStringConvertible {
|
|||||||
let extensionName: String?
|
let extensionName: String?
|
||||||
let extensionSuffix: String?
|
let extensionSuffix: String?
|
||||||
private let staticMembers: Bool?
|
private let staticMembers: Bool?
|
||||||
|
private let xcStrings: Bool?
|
||||||
|
|
||||||
var staticMembersOptions: Bool {
|
var staticMembersOptions: Bool {
|
||||||
if let staticMembers = staticMembers {
|
if let staticMembers = staticMembers {
|
||||||
return staticMembers
|
return staticMembers
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var xcStringsOptions: Bool {
|
||||||
|
if let xcStrings = xcStrings {
|
||||||
|
return xcStrings
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
internal init(inputFile: String,
|
internal init(inputFile: String,
|
||||||
outputPath: String,
|
outputPath: String,
|
||||||
langs: String,
|
langs: String,
|
||||||
@ -284,7 +292,8 @@ struct StringsConfiguration: Codable, CustomDebugStringConvertible {
|
|||||||
extensionOutputPath: String,
|
extensionOutputPath: String,
|
||||||
extensionName: String?,
|
extensionName: String?,
|
||||||
extensionSuffix: String?,
|
extensionSuffix: String?,
|
||||||
staticMembers: Bool?) {
|
staticMembers: Bool?,
|
||||||
|
xcStrings: Bool?) {
|
||||||
self.inputFile = inputFile
|
self.inputFile = inputFile
|
||||||
self.outputPath = outputPath
|
self.outputPath = outputPath
|
||||||
self.langs = langs
|
self.langs = langs
|
||||||
@ -293,6 +302,7 @@ struct StringsConfiguration: Codable, CustomDebugStringConvertible {
|
|||||||
self.extensionName = extensionName
|
self.extensionName = extensionName
|
||||||
self.extensionSuffix = extensionSuffix
|
self.extensionSuffix = extensionSuffix
|
||||||
self.staticMembers = staticMembers
|
self.staticMembers = staticMembers
|
||||||
|
self.xcStrings = xcStrings
|
||||||
}
|
}
|
||||||
|
|
||||||
var debugDescription: String {
|
var debugDescription: String {
|
||||||
|
@ -16,12 +16,15 @@ class ConfigurationFileParser {
|
|||||||
Generate.exit(withError: error)
|
Generate.exit(withError: error)
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let configuration = try? YAMLDecoder().decode(ConfigurationFile.self, from: data) else {
|
do {
|
||||||
let error = GenerateError.invalidConfigurationFile(configurationFile)
|
return try YAMLDecoder().decode(ConfigurationFile.self, from: data)
|
||||||
|
} catch {
|
||||||
|
let error = GenerateError.invalidConfigurationFile(
|
||||||
|
configurationFile,
|
||||||
|
error.localizedDescription.description
|
||||||
|
)
|
||||||
print(error.description)
|
print(error.description)
|
||||||
Generate.exit(withError: error)
|
Generate.exit(withError: error)
|
||||||
}
|
}
|
||||||
|
|
||||||
return configuration
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ extension StringsConfiguration: Runnable {
|
|||||||
if force {
|
if force {
|
||||||
args += ["-f"]
|
args += ["-f"]
|
||||||
}
|
}
|
||||||
|
|
||||||
args += [
|
args += [
|
||||||
inputFile.prependIfRelativePath(projectDirectory),
|
inputFile.prependIfRelativePath(projectDirectory),
|
||||||
"--output-path",
|
"--output-path",
|
||||||
@ -26,7 +26,9 @@ extension StringsConfiguration: Runnable {
|
|||||||
"--extension-output-path",
|
"--extension-output-path",
|
||||||
extensionOutputPath.prependIfRelativePath(projectDirectory),
|
extensionOutputPath.prependIfRelativePath(projectDirectory),
|
||||||
"--static-members",
|
"--static-members",
|
||||||
"\(staticMembersOptions)"
|
"\(staticMembersOptions)",
|
||||||
|
"--xc-strings",
|
||||||
|
"\(xcStringsOptions)"
|
||||||
]
|
]
|
||||||
|
|
||||||
if let extensionName = extensionName {
|
if let extensionName = extensionName {
|
||||||
|
@ -8,10 +8,13 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import ToolCore
|
import ToolCore
|
||||||
|
|
||||||
|
enum OutputImageExtension: String {
|
||||||
|
case png
|
||||||
|
case svg
|
||||||
|
}
|
||||||
|
|
||||||
class XcassetsGenerator {
|
class XcassetsGenerator {
|
||||||
|
|
||||||
static let outputImageExtension = "png"
|
|
||||||
|
|
||||||
let forceGeneration: Bool
|
let forceGeneration: Bool
|
||||||
|
|
||||||
// MARK: - Init
|
// MARK: - Init
|
||||||
@ -60,13 +63,20 @@ class XcassetsGenerator {
|
|||||||
generatedAssetsPaths.append(imagesetName)
|
generatedAssetsPaths.append(imagesetName)
|
||||||
|
|
||||||
// Generate output images path
|
// Generate output images path
|
||||||
let output1x = "\(imagesetPath)/\(parsedImage.name).\(XcassetsGenerator.outputImageExtension)"
|
let output1x = "\(imagesetPath)/\(parsedImage.name).\(OutputImageExtension.png.rawValue)"
|
||||||
let output2x = "\(imagesetPath)/\(parsedImage.name)@2x.\(XcassetsGenerator.outputImageExtension)"
|
let output2x = "\(imagesetPath)/\(parsedImage.name)@2x.\(OutputImageExtension.png.rawValue)"
|
||||||
let output3x = "\(imagesetPath)/\(parsedImage.name)@3x.\(XcassetsGenerator.outputImageExtension)"
|
let output3x = "\(imagesetPath)/\(parsedImage.name)@3x.\(OutputImageExtension.png.rawValue)"
|
||||||
|
|
||||||
// Check if we need to convert image
|
// 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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,29 +90,55 @@ class XcassetsGenerator {
|
|||||||
print(error.description)
|
print(error.description)
|
||||||
Images.exit(withError: error)
|
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
|
let convertArguments = parsedImage.convertArguments
|
||||||
|
|
||||||
if imageData.ext == "svg" {
|
if imageData.ext == "svg" {
|
||||||
// /usr/local/bin/rsvg-convert path/to/image.png -w 200 -h 300 -o path/to/output.png
|
if parsedImage.imageExtensions.contains(.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
|
// /usr/local/bin/rsvg-convert path/to/image.png -w 200 -h 300 -o path/to/output.png
|
||||||
var command1x = ["\(svgConverter)", "\(imageData.path)"]
|
// /usr/local/bin/rsvg-convert path/to/image.png -w 200 -o path/to/output.png
|
||||||
var command2x = ["\(svgConverter)", "\(imageData.path)"]
|
// /usr/local/bin/rsvg-convert path/to/image.png -h 300 -o path/to/output.png
|
||||||
var command3x = ["\(svgConverter)", "\(imageData.path)"]
|
var command1x = ["\(svgConverter)", "\(imageData.path)"]
|
||||||
|
var command2x = ["\(svgConverter)", "\(imageData.path)"]
|
||||||
self.addConvertArgument(command: &command1x, convertArgument: convertArguments.x1)
|
var command3x = ["\(svgConverter)", "\(imageData.path)"]
|
||||||
self.addConvertArgument(command: &command2x, convertArgument: convertArguments.x2)
|
|
||||||
self.addConvertArgument(command: &command3x, convertArgument: convertArguments.x3)
|
self.addConvertArgument(command: &command1x, convertArgument: convertArguments.x1)
|
||||||
|
self.addConvertArgument(command: &command2x, convertArgument: convertArguments.x2)
|
||||||
command1x.append(contentsOf: ["-o", output1x])
|
self.addConvertArgument(command: &command3x, convertArgument: convertArguments.x3)
|
||||||
command2x.append(contentsOf: ["-o", output2x])
|
|
||||||
command3x.append(contentsOf: ["-o", output3x])
|
command1x.append(contentsOf: ["-o", output1x])
|
||||||
|
command2x.append(contentsOf: ["-o", output2x])
|
||||||
Shell.shell(command1x)
|
command3x.append(contentsOf: ["-o", output3x])
|
||||||
Shell.shell(command2x)
|
|
||||||
Shell.shell(command3x)
|
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 {
|
} else {
|
||||||
// convert path/to/image.png -resize 200x300 path/to/output.png
|
// convert path/to/image.png -resize 200x300 path/to/output.png
|
||||||
// convert path/to/image.png -resize 200x path/to/output.png
|
// convert path/to/image.png -resize 200x path/to/output.png
|
||||||
@ -119,7 +155,7 @@ class XcassetsGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Write Content.json
|
// Write Content.json
|
||||||
let imagesetContentJson = parsedImage.contentJson
|
guard let imagesetContentJson = parsedImage.generateContentJson(isVector: imageData.ext == "svg") else { return }
|
||||||
let contentJsonFilePath = "\(imagesetPath)/Contents.json"
|
let contentJsonFilePath = "\(imagesetPath)/Contents.json"
|
||||||
|
|
||||||
let contentJsonFilePathURL = URL(fileURLWithPath: contentJsonFilePath)
|
let contentJsonFilePathURL = URL(fileURLWithPath: contentJsonFilePath)
|
||||||
@ -161,8 +197,8 @@ class XcassetsGenerator {
|
|||||||
|
|
||||||
// MARK: - Helpers: bypass generation
|
// MARK: - Helpers: bypass generation
|
||||||
|
|
||||||
private func shouldGenerate(inputImagePath: String, xcassetImagePath: String) -> Bool {
|
private func shouldGenerate(inputImagePath: String, xcassetImagePath: String, needToGenerateForSvg: Bool) -> Bool {
|
||||||
if forceGeneration {
|
if forceGeneration || needToGenerateForSvg {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
76
Sources/ResgenSwift/Images/Model/ImageContent.swift
Normal file
76
Sources/ResgenSwift/Images/Model/ImageContent.swift
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
//
|
||||||
|
// ImageContent.swift
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Created by Quentin Bandera on 19/04/2024.
|
||||||
|
//
|
||||||
|
|
||||||
|
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.filename < $1.filename })
|
||||||
|
let rhsImages = rhs.images.sorted(by: { $0.filename < $1.filename })
|
||||||
|
|
||||||
|
return lhsImages == rhsImages
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AssetImageDescription: Codable, Equatable {
|
||||||
|
let idiom: 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"
|
||||||
|
}
|
||||||
|
}
|
@ -7,12 +7,31 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
enum ImageExtension: String {
|
||||||
|
case png
|
||||||
|
}
|
||||||
|
|
||||||
struct ParsedImage {
|
struct ParsedImage {
|
||||||
let name: String
|
let name: String
|
||||||
let tags: String
|
let tags: String
|
||||||
let width: Int
|
let width: Int
|
||||||
let height: 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
|
// MARK: - Convert
|
||||||
|
|
||||||
var convertArguments: (x1: ConvertArgument, x2: ConvertArgument, x3: ConvertArgument) {
|
var convertArguments: (x1: ConvertArgument, x2: ConvertArgument, x3: ConvertArgument) {
|
||||||
@ -42,34 +61,69 @@ struct ParsedImage {
|
|||||||
|
|
||||||
// MARK: - Assets
|
// MARK: - Assets
|
||||||
|
|
||||||
var contentJson: String {
|
func generateContentJson(isVector: Bool) -> String? {
|
||||||
"""
|
let encoder = JSONEncoder()
|
||||||
{
|
encoder.outputFormatting = .prettyPrinted
|
||||||
"images" : [
|
|
||||||
{
|
let imageContent = generateImageContent(isVector: isVector)
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "1x",
|
guard let data = try? encoder.encode(imageContent) else {
|
||||||
"filename" : "\(name).\(XcassetsGenerator.outputImageExtension)"
|
let error = ImagesError.writeFile("Contents.json", "Error encoding json file")
|
||||||
},
|
print(error.description)
|
||||||
{
|
Images.exit(withError: error)
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "2x",
|
|
||||||
"filename" : "\(name)@2x.\(XcassetsGenerator.outputImageExtension)"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "3x",
|
|
||||||
"filename" : "\(name)@3x.\(XcassetsGenerator.outputImageExtension)"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"version" : 1,
|
|
||||||
"author" : "ResgenSwift-Imagium"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
"""
|
|
||||||
|
return String(data: data, encoding: .utf8)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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"
|
||||||
|
),
|
||||||
|
properties: AssetProperties(
|
||||||
|
preservesVectorRepresentation: true,
|
||||||
|
templateRenderingIntent: .original
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} 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
|
// MARK: - Extension property
|
||||||
|
|
||||||
func getImageProperty(isStatic: Bool, isSwiftUI: Bool) -> String {
|
func getImageProperty(isStatic: Bool, isSwiftUI: Bool) -> String {
|
||||||
|
@ -38,11 +38,21 @@ class ImageFileParser {
|
|||||||
}
|
}
|
||||||
return Int(splittedLine[3])!
|
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)
|
imagesToGenerate.append(image)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
print(imagesToGenerate)
|
||||||
|
|
||||||
return imagesToGenerate.filter {
|
return imagesToGenerate.filter {
|
||||||
$0.tags.contains(platform.rawValue)
|
$0.tags.contains(platform.rawValue)
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
//
|
//
|
||||||
// StringsFileGenerator.swift
|
// StringsFileGenerator.swift
|
||||||
//
|
//
|
||||||
//
|
//
|
||||||
// Created by Thibaut Schmitt on 04/01/2022.
|
// Created by Thibaut Schmitt on 04/01/2022.
|
||||||
//
|
//
|
||||||
@ -9,15 +9,16 @@ import Foundation
|
|||||||
import ToolCore
|
import ToolCore
|
||||||
|
|
||||||
class StringsFileGenerator {
|
class StringsFileGenerator {
|
||||||
|
|
||||||
// MARK: - Strings Files
|
// MARK: - Strings Files
|
||||||
|
|
||||||
static func writeStringsFiles(sections: [Section],
|
static func writeStringsFiles(sections: [Section],
|
||||||
langs: [String],
|
langs: [String],
|
||||||
defaultLang: String,
|
defaultLang: String,
|
||||||
tags: [String],
|
tags: [String],
|
||||||
outputPath: String,
|
outputPath: String,
|
||||||
inputFilenameWithoutExt: String) {
|
inputFilenameWithoutExt: String) {
|
||||||
|
|
||||||
var stringsFilesContent = [String: String]()
|
var stringsFilesContent = [String: String]()
|
||||||
for lang in langs {
|
for lang in langs {
|
||||||
stringsFilesContent[lang] = Self.generateStringsFileContent(lang: lang,
|
stringsFilesContent[lang] = Self.generateStringsFileContent(lang: lang,
|
||||||
@ -25,11 +26,11 @@ class StringsFileGenerator {
|
|||||||
tags: tags,
|
tags: tags,
|
||||||
sections: sections)
|
sections: sections)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write strings file content
|
// Write strings file content
|
||||||
langs.forEach { lang in
|
langs.forEach { lang in
|
||||||
guard let fileContent = stringsFilesContent[lang] else { return }
|
guard let fileContent = stringsFilesContent[lang] else { return }
|
||||||
|
|
||||||
let stringsFilePath = "\(outputPath)/\(lang).lproj/\(inputFilenameWithoutExt).strings"
|
let stringsFilePath = "\(outputPath)/\(lang).lproj/\(inputFilenameWithoutExt).strings"
|
||||||
let stringsFilePathURL = URL(fileURLWithPath: stringsFilePath)
|
let stringsFilePathURL = URL(fileURLWithPath: stringsFilePath)
|
||||||
do {
|
do {
|
||||||
@ -41,7 +42,32 @@ class StringsFileGenerator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static func writeXcStringsFiles(sections: [Section],
|
||||||
|
langs: [String],
|
||||||
|
defaultLang: String,
|
||||||
|
tags: [String],
|
||||||
|
outputPath: String,
|
||||||
|
inputFilenameWithoutExt: String) {
|
||||||
|
|
||||||
|
let fileContent: String = Self.generateXcStringsFileContent(
|
||||||
|
langs: langs,
|
||||||
|
defaultLang: defaultLang,
|
||||||
|
tags: tags,
|
||||||
|
sections: sections
|
||||||
|
)
|
||||||
|
|
||||||
|
let stringsFilePath = "\(outputPath)/\(inputFilenameWithoutExt).xcstrings"
|
||||||
|
let stringsFilePathURL = URL(fileURLWithPath: stringsFilePath)
|
||||||
|
do {
|
||||||
|
try fileContent.write(to: stringsFilePathURL, atomically: false, encoding: .utf8)
|
||||||
|
} catch let error {
|
||||||
|
let error = StringiumError.writeFile(error.localizedDescription, stringsFilePath)
|
||||||
|
print(error.description)
|
||||||
|
Stringium.exit(withError: error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static func generateStringsFileContent(lang: String,
|
static func generateStringsFileContent(lang: String,
|
||||||
defaultLang: String,
|
defaultLang: String,
|
||||||
tags inputTags: [String],
|
tags inputTags: [String],
|
||||||
@ -53,13 +79,13 @@ class StringsFileGenerator {
|
|||||||
* Language: \(lang)
|
* Language: \(lang)
|
||||||
*/\n
|
*/\n
|
||||||
"""
|
"""
|
||||||
|
|
||||||
sections.forEach { section in
|
sections.forEach { section in
|
||||||
// Check that at least one string will be generated
|
// Check that at least one string will be generated
|
||||||
guard section.hasOneOrMoreMatchingTags(tags: inputTags) else {
|
guard section.hasOneOrMoreMatchingTags(tags: inputTags) else {
|
||||||
return // Go to next section
|
return // Go to next section
|
||||||
}
|
}
|
||||||
|
|
||||||
stringsFileContent += "\n/********** \(section.name) **********/\n\n"
|
stringsFileContent += "\n/********** \(section.name) **********/\n\n"
|
||||||
section.definitions.forEach { definition in
|
section.definitions.forEach { definition in
|
||||||
var skipDefinition = false // Set to true if not matching tag
|
var skipDefinition = false // Set to true if not matching tag
|
||||||
@ -69,16 +95,16 @@ class StringsFileGenerator {
|
|||||||
skipDefinition = true
|
skipDefinition = true
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// If tags contains `noTranslationTag` => get default lang
|
// If tags contains `noTranslationTag` => get default lang
|
||||||
if definition.tags.contains(Stringium.noTranslationTag) {
|
if definition.tags.contains(Stringium.noTranslationTag) {
|
||||||
return definition.translations[defaultLang]
|
return definition.translations[defaultLang]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Else: get specific lang
|
// Else: get specific lang
|
||||||
return definition.translations[lang]
|
return definition.translations[lang]
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if let translation = translationOpt {
|
if let translation = translationOpt {
|
||||||
stringsFileContent += "\"\(definition.name)\" = \"\(translation)\";\n\n"
|
stringsFileContent += "\"\(definition.name)\" = \"\(translation)\";\n\n"
|
||||||
} else if skipDefinition == false {
|
} else if skipDefinition == false {
|
||||||
@ -88,12 +114,123 @@ class StringsFileGenerator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return stringsFileContent
|
return stringsFileContent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - XcStrings Generation
|
||||||
|
|
||||||
|
static func generateXcStringsFileContent(langs: [String],
|
||||||
|
defaultLang: String,
|
||||||
|
tags inputTags: [String],
|
||||||
|
sections: [Section]) -> String {
|
||||||
|
let rootObject = generateRootObject(langs: langs, defaultLang: defaultLang, tags: inputTags, sections: sections)
|
||||||
|
let file = generateXcStringsFileContentFromRootObject(rootObject: rootObject)
|
||||||
|
|
||||||
|
return file
|
||||||
|
}
|
||||||
|
|
||||||
|
static func generateXcStringsFileContentFromRootObject(rootObject: Root) -> String {
|
||||||
|
do {
|
||||||
|
let encoder = JSONEncoder()
|
||||||
|
encoder.outputFormatting = [.prettyPrinted]
|
||||||
|
|
||||||
|
let json = try encoder.encode(rootObject)
|
||||||
|
|
||||||
|
if let jsonString = String(data: json, encoding: .utf8) {
|
||||||
|
return jsonString
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch {
|
||||||
|
debugPrint("Failed to encode: \(error)")
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
static func generateRootObject(langs: [String],
|
||||||
|
defaultLang: String,
|
||||||
|
tags inputTags: [String],
|
||||||
|
sections: [Section]) -> Root {
|
||||||
|
|
||||||
|
var xcStringDefinitionTab: [XCStringDefinition] = []
|
||||||
|
|
||||||
|
sections.forEach { section in
|
||||||
|
// Check that at least one string will be generated
|
||||||
|
guard section.hasOneOrMoreMatchingTags(tags: inputTags) else {
|
||||||
|
return // Go to next section
|
||||||
|
}
|
||||||
|
|
||||||
|
section.definitions.forEach { definition in
|
||||||
|
var skipDefinition = false
|
||||||
|
var isNoTranslation = false
|
||||||
|
|
||||||
|
var localizationTab: [XCStringLocalization] = []
|
||||||
|
|
||||||
|
if definition.hasOneOrMoreMatchingTags(inputTags: inputTags) == false {
|
||||||
|
skipDefinition = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if definition.tags.contains(Stringium.noTranslationTag) {
|
||||||
|
isNoTranslation = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if !skipDefinition {
|
||||||
|
if isNoTranslation {
|
||||||
|
// Search for langs in yaml
|
||||||
|
for lang in langs {
|
||||||
|
if let value = definition.translations[defaultLang], !value.isEmpty {
|
||||||
|
let localization = XCStringLocalization(
|
||||||
|
lang: lang,
|
||||||
|
content: XCStringLocalizationLangContent(
|
||||||
|
stringUnit: DefaultStringUnit(state: "translated", value: value)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
localizationTab.append(localization)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Search for langs in twine
|
||||||
|
for (lang, value) in definition.translations where !value.isEmpty {
|
||||||
|
|
||||||
|
let localization = XCStringLocalization(
|
||||||
|
lang: lang,
|
||||||
|
content: XCStringLocalizationLangContent(
|
||||||
|
stringUnit: DefaultStringUnit(state: "translated", value: value)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
localizationTab.append(localization)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let xcStringDefinition = XCStringDefinition(
|
||||||
|
title: definition.name,
|
||||||
|
content: XCStringDefinitionContent(
|
||||||
|
comment: definition.comment,
|
||||||
|
extractionState: "manual",
|
||||||
|
localizations: XCStringLocalizationContainer(
|
||||||
|
localizations: localizationTab
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
xcStringDefinitionTab.append(xcStringDefinition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let xcStringContainer = XCStringDefinitionContainer(strings: xcStringDefinitionTab)
|
||||||
|
|
||||||
|
return Root(
|
||||||
|
sourceLanguage: defaultLang,
|
||||||
|
strings: xcStringContainer,
|
||||||
|
version: "1.0"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Extension file
|
// MARK: - Extension file
|
||||||
|
|
||||||
static func writeExtensionFiles(sections: [Section],
|
static func writeExtensionFiles(sections: [Section],
|
||||||
defaultLang lang: String,
|
defaultLang lang: String,
|
||||||
tags: [String],
|
tags: [String],
|
||||||
@ -110,7 +247,7 @@ class StringsFileGenerator {
|
|||||||
inputFilename: inputFilename,
|
inputFilename: inputFilename,
|
||||||
extensionName: extensionName,
|
extensionName: extensionName,
|
||||||
extensionSuffix: extensionSuffix)
|
extensionSuffix: extensionSuffix)
|
||||||
|
|
||||||
// Write content
|
// Write content
|
||||||
let extensionFilePathURL = URL(fileURLWithPath: extensionFilePath)
|
let extensionFilePathURL = URL(fileURLWithPath: extensionFilePath)
|
||||||
do {
|
do {
|
||||||
@ -121,9 +258,9 @@ class StringsFileGenerator {
|
|||||||
Stringium.exit(withError: error)
|
Stringium.exit(withError: error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Extension content
|
// MARK: - Extension content
|
||||||
|
|
||||||
static func getExtensionContent(sections: [Section],
|
static func getExtensionContent(sections: [Section],
|
||||||
defaultLang lang: String,
|
defaultLang lang: String,
|
||||||
tags: [String],
|
tags: [String],
|
||||||
@ -139,31 +276,31 @@ class StringsFileGenerator {
|
|||||||
]
|
]
|
||||||
.joined(separator: "\n")
|
.joined(separator: "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Extension part
|
// MARK: - Extension part
|
||||||
|
|
||||||
private static func getHeader(stringsFilename: String, extensionClassname: String) -> String {
|
private static func getHeader(stringsFilename: String, extensionClassname: String) -> String {
|
||||||
"""
|
"""
|
||||||
// Generated by ResgenSwift.Strings.\(Stringium.toolName) \(ResgenSwiftVersion)
|
// Generated by ResgenSwift.Strings.\(Stringium.toolName) \(ResgenSwiftVersion)
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
fileprivate let kStringsFileName = "\(stringsFilename)"
|
fileprivate let kStringsFileName = "\(stringsFilename)"
|
||||||
|
|
||||||
extension \(extensionClassname) {
|
extension \(extensionClassname) {
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func getEnumKey(sections: [Section], tags: [String], extensionClassname: String, extensionSuffix: String) -> String {
|
private static func getEnumKey(sections: [Section], tags: [String], extensionClassname: String, extensionSuffix: String) -> String {
|
||||||
var enumDefinition = "\n enum Key\(extensionSuffix.uppercasedFirst()): String {\n"
|
var enumDefinition = "\n enum Key\(extensionSuffix.uppercasedFirst()): String {\n"
|
||||||
|
|
||||||
// Enum
|
// Enum
|
||||||
sections.forEach { section in
|
sections.forEach { section in
|
||||||
// Check that at least one string will be generated
|
// Check that at least one string will be generated
|
||||||
guard section.hasOneOrMoreMatchingTags(tags: tags) else {
|
guard section.hasOneOrMoreMatchingTags(tags: tags) else {
|
||||||
return // Go to next section
|
return // Go to next section
|
||||||
}
|
}
|
||||||
|
|
||||||
section.definitions.forEach { definition in
|
section.definitions.forEach { definition in
|
||||||
guard definition.hasOneOrMoreMatchingTags(inputTags: tags) == true else {
|
guard definition.hasOneOrMoreMatchingTags(inputTags: tags) == true else {
|
||||||
return // Go to next definition
|
return // Go to next definition
|
||||||
@ -172,7 +309,7 @@ class StringsFileGenerator {
|
|||||||
enumDefinition += " case \(definition.name) = \"\(definition.name)\"\n"
|
enumDefinition += " case \(definition.name) = \"\(definition.name)\"\n"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// KeyPath accessors
|
// KeyPath accessors
|
||||||
enumDefinition += "\n"
|
enumDefinition += "\n"
|
||||||
enumDefinition += " var keyPath: KeyPath<\(extensionClassname), String> {\n"
|
enumDefinition += " var keyPath: KeyPath<\(extensionClassname), String> {\n"
|
||||||
@ -182,7 +319,7 @@ class StringsFileGenerator {
|
|||||||
guard section.hasOneOrMoreMatchingTags(tags: tags) else {
|
guard section.hasOneOrMoreMatchingTags(tags: tags) else {
|
||||||
return // Go to next section
|
return // Go to next section
|
||||||
}
|
}
|
||||||
|
|
||||||
section.definitions.forEach { definition in
|
section.definitions.forEach { definition in
|
||||||
guard definition.hasOneOrMoreMatchingTags(inputTags: tags) == true else {
|
guard definition.hasOneOrMoreMatchingTags(inputTags: tags) == true else {
|
||||||
return // Go to next definition
|
return // Go to next definition
|
||||||
@ -194,23 +331,23 @@ class StringsFileGenerator {
|
|||||||
enumDefinition += " }\n" // Switch
|
enumDefinition += " }\n" // Switch
|
||||||
enumDefinition += " }\n" // var keyPath
|
enumDefinition += " }\n" // var keyPath
|
||||||
enumDefinition += " }" // Enum
|
enumDefinition += " }" // Enum
|
||||||
|
|
||||||
return enumDefinition
|
return enumDefinition
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func getProperties(sections: [Section], defaultLang lang: String, tags: [String], staticVar: Bool) -> String {
|
private static func getProperties(sections: [Section], defaultLang lang: String, tags: [String], staticVar: Bool) -> String {
|
||||||
sections.compactMap { section in
|
sections.compactMap { section in
|
||||||
// Check that at least one string will be generated
|
// Check that at least one string will be generated
|
||||||
guard section.hasOneOrMoreMatchingTags(tags: tags) else {
|
guard section.hasOneOrMoreMatchingTags(tags: tags) else {
|
||||||
return nil // Go to next section
|
return nil // Go to next section
|
||||||
}
|
}
|
||||||
|
|
||||||
var res = "\n // MARK: - \(section.name)\n"
|
var res = "\n // MARK: - \(section.name)\n"
|
||||||
res += section.definitions.compactMap { definition in
|
res += section.definitions.compactMap { definition in
|
||||||
guard definition.hasOneOrMoreMatchingTags(inputTags: tags) == true else {
|
guard definition.hasOneOrMoreMatchingTags(inputTags: tags) == true else {
|
||||||
return nil // Go to next definition
|
return nil // Go to next definition
|
||||||
}
|
}
|
||||||
|
|
||||||
if staticVar {
|
if staticVar {
|
||||||
return "\n\(definition.getNSLocalizedStringStaticProperty(forLang: lang))"
|
return "\n\(definition.getNSLocalizedStringStaticProperty(forLang: lang))"
|
||||||
}
|
}
|
||||||
@ -221,11 +358,11 @@ class StringsFileGenerator {
|
|||||||
}
|
}
|
||||||
.joined(separator: "\n")
|
.joined(separator: "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func getFooter() -> String {
|
private static func getFooter() -> String {
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -84,22 +84,29 @@ class Definition {
|
|||||||
return (inputParameters: inputParameters, translationArguments: translationArguments)
|
return (inputParameters: inputParameters, translationArguments: translationArguments)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func getBaseProperty(lang: String, translation: String, isStatic: Bool) -> String {
|
private func getBaseProperty(lang: String, translation: String, isStatic: Bool, comment: String?) -> String {
|
||||||
"""
|
"""
|
||||||
/// Translation in \(lang) :
|
/// Translation in \(lang) :
|
||||||
/// \(translation)
|
/// \(translation)
|
||||||
|
///
|
||||||
|
/// Comment :
|
||||||
|
/// \(comment?.isEmpty == false ? comment! : "No comment")
|
||||||
\(isStatic ? "static ": "")var \(name): String {
|
\(isStatic ? "static ": "")var \(name): String {
|
||||||
NSLocalizedString("\(name)", tableName: kStringsFileName, bundle: Bundle.main, value: "\(translation)", comment: "")
|
NSLocalizedString("\(name)", tableName: kStringsFileName, bundle: Bundle.main, value: "\(translation)", comment: "\(comment ?? "")")
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func getBaseMethod(lang: String, translation: String, isStatic: Bool, inputParameters: [String], translationArguments: [String]) -> String {
|
private func getBaseMethod(lang: String, translation: String, isStatic: Bool, inputParameters: [String], translationArguments: [String], comment: String?) -> String {
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
/// Translation in \(lang) :
|
/// Translation in \(lang) :
|
||||||
/// \(translation)
|
/// \(translation)
|
||||||
|
///
|
||||||
|
/// Comment :
|
||||||
|
/// \(comment?.isEmpty == false ? comment! : "No comment")
|
||||||
\(isStatic ? "static ": "")func \(name)(\(inputParameters.joined(separator: ", "))) -> String {
|
\(isStatic ? "static ": "")func \(name)(\(inputParameters.joined(separator: ", "))) -> String {
|
||||||
String(format: \(isStatic ? "Self" : "self").\(name), \(translationArguments.joined(separator: ", ")))
|
String(format: \(isStatic ? "Self" : "self").\(name), \(translationArguments.joined(separator: ", ")))
|
||||||
}
|
}
|
||||||
@ -114,8 +121,13 @@ class Definition {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Generate property
|
// Generate property
|
||||||
let property = getBaseProperty(lang: lang, translation: translation, isStatic: false)
|
let property = getBaseProperty(
|
||||||
|
lang: lang,
|
||||||
|
translation: translation,
|
||||||
|
isStatic: false,
|
||||||
|
comment: self.comment
|
||||||
|
)
|
||||||
|
|
||||||
// Generate method
|
// Generate method
|
||||||
var method = ""
|
var method = ""
|
||||||
if let parameters = self.getStringParameters(input: translation) {
|
if let parameters = self.getStringParameters(input: translation) {
|
||||||
@ -123,7 +135,8 @@ class Definition {
|
|||||||
translation: translation,
|
translation: translation,
|
||||||
isStatic: false,
|
isStatic: false,
|
||||||
inputParameters: parameters.inputParameters,
|
inputParameters: parameters.inputParameters,
|
||||||
translationArguments: parameters.translationArguments)
|
translationArguments: parameters.translationArguments,
|
||||||
|
comment: self.comment)
|
||||||
}
|
}
|
||||||
|
|
||||||
return property + method
|
return property + method
|
||||||
@ -137,7 +150,12 @@ class Definition {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Generate property
|
// Generate property
|
||||||
let property = getBaseProperty(lang: lang, translation: translation, isStatic: true)
|
let property = getBaseProperty(
|
||||||
|
lang: lang,
|
||||||
|
translation: translation,
|
||||||
|
isStatic: true,
|
||||||
|
comment: self.comment
|
||||||
|
)
|
||||||
|
|
||||||
// Generate method
|
// Generate method
|
||||||
var method = ""
|
var method = ""
|
||||||
@ -146,7 +164,8 @@ class Definition {
|
|||||||
translation: translation,
|
translation: translation,
|
||||||
isStatic: true,
|
isStatic: true,
|
||||||
inputParameters: parameters.inputParameters,
|
inputParameters: parameters.inputParameters,
|
||||||
translationArguments: parameters.translationArguments)
|
translationArguments: parameters.translationArguments,
|
||||||
|
comment: self.comment)
|
||||||
}
|
}
|
||||||
|
|
||||||
return property + method
|
return property + method
|
||||||
@ -160,10 +179,14 @@ class Definition {
|
|||||||
print(error.description)
|
print(error.description)
|
||||||
Stringium.exit(withError: error)
|
Stringium.exit(withError: error)
|
||||||
}
|
}
|
||||||
|
|
||||||
return """
|
return """
|
||||||
/// Translation in \(lang) :
|
/// Translation in \(lang) :
|
||||||
/// \(translation)
|
/// \(translation)
|
||||||
|
///
|
||||||
|
/// Comment :
|
||||||
|
/// \(comment?.isEmpty == false ? comment! : "No comment")
|
||||||
|
|
||||||
var \(name): String {
|
var \(name): String {
|
||||||
"\(translation)"
|
"\(translation)"
|
||||||
}
|
}
|
||||||
@ -180,6 +203,9 @@ class Definition {
|
|||||||
return """
|
return """
|
||||||
/// Translation in \(lang) :
|
/// Translation in \(lang) :
|
||||||
/// \(translation)
|
/// \(translation)
|
||||||
|
///
|
||||||
|
/// Comment :
|
||||||
|
/// \(comment?.isEmpty == false ? comment! : "No comment")
|
||||||
static var \(name): String {
|
static var \(name): String {
|
||||||
"\(translation)"
|
"\(translation)"
|
||||||
}
|
}
|
||||||
|
109
Sources/ResgenSwift/Strings/Model/XcString.swift
Normal file
109
Sources/ResgenSwift/Strings/Model/XcString.swift
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
//
|
||||||
|
// XcString.swift
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Created by Quentin Bandera on 12/04/2024.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct DynamicKey: CodingKey {
|
||||||
|
var intValue: Int?
|
||||||
|
init?(intValue: Int) {
|
||||||
|
self.intValue = intValue
|
||||||
|
self.stringValue = "\(intValue)"
|
||||||
|
}
|
||||||
|
|
||||||
|
var stringValue: String
|
||||||
|
init?(stringValue: String) {
|
||||||
|
self.stringValue = stringValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Root: Codable, Equatable {
|
||||||
|
let sourceLanguage: String
|
||||||
|
let strings: XCStringDefinitionContainer
|
||||||
|
let version: String
|
||||||
|
}
|
||||||
|
|
||||||
|
struct XCStringDefinitionContainer: Codable, Equatable {
|
||||||
|
let strings: [XCStringDefinition]
|
||||||
|
|
||||||
|
func encode(to encoder: Encoder) throws {
|
||||||
|
var container = encoder.container(keyedBy: DynamicKey.self)
|
||||||
|
|
||||||
|
for str in strings {
|
||||||
|
if let codingKey = DynamicKey(stringValue: str.title) {
|
||||||
|
try container.encode(str.content, forKey: codingKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static func == (lhs: XCStringDefinitionContainer, rhs: XCStringDefinitionContainer) -> Bool {
|
||||||
|
return lhs.strings.sorted(by: {
|
||||||
|
$0.title < $1.title
|
||||||
|
}) == rhs.strings.sorted(by: { $0.title < $1.title })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct XCStringDefinition: Codable, Equatable {
|
||||||
|
let title: String // json key -> custom encoding methods
|
||||||
|
let content: XCStringDefinitionContent
|
||||||
|
}
|
||||||
|
|
||||||
|
struct XCStringDefinitionContent: Codable, Equatable {
|
||||||
|
let comment: String?
|
||||||
|
let extractionState: String
|
||||||
|
var localizations: XCStringLocalizationContainer
|
||||||
|
|
||||||
|
init(comment: String? = nil, extractionState: String, localizations: XCStringLocalizationContainer) {
|
||||||
|
self.comment = comment
|
||||||
|
self.extractionState = extractionState
|
||||||
|
self.localizations = localizations
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct XCStringLocalizationContainer: Codable, Equatable {
|
||||||
|
let localizations: [XCStringLocalization]
|
||||||
|
|
||||||
|
func encode(to encoder: Encoder) throws {
|
||||||
|
var container = encoder.container(keyedBy: DynamicKey.self)
|
||||||
|
|
||||||
|
for loca in localizations {
|
||||||
|
if let codingKey = DynamicKey(stringValue: loca.lang) {
|
||||||
|
try container.encode(loca.content, forKey: codingKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static func == (lhs: XCStringLocalizationContainer, rhs: XCStringLocalizationContainer) -> Bool {
|
||||||
|
return lhs.localizations.count == rhs.localizations.count && lhs.localizations.sorted(by: { $0.lang < $1.lang }) == rhs.localizations.sorted(by: { $0.lang < $1.lang })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct XCStringLocalization: Codable, Equatable {
|
||||||
|
let lang: String // json key -> custom encoding method
|
||||||
|
let content: XCStringLocalizationLangContent
|
||||||
|
}
|
||||||
|
|
||||||
|
struct XCStringLocalizationLangContent: Codable, Equatable {
|
||||||
|
let stringUnit: DefaultStringUnit
|
||||||
|
}
|
||||||
|
|
||||||
|
//enum VarationOrStringUnit: Encodable {
|
||||||
|
// case variations([Varation])
|
||||||
|
// case stringUnit: (DefaultStringUnit)
|
||||||
|
//
|
||||||
|
// func encode(to encoder: any Encoder) throws {
|
||||||
|
// if let varations {
|
||||||
|
//
|
||||||
|
// } else if let {
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
struct DefaultStringUnit: Codable, Equatable {
|
||||||
|
let state: String
|
||||||
|
let value: String
|
||||||
|
}
|
@ -43,13 +43,26 @@ struct Stringium: ParsableCommand {
|
|||||||
let sections = TwineFileParser.parse(options.inputFile)
|
let sections = TwineFileParser.parse(options.inputFile)
|
||||||
|
|
||||||
// Generate strings files
|
// Generate strings files
|
||||||
StringsFileGenerator.writeStringsFiles(sections: sections,
|
print(options.xcStrings)
|
||||||
langs: options.langs,
|
if !options.xcStrings {
|
||||||
defaultLang: options.defaultLang,
|
print("[\(Self.toolName)] Will generate strings")
|
||||||
tags: options.tags,
|
|
||||||
outputPath: options.stringsFileOutputPath,
|
StringsFileGenerator.writeStringsFiles(sections: sections,
|
||||||
inputFilenameWithoutExt: options.inputFilenameWithoutExt)
|
langs: options.langs,
|
||||||
|
defaultLang: options.defaultLang,
|
||||||
|
tags: options.tags,
|
||||||
|
outputPath: options.stringsFileOutputPath,
|
||||||
|
inputFilenameWithoutExt: options.inputFilenameWithoutExt)
|
||||||
|
} else {
|
||||||
|
print("[\(Self.toolName)] Will generate xcStrings")
|
||||||
|
StringsFileGenerator.writeXcStringsFiles(sections: sections,
|
||||||
|
langs: options.langs,
|
||||||
|
defaultLang: options.defaultLang,
|
||||||
|
tags: options.tags,
|
||||||
|
outputPath: options.stringsFileOutputPath,
|
||||||
|
inputFilenameWithoutExt: options.inputFilenameWithoutExt)
|
||||||
|
}
|
||||||
|
|
||||||
// Generate extension
|
// Generate extension
|
||||||
StringsFileGenerator.writeExtensionFiles(sections: sections,
|
StringsFileGenerator.writeExtensionFiles(sections: sections,
|
||||||
defaultLang: options.defaultLang,
|
defaultLang: options.defaultLang,
|
||||||
@ -59,7 +72,7 @@ struct Stringium: ParsableCommand {
|
|||||||
extensionName: options.extensionName,
|
extensionName: options.extensionName,
|
||||||
extensionFilePath: options.extensionFilePath,
|
extensionFilePath: options.extensionFilePath,
|
||||||
extensionSuffix: options.extensionSuffix)
|
extensionSuffix: options.extensionSuffix)
|
||||||
|
|
||||||
print("[\(Self.toolName)] Strings generated")
|
print("[\(Self.toolName)] Strings generated")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,8 +11,8 @@ import ArgumentParser
|
|||||||
struct StringiumOptions: ParsableArguments {
|
struct StringiumOptions: ParsableArguments {
|
||||||
@Flag(name: [.customShort("f"), .customShort("F")], help: "Should force generation")
|
@Flag(name: [.customShort("f"), .customShort("F")], help: "Should force generation")
|
||||||
var forceGeneration = false
|
var forceGeneration = false
|
||||||
|
|
||||||
@Argument(help: "Input files where strings ared defined.", transform: { $0.replaceTiltWithHomeDirectoryPath() })
|
@Argument(help: "Input files where strings are defined.", transform: { $0.replaceTiltWithHomeDirectoryPath() })
|
||||||
var inputFile: String
|
var inputFile: String
|
||||||
|
|
||||||
@Option(name: .customLong("output-path"), help: "Path where to strings file.", transform: { $0.replaceTiltWithHomeDirectoryPath() })
|
@Option(name: .customLong("output-path"), help: "Path where to strings file.", transform: { $0.replaceTiltWithHomeDirectoryPath() })
|
||||||
@ -32,7 +32,10 @@ struct StringiumOptions: ParsableArguments {
|
|||||||
|
|
||||||
@Option(help: "Tell if it will generate static properties or not")
|
@Option(help: "Tell if it will generate static properties or not")
|
||||||
var staticMembers: Bool = false
|
var staticMembers: Bool = false
|
||||||
|
|
||||||
|
@Option(help: "Tell if it will generate xcStrings file or not")
|
||||||
|
var xcStrings: Bool = false
|
||||||
|
|
||||||
@Option(help: "Extension name. If not specified, it will generate an String extension.")
|
@Option(help: "Extension name. If not specified, it will generate an String extension.")
|
||||||
var extensionName: String = Stringium.defaultExtensionName
|
var extensionName: String = Stringium.defaultExtensionName
|
||||||
|
|
||||||
|
@ -63,7 +63,7 @@ public extension String {
|
|||||||
replacingOccurrences(of: "~", with: "\(FileManager.default.homeDirectoryForCurrentUser.relativePath)")
|
replacingOccurrences(of: "~", with: "\(FileManager.default.homeDirectoryForCurrentUser.relativePath)")
|
||||||
}
|
}
|
||||||
|
|
||||||
func colorComponent() -> (alpha: String, red: String, green: String, blue: String) {
|
func colorComponent() -> (alpha: String, red: String, green: String, blue: String) {
|
||||||
var alpha: String = "FF"
|
var alpha: String = "FF"
|
||||||
var red: String
|
var red: String
|
||||||
var green: String
|
var green: String
|
||||||
@ -89,4 +89,19 @@ public extension String {
|
|||||||
func uppercasedFirst() -> String {
|
func uppercasedFirst() -> String {
|
||||||
prefix(1).uppercased() + dropFirst()
|
prefix(1).uppercased() + dropFirst()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func replacingFirstOccurrence(of: String, with: String) -> Self {
|
||||||
|
if let range = self.range(of: of) {
|
||||||
|
let tmp = self.replacingOccurrences(
|
||||||
|
of: of,
|
||||||
|
with: with,
|
||||||
|
options: .literal,
|
||||||
|
range: range
|
||||||
|
)
|
||||||
|
|
||||||
|
return tmp
|
||||||
|
}
|
||||||
|
|
||||||
|
return self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,7 @@ final class AnalyticsGeneratorTests: XCTestCase {
|
|||||||
]
|
]
|
||||||
|
|
||||||
// When
|
// When
|
||||||
AnalyticsGenerator.targets = [Analytics.TargetType.firebase]
|
AnalyticsGenerator.targets = [TrackerType.firebase]
|
||||||
let extensionContent = AnalyticsGenerator.getExtensionContent(sections: [sectionOne, sectionTwo, sectionThree],
|
let extensionContent = AnalyticsGenerator.getExtensionContent(sections: [sectionOne, sectionTwo, sectionThree],
|
||||||
tags: ["ios", "iosonly"],
|
tags: ["ios", "iosonly"],
|
||||||
staticVar: false,
|
staticVar: false,
|
||||||
@ -216,7 +216,7 @@ final class AnalyticsGeneratorTests: XCTestCase {
|
|||||||
]
|
]
|
||||||
|
|
||||||
// When
|
// When
|
||||||
AnalyticsGenerator.targets = [Analytics.TargetType.matomo]
|
AnalyticsGenerator.targets = [TrackerType.matomo]
|
||||||
let extensionContent = AnalyticsGenerator.getExtensionContent(sections: [sectionOne, sectionTwo, sectionThree],
|
let extensionContent = AnalyticsGenerator.getExtensionContent(sections: [sectionOne, sectionTwo, sectionThree],
|
||||||
tags: ["ios", "iosonly"],
|
tags: ["ios", "iosonly"],
|
||||||
staticVar: false,
|
staticVar: false,
|
||||||
@ -409,7 +409,7 @@ final class AnalyticsGeneratorTests: XCTestCase {
|
|||||||
]
|
]
|
||||||
|
|
||||||
// When
|
// When
|
||||||
AnalyticsGenerator.targets = [Analytics.TargetType.matomo, Analytics.TargetType.firebase]
|
AnalyticsGenerator.targets = [TrackerType.matomo, TrackerType.firebase]
|
||||||
let extensionContent = AnalyticsGenerator.getExtensionContent(sections: [sectionOne, sectionTwo, sectionThree],
|
let extensionContent = AnalyticsGenerator.getExtensionContent(sections: [sectionOne, sectionTwo, sectionThree],
|
||||||
tags: ["ios", "iosonly"],
|
tags: ["ios", "iosonly"],
|
||||||
staticVar: false,
|
staticVar: false,
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
//
|
//
|
||||||
// File.swift
|
// DiffString.swift
|
||||||
//
|
//
|
||||||
//
|
//
|
||||||
// Created by Loris Perret on 06/12/2023.
|
// Created by Loris Perret on 06/12/2023.
|
||||||
@ -7,7 +7,6 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
|
||||||
/// Find first differing character between two strings
|
/// Find first differing character between two strings
|
||||||
///
|
///
|
||||||
/// :param: s1 First String
|
/// :param: s1 First String
|
||||||
|
@ -91,7 +91,7 @@ final class ParsedColorTests: XCTestCase {
|
|||||||
// When
|
// When
|
||||||
let contentJson = color.contentsJSON()
|
let contentJson = color.contentsJSON()
|
||||||
guard let data = contentJson.data(using: .utf8),
|
guard let data = contentJson.data(using: .utf8),
|
||||||
let parsedJson = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
|
let parsedJson = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
|
||||||
XCTFail("Cannot convert `contentJSON` string to Data")
|
XCTFail("Cannot convert `contentJSON` string to Data")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -17,8 +17,8 @@ class ImageFileParserTests: XCTestCase {
|
|||||||
#
|
#
|
||||||
# SMAAS Support
|
# SMAAS Support
|
||||||
#
|
#
|
||||||
id image_one 25 ?
|
id image_one 25 ? png
|
||||||
di image_two ? 50
|
di image_two ? 50 webp png
|
||||||
d image_three 25 ?
|
d image_three 25 ?
|
||||||
d image_four 75 ?
|
d image_four 75 ?
|
||||||
"""
|
"""
|
||||||
@ -38,13 +38,15 @@ class ImageFileParserTests: XCTestCase {
|
|||||||
XCTAssertEqual(firstImage!.tags, "id")
|
XCTAssertEqual(firstImage!.tags, "id")
|
||||||
XCTAssertEqual(firstImage!.width, 25)
|
XCTAssertEqual(firstImage!.width, 25)
|
||||||
XCTAssertEqual(firstImage!.height, -1)
|
XCTAssertEqual(firstImage!.height, -1)
|
||||||
|
XCTAssertEqual(firstImage!.imageExtensions, [.png])
|
||||||
|
|
||||||
let secondImage = parsedImages.first {
|
let secondImage = parsedImages.first {
|
||||||
$0.name == "image_two"
|
$0.name == "image_two"
|
||||||
}
|
}
|
||||||
XCTAssertEqual(secondImage!.name, "image_two")
|
XCTAssertEqual(secondImage!.name, "image_two")
|
||||||
XCTAssertEqual(secondImage!.tags, "di")
|
XCTAssertEqual(secondImage!.tags, "di")
|
||||||
XCTAssertEqual(secondImage!.width, -1)
|
XCTAssertEqual(secondImage!.width, -1)
|
||||||
XCTAssertEqual(secondImage!.height, 50)
|
XCTAssertEqual(secondImage!.height, 50)
|
||||||
|
XCTAssertEqual(firstImage!.imageExtensions, [.png])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -127,35 +127,77 @@ final class ParsedImageTests: XCTestCase {
|
|||||||
height: 10)
|
height: 10)
|
||||||
|
|
||||||
// When
|
// When
|
||||||
let property = parsedImage.contentJson
|
let property = parsedImage.generateImageContent(isVector: false)
|
||||||
|
|
||||||
// Expect
|
// Expect
|
||||||
let expect = """
|
let expect = AssetContent(
|
||||||
{
|
images: [
|
||||||
"images" : [
|
AssetImageDescription(
|
||||||
{
|
idiom: "universal",
|
||||||
"idiom" : "universal",
|
scale: "1x",
|
||||||
"scale" : "1x",
|
filename: "\(parsedImage.name).\(OutputImageExtension.png.rawValue)"
|
||||||
"filename" : "\(imageName).\(XcassetsGenerator.outputImageExtension)"
|
),
|
||||||
},
|
AssetImageDescription(
|
||||||
{
|
idiom: "universal",
|
||||||
"idiom" : "universal",
|
scale: "2x",
|
||||||
"scale" : "2x",
|
filename: "\(parsedImage.name)@2x.\(OutputImageExtension.png.rawValue)"
|
||||||
"filename" : "\(imageName)@2x.\(XcassetsGenerator.outputImageExtension)"
|
),
|
||||||
},
|
AssetImageDescription(
|
||||||
{
|
idiom: "universal",
|
||||||
"idiom" : "universal",
|
scale: "3x",
|
||||||
"scale" : "3x",
|
filename: "\(parsedImage.name)@3x.\(OutputImageExtension.png.rawValue)"
|
||||||
"filename" : "\(imageName)@3x.\(XcassetsGenerator.outputImageExtension)"
|
)
|
||||||
}
|
],
|
||||||
],
|
info: AssetInfo(
|
||||||
"info" : {
|
version: 1,
|
||||||
"version" : 1,
|
author: "ResgenSwift-Imagium"
|
||||||
"author" : "ResgenSwift-Imagium"
|
)
|
||||||
}
|
)
|
||||||
}
|
|
||||||
"""
|
XCTAssertEqual(property, expect)
|
||||||
|
}
|
||||||
XCTAssertEqual(property.adaptForXCTest(), expect.adaptForXCTest())
|
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -100,24 +100,33 @@ final class DefinitionTests: XCTestCase {
|
|||||||
let expectFr = """
|
let expectFr = """
|
||||||
/// Translation in fr :
|
/// Translation in fr :
|
||||||
/// C'est la traduction francaise
|
/// C'est la traduction francaise
|
||||||
|
///
|
||||||
|
/// Comment :
|
||||||
|
/// This is a comment
|
||||||
var definition_name: String {
|
var definition_name: String {
|
||||||
NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "C'est la traduction francaise", comment: "")
|
NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "C'est la traduction francaise", comment: "This is a comment")
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
let expectEn = """
|
let expectEn = """
|
||||||
/// Translation in en :
|
/// Translation in en :
|
||||||
/// This is the english translation
|
/// This is the english translation
|
||||||
|
///
|
||||||
|
/// Comment :
|
||||||
|
/// This is a comment
|
||||||
var definition_name: String {
|
var definition_name: String {
|
||||||
NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "This is the english translation", comment: "")
|
NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "This is the english translation", comment: "This is a comment")
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
let expectEnUs = """
|
let expectEnUs = """
|
||||||
/// Translation in en-us :
|
/// Translation in en-us :
|
||||||
/// This is the english us translation
|
/// This is the english us translation
|
||||||
|
///
|
||||||
|
/// Comment :
|
||||||
|
/// This is a comment
|
||||||
var definition_name: String {
|
var definition_name: String {
|
||||||
NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "This is the english us translation", comment: "")
|
NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "This is the english us translation", comment: "This is a comment")
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -125,7 +134,118 @@ final class DefinitionTests: XCTestCase {
|
|||||||
XCTAssertEqual(propertyEn.adaptForXCTest(), expectEn.adaptForXCTest())
|
XCTAssertEqual(propertyEn.adaptForXCTest(), expectEn.adaptForXCTest())
|
||||||
XCTAssertEqual(propertyEnUs.adaptForXCTest(), expectEnUs.adaptForXCTest())
|
XCTAssertEqual(propertyEnUs.adaptForXCTest(), expectEnUs.adaptForXCTest())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testGeneratedNSLocalizedStringPropertyWithEmptyComment() {
|
||||||
|
// Given
|
||||||
|
let definition = Definition(name: "definition_name")
|
||||||
|
definition.tags = ["ios","iosonly","notranslation"]
|
||||||
|
definition.comment = ""
|
||||||
|
definition.translations = [
|
||||||
|
"fr": "C'est la traduction francaise",
|
||||||
|
"en": "This is the english translation",
|
||||||
|
"en-us": "This is the english us translation"
|
||||||
|
]
|
||||||
|
|
||||||
|
// When
|
||||||
|
let propertyFr = definition.getNSLocalizedStringProperty(forLang: "fr")
|
||||||
|
let propertyEn = definition.getNSLocalizedStringProperty(forLang: "en")
|
||||||
|
let propertyEnUs = definition.getNSLocalizedStringProperty(forLang: "en-us")
|
||||||
|
|
||||||
|
// Expect
|
||||||
|
let expectFr = """
|
||||||
|
/// Translation in fr :
|
||||||
|
/// C'est la traduction francaise
|
||||||
|
///
|
||||||
|
/// Comment :
|
||||||
|
/// No comment
|
||||||
|
var definition_name: String {
|
||||||
|
NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "C'est la traduction francaise", comment: "")
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
let expectEn = """
|
||||||
|
/// Translation in en :
|
||||||
|
/// This is the english translation
|
||||||
|
///
|
||||||
|
/// Comment :
|
||||||
|
/// No comment
|
||||||
|
var definition_name: String {
|
||||||
|
NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "This is the english translation", comment: "")
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
let expectEnUs = """
|
||||||
|
/// Translation in en-us :
|
||||||
|
/// This is the english us translation
|
||||||
|
///
|
||||||
|
/// Comment :
|
||||||
|
/// No comment
|
||||||
|
var definition_name: String {
|
||||||
|
NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "This is the english us translation", comment: "")
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
XCTAssertEqual(propertyFr.adaptForXCTest(), expectFr.adaptForXCTest())
|
||||||
|
XCTAssertEqual(propertyEn.adaptForXCTest(), expectEn.adaptForXCTest())
|
||||||
|
XCTAssertEqual(propertyEnUs.adaptForXCTest(), expectEnUs.adaptForXCTest())
|
||||||
|
}
|
||||||
|
|
||||||
|
func testGeneratedNSLocalizedStringPropertyWithNoComment() {
|
||||||
|
// Given
|
||||||
|
let definition = Definition(name: "definition_name")
|
||||||
|
definition.tags = ["ios","iosonly","notranslation"]
|
||||||
|
definition.translations = [
|
||||||
|
"fr": "C'est la traduction francaise",
|
||||||
|
"en": "This is the english translation",
|
||||||
|
"en-us": "This is the english us translation"
|
||||||
|
]
|
||||||
|
|
||||||
|
// When
|
||||||
|
let propertyFr = definition.getNSLocalizedStringProperty(forLang: "fr")
|
||||||
|
let propertyEn = definition.getNSLocalizedStringProperty(forLang: "en")
|
||||||
|
let propertyEnUs = definition.getNSLocalizedStringProperty(forLang: "en-us")
|
||||||
|
|
||||||
|
// Expect
|
||||||
|
let expectFr = """
|
||||||
|
/// Translation in fr :
|
||||||
|
/// C'est la traduction francaise
|
||||||
|
///
|
||||||
|
/// Comment :
|
||||||
|
/// No comment
|
||||||
|
var definition_name: String {
|
||||||
|
NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "C'est la traduction francaise", comment: "")
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
let expectEn = """
|
||||||
|
/// Translation in en :
|
||||||
|
/// This is the english translation
|
||||||
|
///
|
||||||
|
/// Comment :
|
||||||
|
/// No comment
|
||||||
|
var definition_name: String {
|
||||||
|
NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "This is the english translation", comment: "")
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
let expectEnUs = """
|
||||||
|
/// Translation in en-us :
|
||||||
|
/// This is the english us translation
|
||||||
|
///
|
||||||
|
/// Comment :
|
||||||
|
/// No comment
|
||||||
|
var definition_name: String {
|
||||||
|
NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "This is the english us translation", comment: "")
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
XCTAssertEqual(propertyFr.adaptForXCTest(), expectFr.adaptForXCTest())
|
||||||
|
XCTAssertEqual(propertyEn.adaptForXCTest(), expectEn.adaptForXCTest())
|
||||||
|
XCTAssertEqual(propertyEnUs.adaptForXCTest(), expectEnUs.adaptForXCTest())
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - getNSLocalizedStringStaticProperty
|
||||||
|
|
||||||
func testGeneratedNSLocalizedStringStaticProperty() {
|
func testGeneratedNSLocalizedStringStaticProperty() {
|
||||||
// Given
|
// Given
|
||||||
let definition = Definition(name: "definition_name")
|
let definition = Definition(name: "definition_name")
|
||||||
@ -146,24 +266,33 @@ final class DefinitionTests: XCTestCase {
|
|||||||
let expectFr = """
|
let expectFr = """
|
||||||
/// Translation in fr :
|
/// Translation in fr :
|
||||||
/// C'est la traduction francaise
|
/// C'est la traduction francaise
|
||||||
|
///
|
||||||
|
/// Comment :
|
||||||
|
/// This is a comment
|
||||||
static var definition_name: String {
|
static var definition_name: String {
|
||||||
NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "C'est la traduction francaise", comment: "")
|
NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "C'est la traduction francaise", comment: "This is a comment")
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
let expectEn = """
|
let expectEn = """
|
||||||
/// Translation in en :
|
/// Translation in en :
|
||||||
/// This is the english translation
|
/// This is the english translation
|
||||||
|
///
|
||||||
|
/// Comment :
|
||||||
|
/// This is a comment
|
||||||
static var definition_name: String {
|
static var definition_name: String {
|
||||||
NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "This is the english translation", comment: "")
|
NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "This is the english translation", comment: "This is a comment")
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
let expectEnUs = """
|
let expectEnUs = """
|
||||||
/// Translation in en-us :
|
/// Translation in en-us :
|
||||||
/// This is the english us translation
|
/// This is the english us translation
|
||||||
|
///
|
||||||
|
/// Comment :
|
||||||
|
/// This is a comment
|
||||||
static var definition_name: String {
|
static var definition_name: String {
|
||||||
NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "This is the english us translation", comment: "")
|
NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "This is the english us translation", comment: "This is a comment")
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -171,7 +300,116 @@ final class DefinitionTests: XCTestCase {
|
|||||||
XCTAssertEqual(propertyEn.adaptForXCTest(), expectEn.adaptForXCTest())
|
XCTAssertEqual(propertyEn.adaptForXCTest(), expectEn.adaptForXCTest())
|
||||||
XCTAssertEqual(propertyEnUs.adaptForXCTest(), expectEnUs.adaptForXCTest())
|
XCTAssertEqual(propertyEnUs.adaptForXCTest(), expectEnUs.adaptForXCTest())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testGeneratedNSLocalizedStringStaticPropertyWithEmptyComment() {
|
||||||
|
// Given
|
||||||
|
let definition = Definition(name: "definition_name")
|
||||||
|
definition.tags = ["ios","iosonly","notranslation"]
|
||||||
|
definition.comment = ""
|
||||||
|
definition.translations = [
|
||||||
|
"fr": "C'est la traduction francaise",
|
||||||
|
"en": "This is the english translation",
|
||||||
|
"en-us": "This is the english us translation"
|
||||||
|
]
|
||||||
|
|
||||||
|
// When
|
||||||
|
let propertyFr = definition.getNSLocalizedStringStaticProperty(forLang: "fr")
|
||||||
|
let propertyEn = definition.getNSLocalizedStringStaticProperty(forLang: "en")
|
||||||
|
let propertyEnUs = definition.getNSLocalizedStringStaticProperty(forLang: "en-us")
|
||||||
|
|
||||||
|
// Expect
|
||||||
|
let expectFr = """
|
||||||
|
/// Translation in fr :
|
||||||
|
/// C'est la traduction francaise
|
||||||
|
///
|
||||||
|
/// Comment :
|
||||||
|
/// No comment
|
||||||
|
static var definition_name: String {
|
||||||
|
NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "C'est la traduction francaise", comment: "")
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
let expectEn = """
|
||||||
|
/// Translation in en :
|
||||||
|
/// This is the english translation
|
||||||
|
///
|
||||||
|
/// Comment :
|
||||||
|
/// No comment
|
||||||
|
static var definition_name: String {
|
||||||
|
NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "This is the english translation", comment: "")
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
let expectEnUs = """
|
||||||
|
/// Translation in en-us :
|
||||||
|
/// This is the english us translation
|
||||||
|
///
|
||||||
|
/// Comment :
|
||||||
|
/// No comment
|
||||||
|
static var definition_name: String {
|
||||||
|
NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "This is the english us translation", comment: "")
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
XCTAssertEqual(propertyFr.adaptForXCTest(), expectFr.adaptForXCTest())
|
||||||
|
XCTAssertEqual(propertyEn.adaptForXCTest(), expectEn.adaptForXCTest())
|
||||||
|
XCTAssertEqual(propertyEnUs.adaptForXCTest(), expectEnUs.adaptForXCTest())
|
||||||
|
}
|
||||||
|
|
||||||
|
func testGeneratedNSLocalizedStringStaticPropertyWithNoComment() {
|
||||||
|
// Given
|
||||||
|
let definition = Definition(name: "definition_name")
|
||||||
|
definition.tags = ["ios","iosonly","notranslation"]
|
||||||
|
definition.translations = [
|
||||||
|
"fr": "C'est la traduction francaise",
|
||||||
|
"en": "This is the english translation",
|
||||||
|
"en-us": "This is the english us translation"
|
||||||
|
]
|
||||||
|
|
||||||
|
// When
|
||||||
|
let propertyFr = definition.getNSLocalizedStringStaticProperty(forLang: "fr")
|
||||||
|
let propertyEn = definition.getNSLocalizedStringStaticProperty(forLang: "en")
|
||||||
|
let propertyEnUs = definition.getNSLocalizedStringStaticProperty(forLang: "en-us")
|
||||||
|
|
||||||
|
// Expect
|
||||||
|
let expectFr = """
|
||||||
|
/// Translation in fr :
|
||||||
|
/// C'est la traduction francaise
|
||||||
|
///
|
||||||
|
/// Comment :
|
||||||
|
/// No comment
|
||||||
|
static var definition_name: String {
|
||||||
|
NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "C'est la traduction francaise", comment: "")
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
let expectEn = """
|
||||||
|
/// Translation in en :
|
||||||
|
/// This is the english translation
|
||||||
|
///
|
||||||
|
/// Comment :
|
||||||
|
/// No comment
|
||||||
|
static var definition_name: String {
|
||||||
|
NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "This is the english translation", comment: "")
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
let expectEnUs = """
|
||||||
|
/// Translation in en-us :
|
||||||
|
/// This is the english us translation
|
||||||
|
///
|
||||||
|
/// Comment :
|
||||||
|
/// No comment
|
||||||
|
static var definition_name: String {
|
||||||
|
NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "This is the english us translation", comment: "")
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
XCTAssertEqual(propertyFr.adaptForXCTest(), expectFr.adaptForXCTest())
|
||||||
|
XCTAssertEqual(propertyEn.adaptForXCTest(), expectEn.adaptForXCTest())
|
||||||
|
XCTAssertEqual(propertyEnUs.adaptForXCTest(), expectEnUs.adaptForXCTest())
|
||||||
|
}
|
||||||
|
|
||||||
func testGeneratedNSLocalizedStringPropertyWithOneArgument() {
|
func testGeneratedNSLocalizedStringPropertyWithOneArgument() {
|
||||||
// Given
|
// Given
|
||||||
let definition = Definition(name: "definition_name")
|
let definition = Definition(name: "definition_name")
|
||||||
@ -188,17 +426,23 @@ final class DefinitionTests: XCTestCase {
|
|||||||
let expectFr = """
|
let expectFr = """
|
||||||
/// Translation in fr :
|
/// Translation in fr :
|
||||||
/// Welcome "%@" !
|
/// Welcome "%@" !
|
||||||
|
///
|
||||||
|
/// Comment :
|
||||||
|
/// This is a comment
|
||||||
var definition_name: String {
|
var definition_name: String {
|
||||||
NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "Welcome \"%@\" !", comment: "")
|
NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "Welcome \"%@\" !", comment: "This is a comment")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Translation in fr :
|
/// Translation in fr :
|
||||||
/// Welcome "%@" !
|
/// Welcome "%@" !
|
||||||
|
///
|
||||||
|
/// Comment :
|
||||||
|
/// This is a comment
|
||||||
func definition_name(arg0: String) -> String {
|
func definition_name(arg0: String) -> String {
|
||||||
String(format: self.definition_name, arg0)
|
String(format: self.definition_name, arg0)
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
XCTAssertEqual(propertyFr.adaptForXCTest(), expectFr.adaptForXCTest())
|
XCTAssertEqual(propertyFr.adaptForXCTest(), expectFr.adaptForXCTest())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -218,12 +462,18 @@ final class DefinitionTests: XCTestCase {
|
|||||||
let expectFr = """
|
let expectFr = """
|
||||||
/// Translation in fr :
|
/// Translation in fr :
|
||||||
/// Welcome "%@" ! Your age is %d :) Your weight is %f ;-)
|
/// Welcome "%@" ! Your age is %d :) Your weight is %f ;-)
|
||||||
|
///
|
||||||
|
/// Comment :
|
||||||
|
/// This is a comment
|
||||||
var definition_name: String {
|
var definition_name: String {
|
||||||
NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "Welcome \"%@\" ! Your age is %d :) Your weight is %f ;-)", comment: "")
|
NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "Welcome \"%@\" ! Your age is %d :) Your weight is %f ;-)", comment: "This is a comment")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Translation in fr :
|
/// Translation in fr :
|
||||||
/// Welcome "%@" ! Your age is %d :) Your weight is %f ;-)
|
/// Welcome "%@" ! Your age is %d :) Your weight is %f ;-)
|
||||||
|
///
|
||||||
|
/// Comment :
|
||||||
|
/// This is a comment
|
||||||
func definition_name(arg0: String, arg1: Int, arg2: Double) -> String {
|
func definition_name(arg0: String, arg1: Int, arg2: Double) -> String {
|
||||||
String(format: self.definition_name, arg0, arg1, arg2)
|
String(format: self.definition_name, arg0, arg1, arg2)
|
||||||
}
|
}
|
||||||
@ -249,12 +499,18 @@ final class DefinitionTests: XCTestCase {
|
|||||||
let expectFr = """
|
let expectFr = """
|
||||||
/// Translation in fr :
|
/// Translation in fr :
|
||||||
/// Vous %%: %1$@ %2$@ Age: %3$d
|
/// Vous %%: %1$@ %2$@ Age: %3$d
|
||||||
|
///
|
||||||
|
/// Comment :
|
||||||
|
/// This is a comment
|
||||||
var definition_name: String {
|
var definition_name: String {
|
||||||
NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "Vous %%: %1$@ %2$@ Age: %3$d", comment: "")
|
NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "Vous %%: %1$@ %2$@ Age: %3$d", comment: "This is a comment")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Translation in fr :
|
/// Translation in fr :
|
||||||
/// Vous %%: %1$@ %2$@ Age: %3$d
|
/// Vous %%: %1$@ %2$@ Age: %3$d
|
||||||
|
///
|
||||||
|
/// Comment :
|
||||||
|
/// This is a comment
|
||||||
func definition_name(arg0: String, arg1: String, arg2: Int) -> String {
|
func definition_name(arg0: String, arg1: String, arg2: Int) -> String {
|
||||||
String(format: self.definition_name, arg0, arg1, arg2)
|
String(format: self.definition_name, arg0, arg1, arg2)
|
||||||
}
|
}
|
||||||
@ -263,12 +519,18 @@ final class DefinitionTests: XCTestCase {
|
|||||||
let expectEn = """
|
let expectEn = """
|
||||||
/// Translation in en :
|
/// Translation in en :
|
||||||
/// You %%: %2$@ %1$@ Age: %3$d
|
/// You %%: %2$@ %1$@ Age: %3$d
|
||||||
|
///
|
||||||
|
/// Comment :
|
||||||
|
/// This is a comment
|
||||||
var definition_name: String {
|
var definition_name: String {
|
||||||
NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "You %%: %2$@ %1$@ Age: %3$d", comment: "")
|
NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "You %%: %2$@ %1$@ Age: %3$d", comment: "This is a comment")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Translation in en :
|
/// Translation in en :
|
||||||
/// You %%: %2$@ %1$@ Age: %3$d
|
/// You %%: %2$@ %1$@ Age: %3$d
|
||||||
|
///
|
||||||
|
/// Comment :
|
||||||
|
/// This is a comment
|
||||||
func definition_name(arg0: String, arg1: String, arg2: Int) -> String {
|
func definition_name(arg0: String, arg1: String, arg2: Int) -> String {
|
||||||
String(format: self.definition_name, arg0, arg1, arg2)
|
String(format: self.definition_name, arg0, arg1, arg2)
|
||||||
}
|
}
|
||||||
@ -300,6 +562,9 @@ final class DefinitionTests: XCTestCase {
|
|||||||
let expectFr = """
|
let expectFr = """
|
||||||
/// Translation in fr :
|
/// Translation in fr :
|
||||||
/// C'est la traduction francaise
|
/// C'est la traduction francaise
|
||||||
|
///
|
||||||
|
/// Comment :
|
||||||
|
/// This is a comment
|
||||||
var definition_name: String {
|
var definition_name: String {
|
||||||
"C'est la traduction francaise"
|
"C'est la traduction francaise"
|
||||||
}
|
}
|
||||||
@ -308,6 +573,9 @@ final class DefinitionTests: XCTestCase {
|
|||||||
let expectEn = """
|
let expectEn = """
|
||||||
/// Translation in en :
|
/// Translation in en :
|
||||||
/// This is the english translation
|
/// This is the english translation
|
||||||
|
///
|
||||||
|
/// Comment :
|
||||||
|
/// This is a comment
|
||||||
var definition_name: String {
|
var definition_name: String {
|
||||||
"This is the english translation"
|
"This is the english translation"
|
||||||
}
|
}
|
||||||
@ -316,6 +584,9 @@ final class DefinitionTests: XCTestCase {
|
|||||||
let expectEnUs = """
|
let expectEnUs = """
|
||||||
/// Translation in en-us :
|
/// Translation in en-us :
|
||||||
/// This is the english us translation
|
/// This is the english us translation
|
||||||
|
///
|
||||||
|
/// Comment :
|
||||||
|
/// This is a comment
|
||||||
var definition_name: String {
|
var definition_name: String {
|
||||||
"This is the english us translation"
|
"This is the english us translation"
|
||||||
}
|
}
|
||||||
@ -325,7 +596,118 @@ final class DefinitionTests: XCTestCase {
|
|||||||
XCTAssertEqual(propertyEn.adaptForXCTest(), expectEn.adaptForXCTest())
|
XCTAssertEqual(propertyEn.adaptForXCTest(), expectEn.adaptForXCTest())
|
||||||
XCTAssertEqual(propertyEnUs.adaptForXCTest(), expectEnUs.adaptForXCTest())
|
XCTAssertEqual(propertyEnUs.adaptForXCTest(), expectEnUs.adaptForXCTest())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testGeneratedRawPropertyWithEmptyComment() {
|
||||||
|
// Given
|
||||||
|
let definition = Definition(name: "definition_name")
|
||||||
|
definition.tags = ["ios","iosonly","notranslation"]
|
||||||
|
definition.comment = ""
|
||||||
|
definition.translations = [
|
||||||
|
"fr": "C'est la traduction francaise",
|
||||||
|
"en": "This is the english translation",
|
||||||
|
"en-us": "This is the english us translation"
|
||||||
|
]
|
||||||
|
|
||||||
|
// When
|
||||||
|
let propertyFr = definition.getProperty(forLang: "fr")
|
||||||
|
let propertyEn = definition.getProperty(forLang: "en")
|
||||||
|
let propertyEnUs = definition.getProperty(forLang: "en-us")
|
||||||
|
|
||||||
|
// Expect
|
||||||
|
let expectFr = """
|
||||||
|
/// Translation in fr :
|
||||||
|
/// C'est la traduction francaise
|
||||||
|
///
|
||||||
|
/// Comment :
|
||||||
|
/// No comment
|
||||||
|
var definition_name: String {
|
||||||
|
"C'est la traduction francaise"
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
let expectEn = """
|
||||||
|
/// Translation in en :
|
||||||
|
/// This is the english translation
|
||||||
|
///
|
||||||
|
/// Comment :
|
||||||
|
/// No comment
|
||||||
|
var definition_name: String {
|
||||||
|
"This is the english translation"
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
let expectEnUs = """
|
||||||
|
/// Translation in en-us :
|
||||||
|
/// This is the english us translation
|
||||||
|
///
|
||||||
|
/// Comment :
|
||||||
|
/// No comment
|
||||||
|
var definition_name: String {
|
||||||
|
"This is the english us translation"
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
XCTAssertEqual(propertyFr.adaptForXCTest(), expectFr.adaptForXCTest())
|
||||||
|
XCTAssertEqual(propertyEn.adaptForXCTest(), expectEn.adaptForXCTest())
|
||||||
|
XCTAssertEqual(propertyEnUs.adaptForXCTest(), expectEnUs.adaptForXCTest())
|
||||||
|
}
|
||||||
|
|
||||||
|
func testGeneratedRawPropertyWithNoComment() {
|
||||||
|
// Given
|
||||||
|
let definition = Definition(name: "definition_name")
|
||||||
|
definition.tags = ["ios","iosonly","notranslation"]
|
||||||
|
definition.translations = [
|
||||||
|
"fr": "C'est la traduction francaise",
|
||||||
|
"en": "This is the english translation",
|
||||||
|
"en-us": "This is the english us translation"
|
||||||
|
]
|
||||||
|
|
||||||
|
// When
|
||||||
|
let propertyFr = definition.getProperty(forLang: "fr")
|
||||||
|
let propertyEn = definition.getProperty(forLang: "en")
|
||||||
|
let propertyEnUs = definition.getProperty(forLang: "en-us")
|
||||||
|
|
||||||
|
// Expect
|
||||||
|
let expectFr = """
|
||||||
|
/// Translation in fr :
|
||||||
|
/// C'est la traduction francaise
|
||||||
|
///
|
||||||
|
/// Comment :
|
||||||
|
/// No comment
|
||||||
|
var definition_name: String {
|
||||||
|
"C'est la traduction francaise"
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
let expectEn = """
|
||||||
|
/// Translation in en :
|
||||||
|
/// This is the english translation
|
||||||
|
///
|
||||||
|
/// Comment :
|
||||||
|
/// No comment
|
||||||
|
var definition_name: String {
|
||||||
|
"This is the english translation"
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
let expectEnUs = """
|
||||||
|
/// Translation in en-us :
|
||||||
|
/// This is the english us translation
|
||||||
|
///
|
||||||
|
/// Comment :
|
||||||
|
/// No comment
|
||||||
|
var definition_name: String {
|
||||||
|
"This is the english us translation"
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
XCTAssertEqual(propertyFr.adaptForXCTest(), expectFr.adaptForXCTest())
|
||||||
|
XCTAssertEqual(propertyEn.adaptForXCTest(), expectEn.adaptForXCTest())
|
||||||
|
XCTAssertEqual(propertyEnUs.adaptForXCTest(), expectEnUs.adaptForXCTest())
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Raw static properties
|
||||||
|
|
||||||
func testGeneratedRawStaticProperty() {
|
func testGeneratedRawStaticProperty() {
|
||||||
// Given
|
// Given
|
||||||
let definition = Definition(name: "definition_name")
|
let definition = Definition(name: "definition_name")
|
||||||
@ -346,6 +728,9 @@ final class DefinitionTests: XCTestCase {
|
|||||||
let expectFr = """
|
let expectFr = """
|
||||||
/// Translation in fr :
|
/// Translation in fr :
|
||||||
/// C'est la traduction francaise
|
/// C'est la traduction francaise
|
||||||
|
///
|
||||||
|
/// Comment :
|
||||||
|
/// This is a comment
|
||||||
static var definition_name: String {
|
static var definition_name: String {
|
||||||
"C'est la traduction francaise"
|
"C'est la traduction francaise"
|
||||||
}
|
}
|
||||||
@ -354,7 +739,10 @@ final class DefinitionTests: XCTestCase {
|
|||||||
let expectEn = """
|
let expectEn = """
|
||||||
/// Translation in en :
|
/// Translation in en :
|
||||||
/// This is the english translation
|
/// This is the english translation
|
||||||
static var definition_name: String {
|
///
|
||||||
|
/// Comment :
|
||||||
|
/// This is a comment
|
||||||
|
static var definition_name: String {
|
||||||
"This is the english translation"
|
"This is the english translation"
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
@ -362,6 +750,9 @@ final class DefinitionTests: XCTestCase {
|
|||||||
let expectEnUs = """
|
let expectEnUs = """
|
||||||
/// Translation in en-us :
|
/// Translation in en-us :
|
||||||
/// This is the english us translation
|
/// This is the english us translation
|
||||||
|
///
|
||||||
|
/// Comment :
|
||||||
|
/// This is a comment
|
||||||
static var definition_name: String {
|
static var definition_name: String {
|
||||||
"This is the english us translation"
|
"This is the english us translation"
|
||||||
}
|
}
|
||||||
@ -371,4 +762,113 @@ final class DefinitionTests: XCTestCase {
|
|||||||
XCTAssertEqual(propertyEn.adaptForXCTest(), expectEn.adaptForXCTest())
|
XCTAssertEqual(propertyEn.adaptForXCTest(), expectEn.adaptForXCTest())
|
||||||
XCTAssertEqual(propertyEnUs.adaptForXCTest(), expectEnUs.adaptForXCTest())
|
XCTAssertEqual(propertyEnUs.adaptForXCTest(), expectEnUs.adaptForXCTest())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testGeneratedRawStaticPropertyWithEmptyComment() {
|
||||||
|
// Given
|
||||||
|
let definition = Definition(name: "definition_name")
|
||||||
|
definition.tags = ["ios","iosonly","notranslation"]
|
||||||
|
definition.comment = ""
|
||||||
|
definition.translations = [
|
||||||
|
"fr": "C'est la traduction francaise",
|
||||||
|
"en": "This is the english translation",
|
||||||
|
"en-us": "This is the english us translation"
|
||||||
|
]
|
||||||
|
|
||||||
|
// When
|
||||||
|
let propertyFr = definition.getStaticProperty(forLang: "fr")
|
||||||
|
let propertyEn = definition.getStaticProperty(forLang: "en")
|
||||||
|
let propertyEnUs = definition.getStaticProperty(forLang: "en-us")
|
||||||
|
|
||||||
|
// Expect
|
||||||
|
let expectFr = """
|
||||||
|
/// Translation in fr :
|
||||||
|
/// C'est la traduction francaise
|
||||||
|
///
|
||||||
|
/// Comment :
|
||||||
|
/// No comment
|
||||||
|
static var definition_name: String {
|
||||||
|
"C'est la traduction francaise"
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
let expectEn = """
|
||||||
|
/// Translation in en :
|
||||||
|
/// This is the english translation
|
||||||
|
///
|
||||||
|
/// Comment :
|
||||||
|
/// No comment
|
||||||
|
static var definition_name: String {
|
||||||
|
"This is the english translation"
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
let expectEnUs = """
|
||||||
|
/// Translation in en-us :
|
||||||
|
/// This is the english us translation
|
||||||
|
///
|
||||||
|
/// Comment :
|
||||||
|
/// No comment
|
||||||
|
static var definition_name: String {
|
||||||
|
"This is the english us translation"
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
XCTAssertEqual(propertyFr.adaptForXCTest(), expectFr.adaptForXCTest())
|
||||||
|
XCTAssertEqual(propertyEn.adaptForXCTest(), expectEn.adaptForXCTest())
|
||||||
|
XCTAssertEqual(propertyEnUs.adaptForXCTest(), expectEnUs.adaptForXCTest())
|
||||||
|
}
|
||||||
|
|
||||||
|
func testGeneratedRawStaticPropertyWithNoComment() {
|
||||||
|
// Given
|
||||||
|
let definition = Definition(name: "definition_name")
|
||||||
|
definition.tags = ["ios","iosonly","notranslation"]
|
||||||
|
definition.translations = [
|
||||||
|
"fr": "C'est la traduction francaise",
|
||||||
|
"en": "This is the english translation",
|
||||||
|
"en-us": "This is the english us translation"
|
||||||
|
]
|
||||||
|
|
||||||
|
// When
|
||||||
|
let propertyFr = definition.getStaticProperty(forLang: "fr")
|
||||||
|
let propertyEn = definition.getStaticProperty(forLang: "en")
|
||||||
|
let propertyEnUs = definition.getStaticProperty(forLang: "en-us")
|
||||||
|
|
||||||
|
// Expect
|
||||||
|
let expectFr = """
|
||||||
|
/// Translation in fr :
|
||||||
|
/// C'est la traduction francaise
|
||||||
|
///
|
||||||
|
/// Comment :
|
||||||
|
/// No comment
|
||||||
|
static var definition_name: String {
|
||||||
|
"C'est la traduction francaise"
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
let expectEn = """
|
||||||
|
/// Translation in en :
|
||||||
|
/// This is the english translation
|
||||||
|
///
|
||||||
|
/// Comment :
|
||||||
|
/// No comment
|
||||||
|
static var definition_name: String {
|
||||||
|
"This is the english translation"
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
let expectEnUs = """
|
||||||
|
/// Translation in en-us :
|
||||||
|
/// This is the english us translation
|
||||||
|
///
|
||||||
|
/// Comment :
|
||||||
|
/// No comment
|
||||||
|
static var definition_name: String {
|
||||||
|
"This is the english us translation"
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
XCTAssertEqual(propertyFr.adaptForXCTest(), expectFr.adaptForXCTest())
|
||||||
|
XCTAssertEqual(propertyEn.adaptForXCTest(), expectEn.adaptForXCTest())
|
||||||
|
XCTAssertEqual(propertyEnUs.adaptForXCTest(), expectEnUs.adaptForXCTest())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user