Compare commits
50 Commits
Author | SHA1 | Date | |
---|---|---|---|
8442c89944 | |||
57cedd37bb | |||
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 | |||
1f2933950b | |||
3b90387e10 | |||
1ee4998ec6 | |||
ca763cd5d0 | |||
3fc2fd9bac | |||
09c153ba65 | |||
2a144fc00e | |||
6aef8bc2de | |||
3e133773a9 | |||
5fd680110c | |||
ce274219fc | |||
fa5bf192e8 | |||
1a45ec7b0d |
@ -13,7 +13,7 @@
|
|||||||
- Update plist `UIAppFonts` when generated fonts (use plistBuddy)
|
- Update plist `UIAppFonts` when generated fonts (use plistBuddy)
|
||||||
- New parameter: `infoPlistPaths`
|
- New parameter: `infoPlistPaths`
|
||||||
- Generate SwiftUI extensions for colors, fonts and images
|
- Generate SwiftUI extensions for colors, fonts and images
|
||||||
- New parameter: `extensionNameSwiftUI`
|
- New parameter: `extensionNameUIKit`
|
||||||
- Adding Makefile to install, unsintall and create man page.
|
- Adding Makefile to install, unsintall and create man page.
|
||||||
|
|
||||||
## Fixes
|
## Fixes
|
||||||
|
2
Jenkinsfile
vendored
2
Jenkinsfile
vendored
@ -1,6 +1,6 @@
|
|||||||
library "openiumpipeline"
|
library "openiumpipeline"
|
||||||
|
|
||||||
env.DEVELOPER_DIR= "/Applications/Xcode-14.3.0.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,12 +1,75 @@
|
|||||||
{
|
{
|
||||||
"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" : "c9c3df6ab812de32bae61fc0cd1bf6d45170ebf0",
|
||||||
|
"version" : "1.8.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "sourcekitten",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/jpsim/SourceKitten.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "b6dc09ee51dfb0c66e042d2328c017483a1a5d56",
|
||||||
|
"version" : "0.34.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"identity" : "swift-argument-parser",
|
"identity" : "swift-argument-parser",
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
"location" : "https://github.com/apple/swift-argument-parser",
|
"location" : "https://github.com/apple/swift-argument-parser",
|
||||||
"state" : {
|
"state" : {
|
||||||
"revision" : "9f39744e025c7d377987f30b03770805dcb0bcd1",
|
"revision" : "8f4d2753f0e4778c76d5f05ad16c74f707390531",
|
||||||
"version" : "1.1.4"
|
"version" : "1.2.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "swift-syntax",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/apple/swift-syntax.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "6ad4ea24b01559dde0773e3d091f1b9e36175036",
|
||||||
|
"version" : "509.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "swiftlint",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/realm/SwiftLint.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "f17a4f9dfb6a6afb0408426354e4180daaf49cee",
|
||||||
|
"version" : "0.54.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "swiftytexttable",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/scottrhoyt/SwiftyTextTable.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "c6df6cf533d120716bff38f8ff9885e1ce2a4ac3",
|
||||||
|
"version" : "0.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "swxmlhash",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/drmohundro/SWXMLHash.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "a853604c9e9a83ad9954c7e3d2a565273982471f",
|
||||||
|
"version" : "7.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -14,8 +77,8 @@
|
|||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
"location" : "https://github.com/jpsim/Yams.git",
|
"location" : "https://github.com/jpsim/Yams.git",
|
||||||
"state" : {
|
"state" : {
|
||||||
"revision" : "01835dc202670b5bb90d07f3eae41867e9ed29f6",
|
"revision" : "0d9ee7ea8c4ebd4a489ad7a73d5c6cad55d6fed3",
|
||||||
"version" : "5.0.1"
|
"version" : "5.0.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -9,7 +9,8 @@ let package = Package(
|
|||||||
dependencies: [
|
dependencies: [
|
||||||
// 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")),
|
||||||
],
|
],
|
||||||
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.
|
||||||
@ -20,7 +21,8 @@ let package = Package(
|
|||||||
"ToolCore",
|
"ToolCore",
|
||||||
.product(name: "ArgumentParser", package: "swift-argument-parser"),
|
.product(name: "ArgumentParser", package: "swift-argument-parser"),
|
||||||
"Yams"
|
"Yams"
|
||||||
]
|
],
|
||||||
|
plugins: [.plugin(name: "SwiftLintPlugin", package: "SwiftLint")]
|
||||||
),
|
),
|
||||||
|
|
||||||
// Helper targets
|
// Helper targets
|
||||||
|
100
README.md
100
README.md
@ -16,7 +16,7 @@ iOS required to use the **real name** of the font, this name can be different fr
|
|||||||
swift run -c release ResgenSwift fonts $FORCE_FLAG "./Fonts/fonts.txt" \
|
swift run -c release ResgenSwift fonts $FORCE_FLAG "./Fonts/fonts.txt" \
|
||||||
--extension-output-path "./Fonts/Generated" \
|
--extension-output-path "./Fonts/Generated" \
|
||||||
--extension-name "AppFont" \
|
--extension-name "AppFont" \
|
||||||
--extension-name-swift-ui "SUIAppFont" \
|
--extension-name-ui-kit "UIAppFont" \
|
||||||
--extension-suffix "GreatApp" \
|
--extension-suffix "GreatApp" \
|
||||||
--static-members true \
|
--static-members true \
|
||||||
--info-plist-paths "./path/one/to/Info.plist ./path/two/to/Info.plist"
|
--info-plist-paths "./path/one/to/Info.plist ./path/two/to/Info.plist"
|
||||||
@ -27,8 +27,8 @@ swift run -c release ResgenSwift fonts $FORCE_FLAG "./Fonts/fonts.txt" \
|
|||||||
1. `-f`: force generation
|
1. `-f`: force generation
|
||||||
2. Font input folder, it will search for every `.ttf` and `.otf` files specified in `fonts.txt`
|
2. Font input folder, it will search for every `.ttf` and `.otf` files specified in `fonts.txt`
|
||||||
3. `--extension-output-path`: path where to generate generated extension
|
3. `--extension-output-path`: path where to generate generated extension
|
||||||
4. `--extension-name` *(optional)* : name of the class to add UIKit getters
|
4. `--extension-name` *(optional)* : name of the class to add SwiftUI getters
|
||||||
5. `--extension-name-swift-ui` *(optional)* : name of the class to add SwiftUI getters
|
5. `--extension-name-ui-kit` *(optional)* : name of the class to add UIKit getters
|
||||||
6. `--extension-suffix` *(optional)* : additional text which is added to the filename (ex: `AppFont+GreatApp.swift`)
|
6. `--extension-suffix` *(optional)* : additional text which is added to the filename (ex: `AppFont+GreatApp.swift`)
|
||||||
7. `--static-members` *(optional)*: generate static properties or not
|
7. `--static-members` *(optional)*: generate static properties or not
|
||||||
8. `--info-plist-paths` *(optional)*: array of `.plist`, you can specify multiple `Info.plist` for multiple targets
|
8. `--info-plist-paths` *(optional)*: array of `.plist`, you can specify multiple `Info.plist` for multiple targets
|
||||||
@ -44,7 +44,7 @@ swift run -c release ResgenSwift colors $FORCE_FLAG "./Colors/colors.txt" \
|
|||||||
--xcassets-path "./Colors/colors.xcassets" \
|
--xcassets-path "./Colors/colors.xcassets" \
|
||||||
--extension-output-path "./Colors/Generated/" \
|
--extension-output-path "./Colors/Generated/" \
|
||||||
--extension-name "AppColor" \
|
--extension-name "AppColor" \
|
||||||
--extension-name-swift-ui "SUIAppColor" \
|
--extension-name-ui-kit "UIAppColor" \
|
||||||
--extension-suffix "GreatApp" \
|
--extension-suffix "GreatApp" \
|
||||||
--static-members true
|
--static-members true
|
||||||
```
|
```
|
||||||
@ -55,8 +55,8 @@ swift run -c release ResgenSwift colors $FORCE_FLAG "./Colors/colors.txt" \
|
|||||||
2. Input colors file
|
2. Input colors file
|
||||||
3. `--style` can be `all` or `light`
|
3. `--style` can be `all` or `light`
|
||||||
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 the class to add UIKit getters
|
5. `--extension-name` *(optional)* : name of the class to add SwiftUI getters
|
||||||
6. `--extension-name-swift-ui` *(optional)* : name of the class to add SwiftUI getters
|
6. `--extension-name-ui-kit` *(optional)* : name of the class to add UIKit getters
|
||||||
7. `--extension-suffix` *(optional)* : additional text which is added to filename (ex: `AppColor+GreatApp.swift`)
|
7. `--extension-suffix` *(optional)* : additional text which is added to filename (ex: `AppColor+GreatApp.swift`)
|
||||||
8. `--static-members` *(optional)*: generate static properties or not
|
8. `--static-members` *(optional)*: generate static properties or not
|
||||||
|
|
||||||
@ -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
|
||||||
|
|
||||||
|
|
||||||
@ -133,16 +135,81 @@ swift run -c release ResgenSwift strings tags $FORCE_FLAG "./Tags/tags.txt" \
|
|||||||
|
|
||||||
> ⚠️ If extension name is not set or is `Tags`, it will generate the following typaloas `typealias Tags = String`.
|
> ⚠️ If extension name is not set or is `Tags`, it will generate the following typaloas `typealias Tags = String`.
|
||||||
|
|
||||||
|
|
||||||
|
## Analytics
|
||||||
|
|
||||||
|
Analytics will generate all you need to analyze UX with Matomo or Firebase Analytics. Input files are formatted in YAML. This command will generate a manager for each target and an AnalyticsManager. This is this one you will need to use. And it will generate a method for all tags you have declared in the YAML file. Next, you will need to use the `configure()` method of AnalyticsManager and if you want to use matomo to set up the `siteId` and the `url` of the site.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
swift run -c release ResgenSwift strings tags $FORCE_FLAG "./Tags/tags.txt" \
|
||||||
|
--target "matomo firebase" \
|
||||||
|
--extension-output-path "./Analytics/Generated" \
|
||||||
|
--extension-name "AppAnalytics" \
|
||||||
|
--extension-suffix "GreatApp" \
|
||||||
|
--static-members true
|
||||||
|
```
|
||||||
|
|
||||||
|
**Parameters**
|
||||||
|
|
||||||
|
1. `-f`: force generation
|
||||||
|
2. Input tags file (must be YAML formatted)
|
||||||
|
3. `--target`: target with you will log UX
|
||||||
|
4. `--extension-output-path`: path where to generate generated extension
|
||||||
|
5. `--extension-name` *(optional)* : name of class to add the extension
|
||||||
|
6. `--extension-suffix` *(optional)* : additional text which is added to filename (ex: `AppAnalytics+GreatApp.swift`)
|
||||||
|
7. `--static-members` *(optional)*: generate static properties or not
|
||||||
|
|
||||||
|
> ⚠️ If extension name is not set or is `Analytics`, it will generate the following typaloas `typealias Analytics = String`.
|
||||||
|
|
||||||
|
### YAML
|
||||||
|
|
||||||
|
```
|
||||||
|
- id: s1_def_one
|
||||||
|
name: s1 def one _TITLE_
|
||||||
|
path: s1_def_one/_TITLE_
|
||||||
|
action: Tap
|
||||||
|
category: User
|
||||||
|
tags: ios,droid
|
||||||
|
comments:
|
||||||
|
parameters:
|
||||||
|
- name: title
|
||||||
|
type: String
|
||||||
|
replaceIn: name,path
|
||||||
|
```
|
||||||
|
|
||||||
|
1. `id`: name of the method (method name will be composed of `log` + `Event|Screen` + `id`)
|
||||||
|
2. `name`: name of the tag
|
||||||
|
3. `path` *(optional with firebase)* : needed for matomo but not with firebase (log screen)
|
||||||
|
4. `action` *(optional with firebase)* : needed for matomo but not with firebase (log event)
|
||||||
|
5. `category` *(optional with firebase)* : needed for matomo but not with firebase (log event)
|
||||||
|
6. `tags`: which platform target
|
||||||
|
7. `comments` *(optional)*
|
||||||
|
8. `parameters` *(optional)*
|
||||||
|
|
||||||
|
**Parameters**
|
||||||
|
|
||||||
|
You can use parameters in generate methods.
|
||||||
|
|
||||||
|
1. `name`: name of the parameter
|
||||||
|
2. `type`: type of the parameter (Int, String, Bool, Double)
|
||||||
|
3. `replaceIn` *(optional)*
|
||||||
|
|
||||||
|
**Replace in**
|
||||||
|
|
||||||
|
This is section is equivalent of `%s | %d | %f | %@`. You can put the content of the parameter in *name*, *path*, *action*, *category*.
|
||||||
|
You need to put `_` + `NAME OF THE PARAMETER` + `_` in the target and which target you want in the value of `replaceIn`. (name need to be in uppercase)
|
||||||
|
|
||||||
|
|
||||||
## 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" \
|
||||||
--xcassets-path "./Images/app.xcassets" \
|
--xcassets-path "./Images/app.xcassets" \
|
||||||
--extension-output-path "./Images/Generated" \
|
--extension-output-path "./Images/Generated" \
|
||||||
--extension-name "AppImage" \
|
--extension-name "AppImage" \
|
||||||
--extension-name-swift-ui "SUIAppImage" \
|
--extension-name-ui-kit "UIAppImage" \
|
||||||
--extension-suffix "GreatApp" \
|
--extension-suffix "GreatApp" \
|
||||||
--static-members true
|
--static-members true
|
||||||
```
|
```
|
||||||
@ -153,11 +220,12 @@ swift run -c release ResgenSwift images $FORCE_FLAG "./Images/images.txt" \
|
|||||||
2. Input images definitions file
|
2. Input images definitions file
|
||||||
3. `--xcassets-path`: xcasset path where to generate imagesets
|
3. `--xcassets-path`: xcasset path where to generate imagesets
|
||||||
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 the class to add UIKit getters
|
5. `--extension-name` *(optional)* : name of the class to add SwiftUI getters
|
||||||
6. `--extension-name-swift-ui` *(optional)* : name of the class to add SwiftUI getters
|
6. `--extension-name-ui-kit` *(optional)* : name of the class to add UIKit getters
|
||||||
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
|
||||||
|
|
||||||
@ -176,7 +244,7 @@ colors:
|
|||||||
xcassetsPath: String
|
xcassetsPath: String
|
||||||
extensionOutputPath: String
|
extensionOutputPath: String
|
||||||
extensionName: String?
|
extensionName: String?
|
||||||
extensionNameSwiftUI: String?
|
extensionNameUIKit: String?
|
||||||
extensionSuffix: String?
|
extensionSuffix: String?
|
||||||
staticMembers: Bool?
|
staticMembers: Bool?
|
||||||
|
|
||||||
@ -185,7 +253,7 @@ fonts:
|
|||||||
inputFile: String
|
inputFile: String
|
||||||
extensionOutputPath: String
|
extensionOutputPath: String
|
||||||
extensionName: String?
|
extensionName: String?
|
||||||
extensionNameSwiftUI: String?
|
extensionNameUIKit: String?
|
||||||
extensionSuffix: String?
|
extensionSuffix: String?
|
||||||
staticMembers: Bool?
|
staticMembers: Bool?
|
||||||
infoPlistPaths: [String]
|
infoPlistPaths: [String]
|
||||||
@ -196,7 +264,7 @@ images:
|
|||||||
xcassetsPath: String
|
xcassetsPath: String
|
||||||
extensionOutputPath: String
|
extensionOutputPath: String
|
||||||
extensionName: String?
|
extensionName: String?
|
||||||
extensionNameSwiftUI: String?
|
extensionNameUIKit: String?
|
||||||
extensionSuffix: String?
|
extensionSuffix: String?
|
||||||
staticMembers: Bool?
|
staticMembers: Bool?
|
||||||
|
|
||||||
@ -236,7 +304,7 @@ colors:
|
|||||||
xcassetsPath: String
|
xcassetsPath: String
|
||||||
extensionOutputPath: String
|
extensionOutputPath: String
|
||||||
extensionName: String?
|
extensionName: String?
|
||||||
extensionNameSwiftUI: String?
|
extensionNameUIKit: String?
|
||||||
extensionSuffix: String?
|
extensionSuffix: String?
|
||||||
staticMembers: Bool?
|
staticMembers: Bool?
|
||||||
-
|
-
|
||||||
@ -245,7 +313,7 @@ colors:
|
|||||||
xcassetsPath: String
|
xcassetsPath: String
|
||||||
extensionOutputPath: String
|
extensionOutputPath: String
|
||||||
extensionName: String?
|
extensionName: String?
|
||||||
extensionNameSwiftUI: String?
|
extensionNameUIKit: String?
|
||||||
extensionSuffix: String?
|
extensionSuffix: String?
|
||||||
staticMembers: Bool?
|
staticMembers: Bool?
|
||||||
...
|
...
|
||||||
|
205
SampleFiles/Tags/Generated/Analytics+GenAllScript.swift
Normal file
205
SampleFiles/Tags/Generated/Analytics+GenAllScript.swift
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
// Generated by ResgenSwift.Analytics 1.2
|
||||||
|
|
||||||
|
import MatomoTracker
|
||||||
|
import FirebaseAnalytics
|
||||||
|
|
||||||
|
// MARK: - Protocol
|
||||||
|
|
||||||
|
protocol AnalyticsManagerProtocol {
|
||||||
|
func logScreen(name: String, path: String)
|
||||||
|
func logEvent(
|
||||||
|
name: String,
|
||||||
|
action: String,
|
||||||
|
category: String,
|
||||||
|
params: [String: Any]?
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Matomo
|
||||||
|
|
||||||
|
class MatomoAnalyticsManager: AnalyticsManagerProtocol {
|
||||||
|
|
||||||
|
// MARK: - Properties
|
||||||
|
|
||||||
|
private var tracker: MatomoTracker
|
||||||
|
|
||||||
|
// MARK: - Init
|
||||||
|
|
||||||
|
init(siteId: String, url: String) {
|
||||||
|
debugPrint("[Matomo service] Server URL: \(url)")
|
||||||
|
debugPrint("[Matomo service] Site ID: \(siteId)")
|
||||||
|
tracker = MatomoTracker(
|
||||||
|
siteId: siteId,
|
||||||
|
baseURL: URL(string: url)!
|
||||||
|
)
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
tracker.dispatchInterval = 5
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
tracker.logger = DefaultLogger(minLevel: .verbose)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
debugPrint("[Matomo service] Configured with content base: \(tracker.contentBase?.absoluteString ?? "-")")
|
||||||
|
debugPrint("[Matomo service] Opt out: \(tracker.isOptedOut)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Methods
|
||||||
|
|
||||||
|
func logScreen(name: String, path: String) {
|
||||||
|
guard !tracker.isOptedOut else { return }
|
||||||
|
guard let trackerUrl = tracker.contentBase?.absoluteString else { return }
|
||||||
|
|
||||||
|
let urlString = URL(string: "\(trackerUrl)" + "/" + "\(path)" + "iOS")
|
||||||
|
tracker.track(
|
||||||
|
view: [name],
|
||||||
|
url: urlString
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func logEvent(
|
||||||
|
name: String,
|
||||||
|
action: String,
|
||||||
|
category: String,
|
||||||
|
params: [String: Any]?
|
||||||
|
) {
|
||||||
|
guard !tracker.isOptedOut else { return }
|
||||||
|
|
||||||
|
tracker.track(
|
||||||
|
eventWithCategory: category,
|
||||||
|
action: action,
|
||||||
|
name: name,
|
||||||
|
number: nil,
|
||||||
|
url: nil
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Firebase
|
||||||
|
|
||||||
|
class FirebaseAnalyticsManager: AnalyticsManagerProtocol {
|
||||||
|
func logScreen(name: String, path: String) {
|
||||||
|
var parameters = [
|
||||||
|
AnalyticsParameterScreenName: name as NSObject
|
||||||
|
]
|
||||||
|
|
||||||
|
Analytics.logEvent(
|
||||||
|
AnalyticsEventScreenView,
|
||||||
|
parameters: parameters
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func logEvent(
|
||||||
|
name: String,
|
||||||
|
action: String,
|
||||||
|
category: String,
|
||||||
|
params: [String: Any]?
|
||||||
|
) {
|
||||||
|
var parameters: [String:NSObject] = [
|
||||||
|
"action": action as NSObject,
|
||||||
|
"category": category as NSObject,
|
||||||
|
]
|
||||||
|
|
||||||
|
if let supplementaryParameters = params {
|
||||||
|
for (newKey, newValue) in supplementaryParameters {
|
||||||
|
if parameters.contains(where: { (key: String, value: NSObject) in
|
||||||
|
key == newKey
|
||||||
|
}) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
parameters[newKey] = newValue as? NSObject
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Analytics.logEvent(
|
||||||
|
name.replacingOccurrences(of: [" "], with: "_"),
|
||||||
|
parameters: parameters
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Manager
|
||||||
|
|
||||||
|
class AnalyticsManager {
|
||||||
|
static var shared = AnalyticsManager()
|
||||||
|
|
||||||
|
// MARK: - Properties
|
||||||
|
|
||||||
|
var managers: [AnalyticsManagerProtocol] = []
|
||||||
|
|
||||||
|
private var isEnabled: Bool = true
|
||||||
|
|
||||||
|
// MARK: - Methods
|
||||||
|
|
||||||
|
func setAnalyticsEnabled(_ enable: Bool) {
|
||||||
|
isEnabled = enable
|
||||||
|
}
|
||||||
|
|
||||||
|
func configure(siteId: String, url: String) {
|
||||||
|
managers.append(
|
||||||
|
MatomoAnalyticsManager(
|
||||||
|
siteId: siteId,
|
||||||
|
url: url
|
||||||
|
)
|
||||||
|
)
|
||||||
|
managers.append(FirebaseAnalyticsManager())
|
||||||
|
}
|
||||||
|
|
||||||
|
private func logScreen(name: String, path: String) {
|
||||||
|
guard isEnabled else { return }
|
||||||
|
|
||||||
|
managers.forEach { manager in
|
||||||
|
manager.logScreen(name: name, path: path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func logEvent(
|
||||||
|
name: String,
|
||||||
|
action: String,
|
||||||
|
category: String,
|
||||||
|
params: [String: Any]?
|
||||||
|
) {
|
||||||
|
guard isEnabled else { return }
|
||||||
|
|
||||||
|
managers.forEach { manager in
|
||||||
|
manager.logEvent(
|
||||||
|
name: name,
|
||||||
|
action: action,
|
||||||
|
category: category,
|
||||||
|
params: params
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - section_one
|
||||||
|
|
||||||
|
func logScreenS1DefOne(title: String) {
|
||||||
|
logScreen(
|
||||||
|
name: "s1 def one \(title)",
|
||||||
|
path: "s1_def_one/\(title)"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func logEventS1DefTwo(title: String, count: String) {
|
||||||
|
logEvent(
|
||||||
|
name: "s1 def two",
|
||||||
|
action: "test",
|
||||||
|
category: "test",
|
||||||
|
params: [
|
||||||
|
"title": title,
|
||||||
|
"count": count
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - section_two
|
||||||
|
|
||||||
|
func logScreenS2DefOne() {
|
||||||
|
logScreen(
|
||||||
|
name: "s2 def one",
|
||||||
|
path: "s2_def_one/"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
52
SampleFiles/Tags/sampleTags.yml
Normal file
52
SampleFiles/Tags/sampleTags.yml
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
---
|
||||||
|
categories:
|
||||||
|
- id: section_one
|
||||||
|
screens:
|
||||||
|
- id: s1_def_one
|
||||||
|
name: s1 def one _TITLE_
|
||||||
|
path: s1_def_one/_TITLE_
|
||||||
|
tags: ios,droid
|
||||||
|
parameters:
|
||||||
|
- name: title
|
||||||
|
type: String
|
||||||
|
replaceIn: name,path
|
||||||
|
|
||||||
|
events:
|
||||||
|
- id: s1_def_two
|
||||||
|
name: s1 def two
|
||||||
|
action: test
|
||||||
|
category: test
|
||||||
|
tags: ios
|
||||||
|
parameters:
|
||||||
|
- name: title
|
||||||
|
type: String
|
||||||
|
- name: count
|
||||||
|
type: String
|
||||||
|
|
||||||
|
- id: section_two
|
||||||
|
screens:
|
||||||
|
- id: s2_def_one
|
||||||
|
name: s2 def one
|
||||||
|
path: s2_def_one/
|
||||||
|
tags: ios
|
||||||
|
|
||||||
|
events:
|
||||||
|
- id: s2_def_two
|
||||||
|
name: s2 def two
|
||||||
|
action: test
|
||||||
|
category: test
|
||||||
|
tags: droid
|
||||||
|
|
||||||
|
- id: section_three
|
||||||
|
screens:
|
||||||
|
- id: s3_def_one
|
||||||
|
name: s3 def one
|
||||||
|
path: s3_def_one/
|
||||||
|
tags: droid
|
||||||
|
|
||||||
|
events:
|
||||||
|
- id: s3_def_two
|
||||||
|
name: s3 def two
|
||||||
|
action: test
|
||||||
|
category: test
|
||||||
|
tags: droid
|
@ -5,8 +5,8 @@ FORCE_FLAG="$1"
|
|||||||
## Font
|
## Font
|
||||||
#swift run -c release ResgenSwift fonts $FORCE_FLAG "./Fonts/sampleFontsAll.txt" \
|
#swift run -c release ResgenSwift fonts $FORCE_FLAG "./Fonts/sampleFontsAll.txt" \
|
||||||
# --extension-output-path "./Fonts/Generated" \
|
# --extension-output-path "./Fonts/Generated" \
|
||||||
# --extension-name "UIFontYolo" \
|
# --extension-name "FontYolo" \
|
||||||
# --extension-name-swift-ui "FontYolo" \
|
# --extension-name-ui-kit "UIFontYolo" \
|
||||||
# --extension-suffix "GenAllScript" \
|
# --extension-suffix "GenAllScript" \
|
||||||
# --info-plist-paths "./Fonts/Generated/test.plist ./Fonts/Generated/test2.plist"
|
# --info-plist-paths "./Fonts/Generated/test.plist ./Fonts/Generated/test2.plist"
|
||||||
#
|
#
|
||||||
@ -17,8 +17,8 @@ FORCE_FLAG="$1"
|
|||||||
# --style all \
|
# --style all \
|
||||||
# --xcassets-path "./Colors/colors.xcassets" \
|
# --xcassets-path "./Colors/colors.xcassets" \
|
||||||
# --extension-output-path "./Colors/Generated/" \
|
# --extension-output-path "./Colors/Generated/" \
|
||||||
# --extension-name "UIColorYolo" \
|
# --extension-name "ColorYolo" \
|
||||||
# --extension-name-swift-ui "ColorYolo" \
|
# --extension-name-ui-kit "UIhkjhkColorYolo" \
|
||||||
# --extension-suffix "GenAllScript"
|
# --extension-suffix "GenAllScript"
|
||||||
#
|
#
|
||||||
#echo "\n-------------------------\n"
|
#echo "\n-------------------------\n"
|
||||||
@ -30,18 +30,18 @@ FORCE_FLAG="$1"
|
|||||||
# --default-lang "en" \
|
# --default-lang "en" \
|
||||||
# --extension-output-path "./Twine/Generated"
|
# --extension-output-path "./Twine/Generated"
|
||||||
|
|
||||||
echo "\n-------------------------\n"
|
#echo "\n-------------------------\n"
|
||||||
|
|
||||||
# Strings
|
## Strings
|
||||||
swift run -c release ResgenSwift strings stringium $FORCE_FLAG "./Strings/sampleStrings.txt" \
|
#swift run -c release ResgenSwift strings stringium $FORCE_FLAG "./Strings/sampleStrings.txt" \
|
||||||
--output-path "./Strings/Generated" \
|
# --output-path "./Strings/Generated" \
|
||||||
--langs "fr en en-us" \
|
# --langs "fr en en-us" \
|
||||||
--default-lang "en" \
|
# --default-lang "en" \
|
||||||
--extension-output-path "./Strings/Generated" \
|
# --extension-output-path "./Strings/Generated" \
|
||||||
--extension-name "String" \
|
# --extension-name "String" \
|
||||||
--extension-suffix "GenAllScript"
|
# --extension-suffix "GenAllScript"
|
||||||
|
|
||||||
echo "\n-------------------------\n"
|
#echo "\n-------------------------\n"
|
||||||
|
|
||||||
## Tags
|
## Tags
|
||||||
#swift run -c release ResgenSwift strings tags $FORCE_FLAG "./Tags/sampleTags.txt" \
|
#swift run -c release ResgenSwift strings tags $FORCE_FLAG "./Tags/sampleTags.txt" \
|
||||||
@ -49,13 +49,22 @@ echo "\n-------------------------\n"
|
|||||||
# --extension-output-path "./Tags/Generated" \
|
# --extension-output-path "./Tags/Generated" \
|
||||||
# --extension-name "Tags" \
|
# --extension-name "Tags" \
|
||||||
# --extension-suffix "GenAllScript"
|
# --extension-suffix "GenAllScript"
|
||||||
#
|
|
||||||
|
#echo "\n-------------------------\n"
|
||||||
|
|
||||||
|
# Analytics
|
||||||
|
swift run -c release ResgenSwift analytics $FORCE_FLAG "./Tags/sampleTags.yml" \
|
||||||
|
--target "matomo firebase" \
|
||||||
|
--extension-output-path "./Tags/Generated" \
|
||||||
|
--extension-name "Analytics" \
|
||||||
|
--extension-suffix "GenAllScript"
|
||||||
|
|
||||||
#echo "\n-------------------------\n"
|
#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 "UIImage" \
|
# --extension-name "ImageYolo" \
|
||||||
# --extension-name-swift-ui "ImageYolo" \
|
# --extension-name-ui-kit "UIImageYolo" \
|
||||||
# --extension-suffix "GenAllScript"
|
# --extension-suffix "GenAllScript"
|
||||||
|
@ -47,8 +47,8 @@ images:
|
|||||||
inputFile: ./Images/sampleImages.txt
|
inputFile: ./Images/sampleImages.txt
|
||||||
xcassetsPath: ./Images/imagium.xcassets
|
xcassetsPath: ./Images/imagium.xcassets
|
||||||
extensionOutputPath: ./Images/Generated
|
extensionOutputPath: ./Images/Generated
|
||||||
extensionName: UIImage
|
extensionName: ImageYolo
|
||||||
extensionNameSwiftUI: ImageYolo
|
extensionNameUIKit: UIImageYolo
|
||||||
extensionSuffix: GenAllScript
|
extensionSuffix: GenAllScript
|
||||||
|
|
||||||
|
|
||||||
@ -61,8 +61,8 @@ colors:
|
|||||||
style: all
|
style: all
|
||||||
xcassetsPath: ./Colors/colors.xcassets
|
xcassetsPath: ./Colors/colors.xcassets
|
||||||
extensionOutputPath: ./Colors/Generated/
|
extensionOutputPath: ./Colors/Generated/
|
||||||
extensionName: UIColorYolo
|
extensionName: ColorYolo
|
||||||
extensionNameSwiftUI: ColorYolo
|
extensionNameUIKit: UIColorYolo
|
||||||
extensionSuffix: GenAllScript
|
extensionSuffix: GenAllScript
|
||||||
|
|
||||||
|
|
||||||
@ -70,7 +70,7 @@ colors:
|
|||||||
# Tags
|
# Tags
|
||||||
#
|
#
|
||||||
tags:
|
tags:
|
||||||
-
|
-
|
||||||
inputFile: ./Tags/sampleTags.txt
|
inputFile: ./Tags/sampleTags.txt
|
||||||
lang: ium
|
lang: ium
|
||||||
extensionOutputPath: ./Tags/Generated
|
extensionOutputPath: ./Tags/Generated
|
||||||
@ -78,6 +78,18 @@ tags:
|
|||||||
extensionSuffix: GenAllScript
|
extensionSuffix: GenAllScript
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Analytics
|
||||||
|
#
|
||||||
|
analytics:
|
||||||
|
-
|
||||||
|
inputFile: ./Tags/sampleTags.yml
|
||||||
|
target: "matomo firebase"
|
||||||
|
extensionOutputPath: ./Tags/Generated
|
||||||
|
extensionName: Analytics
|
||||||
|
extensionSuffix: GenAllScript
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Fonts
|
# Fonts
|
||||||
#
|
#
|
||||||
@ -85,7 +97,7 @@ fonts:
|
|||||||
-
|
-
|
||||||
inputFile: ./Fonts/sampleFontsAll.txt
|
inputFile: ./Fonts/sampleFontsAll.txt
|
||||||
extensionOutputPath: ./Fonts/Generated
|
extensionOutputPath: ./Fonts/Generated
|
||||||
extensionName: UIFontYolo
|
extensionName: FontYolo
|
||||||
extensionNameSwiftUI: FontYolo
|
extensionNameUIKit: UIFontYolo
|
||||||
extensionSuffix: GenAllScript
|
extensionSuffix: GenAllScript
|
||||||
infoPlistPaths: "./Fonts/Generated/test.plist ./Fonts/Generated/test2.plist"
|
infoPlistPaths: "./Fonts/Generated/test.plist ./Fonts/Generated/test2.plist"
|
||||||
|
86
Sources/ResgenSwift/Analytics/Analytics.swift
Normal file
86
Sources/ResgenSwift/Analytics/Analytics.swift
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
//
|
||||||
|
// Analytics.swift
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Created by Loris Perret on 08/12/2023.
|
||||||
|
//
|
||||||
|
|
||||||
|
import ToolCore
|
||||||
|
import Foundation
|
||||||
|
import ArgumentParser
|
||||||
|
|
||||||
|
struct Analytics: ParsableCommand {
|
||||||
|
|
||||||
|
// MARK: - Command Configuration
|
||||||
|
|
||||||
|
static var configuration = CommandConfiguration(
|
||||||
|
abstract: "Generate analytics extension file.",
|
||||||
|
version: ResgenSwiftVersion
|
||||||
|
)
|
||||||
|
|
||||||
|
// MARK: - Static
|
||||||
|
|
||||||
|
static let toolName = "Analytics"
|
||||||
|
static let defaultExtensionName = "Analytics"
|
||||||
|
|
||||||
|
// MARK: - Command Options
|
||||||
|
|
||||||
|
@OptionGroup var options: AnalyticsOptions
|
||||||
|
|
||||||
|
// MARK: - Run
|
||||||
|
|
||||||
|
mutating func run() {
|
||||||
|
print("[\(Self.toolName)] Starting analytics generation")
|
||||||
|
print("[\(Self.toolName)] Will use inputFile \(options.inputFile) to generate analytics for target: \(options.target)")
|
||||||
|
|
||||||
|
// Check requirements
|
||||||
|
guard checkRequirements() else { return }
|
||||||
|
|
||||||
|
print("[\(Self.toolName)] Will generate analytics")
|
||||||
|
|
||||||
|
// Check requirements
|
||||||
|
guard checkRequirements() else { return }
|
||||||
|
|
||||||
|
// Parse input file
|
||||||
|
let sections = AnalyticsFileParser.parse(options.inputFile, target: options.target)
|
||||||
|
|
||||||
|
// Generate extension
|
||||||
|
AnalyticsGenerator.writeExtensionFiles(sections: sections,
|
||||||
|
target: options.target,
|
||||||
|
tags: ["ios", "iosonly"],
|
||||||
|
staticVar: options.staticMembers,
|
||||||
|
extensionName: options.extensionName,
|
||||||
|
extensionFilePath: options.extensionFilePath)
|
||||||
|
|
||||||
|
print("[\(Self.toolName)] Analytics generated")
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Requirements
|
||||||
|
|
||||||
|
private func checkRequirements() -> Bool {
|
||||||
|
let fileManager = FileManager()
|
||||||
|
|
||||||
|
// Input file
|
||||||
|
guard fileManager.fileExists(atPath: options.inputFile) else {
|
||||||
|
let error = AnalyticsError.fileNotExists(options.inputFile)
|
||||||
|
print(error.description)
|
||||||
|
Analytics.exit(withError: error)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
47
Sources/ResgenSwift/Analytics/AnalyticsOptions.swift
Normal file
47
Sources/ResgenSwift/Analytics/AnalyticsOptions.swift
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
//
|
||||||
|
// AnalyticsOptions.swift
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Created by Loris Perret on 08/12/2023.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import ArgumentParser
|
||||||
|
|
||||||
|
struct AnalyticsOptions: ParsableArguments {
|
||||||
|
@Flag(name: [.customShort("f"), .customShort("F")], help: "Should force generation")
|
||||||
|
var forceGeneration = false
|
||||||
|
|
||||||
|
@Argument(help: "Input files where tags ared defined.", transform: { $0.replaceTiltWithHomeDirectoryPath() })
|
||||||
|
var inputFile: String
|
||||||
|
|
||||||
|
@Option(help: "Target(s) analytics to generate. (\"matomo\" | \"firebase\")")
|
||||||
|
var target: String
|
||||||
|
|
||||||
|
@Option(help: "Path where to generate the extension.", transform: { $0.replaceTiltWithHomeDirectoryPath() })
|
||||||
|
var extensionOutputPath: String
|
||||||
|
|
||||||
|
@Option(help: "Tell if it will generate static properties or not")
|
||||||
|
var staticMembers: Bool = false
|
||||||
|
|
||||||
|
@Option(help: "Extension name. If not specified, it will generate a Analytics extension.")
|
||||||
|
var extensionName: String = Analytics.defaultExtensionName
|
||||||
|
|
||||||
|
@Option(help: "Extension suffix. Ex: MyApp, it will generate {extensionName}+Analytics{extensionSuffix}.swift")
|
||||||
|
var extensionSuffix: String?
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Computed var
|
||||||
|
|
||||||
|
extension AnalyticsOptions {
|
||||||
|
var extensionFileName: String {
|
||||||
|
if let extensionSuffix = extensionSuffix {
|
||||||
|
return "\(extensionName)+\(extensionSuffix).swift"
|
||||||
|
}
|
||||||
|
return "\(extensionName).swift"
|
||||||
|
}
|
||||||
|
|
||||||
|
var extensionFilePath: String {
|
||||||
|
"\(extensionOutputPath)/\(extensionFileName)"
|
||||||
|
}
|
||||||
|
}
|
227
Sources/ResgenSwift/Analytics/Generator/AnalyticsGenerator.swift
Normal file
227
Sources/ResgenSwift/Analytics/Generator/AnalyticsGenerator.swift
Normal file
@ -0,0 +1,227 @@
|
|||||||
|
//
|
||||||
|
// AnalyticsGenerator.swift
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Created by Loris Perret on 08/12/2023.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import ToolCore
|
||||||
|
import CoreVideo
|
||||||
|
|
||||||
|
class AnalyticsGenerator {
|
||||||
|
static var targets: [TrackerType] = []
|
||||||
|
|
||||||
|
static func writeExtensionFiles(sections: [AnalyticsCategory], target: String, tags: [String], staticVar: Bool, extensionName: String, extensionFilePath: String) {
|
||||||
|
// Get target type from enum
|
||||||
|
let targetsString: [String] = target.components(separatedBy: " ")
|
||||||
|
|
||||||
|
TrackerType.allCases.forEach { enumTarget in
|
||||||
|
if targetsString.contains(enumTarget.value) {
|
||||||
|
targets.append(enumTarget)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get extension content
|
||||||
|
let extensionFileContent = Self.getExtensionContent(sections: sections,
|
||||||
|
tags: tags,
|
||||||
|
staticVar: staticVar,
|
||||||
|
extensionName: extensionName)
|
||||||
|
|
||||||
|
// Write content
|
||||||
|
let extensionFilePathURL = URL(fileURLWithPath: extensionFilePath)
|
||||||
|
do {
|
||||||
|
try extensionFileContent.write(to: extensionFilePathURL, atomically: false, encoding: .utf8)
|
||||||
|
} catch let error {
|
||||||
|
let error = AnalyticsError.writeFile(extensionFilePath, error.localizedDescription)
|
||||||
|
print(error.description)
|
||||||
|
Analytics.exit(withError: error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Extension content
|
||||||
|
|
||||||
|
static func getExtensionContent(sections: [AnalyticsCategory], tags: [String], staticVar: Bool, extensionName: String) -> String {
|
||||||
|
[
|
||||||
|
Self.getHeader(extensionClassname: extensionName, staticVar: staticVar),
|
||||||
|
Self.getProperties(sections: sections, tags: tags, staticVar: staticVar),
|
||||||
|
Self.getFooter()
|
||||||
|
]
|
||||||
|
.joined(separator: "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Extension part
|
||||||
|
|
||||||
|
private static func getHeader(extensionClassname: String, staticVar: Bool) -> String {
|
||||||
|
"""
|
||||||
|
// Generated by ResgenSwift.\(Analytics.toolName) \(ResgenSwiftVersion)
|
||||||
|
|
||||||
|
\(Self.getImport())
|
||||||
|
|
||||||
|
\(Self.getAnalyticsProtocol())
|
||||||
|
// MARK: - Manager
|
||||||
|
|
||||||
|
class AnalyticsManager {
|
||||||
|
static var shared = AnalyticsManager()
|
||||||
|
|
||||||
|
// MARK: - Properties
|
||||||
|
|
||||||
|
var managers: [AnalyticsManagerProtocol] = []
|
||||||
|
|
||||||
|
\(Self.getEnabledContent())
|
||||||
|
|
||||||
|
\(Self.getAnalyticsProperties())
|
||||||
|
|
||||||
|
\(Self.getPrivateLogFunction())
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func getEnabledContent() -> String {
|
||||||
|
"""
|
||||||
|
private var isEnabled: Bool = true
|
||||||
|
|
||||||
|
// MARK: - Methods
|
||||||
|
|
||||||
|
func setAnalyticsEnabled(_ enable: Bool) {
|
||||||
|
isEnabled = enable
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func getImport() -> String {
|
||||||
|
var result: [String] = []
|
||||||
|
|
||||||
|
if targets.contains(TrackerType.matomo) {
|
||||||
|
result.append("import MatomoTracker")
|
||||||
|
}
|
||||||
|
if targets.contains(TrackerType.firebase) {
|
||||||
|
result.append("import FirebaseAnalytics")
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.joined(separator: "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func getPrivateLogFunction() -> String {
|
||||||
|
"""
|
||||||
|
private func logScreen(name: String, path: String) {
|
||||||
|
guard isEnabled else { return }
|
||||||
|
|
||||||
|
managers.forEach { manager in
|
||||||
|
manager.logScreen(name: name, path: path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func logEvent(
|
||||||
|
name: String,
|
||||||
|
action: String,
|
||||||
|
category: String,
|
||||||
|
params: [String: Any]?
|
||||||
|
) {
|
||||||
|
guard isEnabled else { return }
|
||||||
|
|
||||||
|
managers.forEach { manager in
|
||||||
|
manager.logEvent(
|
||||||
|
name: name,
|
||||||
|
action: action,
|
||||||
|
category: category,
|
||||||
|
params: params
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func getAnalyticsProperties() -> String {
|
||||||
|
var header = ""
|
||||||
|
var content: [String] = []
|
||||||
|
let footer = " }"
|
||||||
|
|
||||||
|
if targets.contains(TrackerType.matomo) {
|
||||||
|
header = "func configure(siteId: String, url: String) {"
|
||||||
|
} else if targets.contains(TrackerType.firebase) {
|
||||||
|
header = "func configure() {"
|
||||||
|
}
|
||||||
|
|
||||||
|
if targets.contains(TrackerType.matomo) {
|
||||||
|
content.append("""
|
||||||
|
managers.append(
|
||||||
|
MatomoAnalyticsManager(
|
||||||
|
siteId: siteId,
|
||||||
|
url: url
|
||||||
|
)
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
if targets.contains(TrackerType.firebase) {
|
||||||
|
content.append(" managers.append(FirebaseAnalyticsManager())")
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
header,
|
||||||
|
content.joined(separator: "\n"),
|
||||||
|
footer
|
||||||
|
]
|
||||||
|
.joined(separator: "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func getAnalyticsProtocol() -> String {
|
||||||
|
let proto = """
|
||||||
|
// MARK: - Protocol
|
||||||
|
|
||||||
|
protocol AnalyticsManagerProtocol {
|
||||||
|
func logScreen(name: String, path: String)
|
||||||
|
func logEvent(
|
||||||
|
name: String,
|
||||||
|
action: String,
|
||||||
|
category: String,
|
||||||
|
params: [String: Any]?
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
var result: [String] = [proto]
|
||||||
|
|
||||||
|
if targets.contains(TrackerType.matomo) {
|
||||||
|
result.append(MatomoGenerator.service)
|
||||||
|
}
|
||||||
|
|
||||||
|
if targets.contains(TrackerType.firebase) {
|
||||||
|
result.append(FirebaseGenerator.service)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.joined(separator: "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func getProperties(sections: [AnalyticsCategory], tags: [String], staticVar: Bool) -> String {
|
||||||
|
sections
|
||||||
|
.compactMap { section in
|
||||||
|
// Check that at least one string will be generated
|
||||||
|
guard section.hasOneOrMoreMatchingTags(tags: tags) else {
|
||||||
|
return nil// Go to next section
|
||||||
|
}
|
||||||
|
|
||||||
|
var res = "\n // MARK: - \(section.id)"
|
||||||
|
section.definitions.forEach { definition in
|
||||||
|
guard definition.hasOneOrMoreMatchingTags(inputTags: tags) == true else {
|
||||||
|
return // Go to next definition
|
||||||
|
}
|
||||||
|
|
||||||
|
if staticVar {
|
||||||
|
res += "\n\n\(definition.getStaticProperty())"
|
||||||
|
} else {
|
||||||
|
res += "\n\n\(definition.getProperty())"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
.joined(separator: "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func getFooter() -> String {
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,87 @@
|
|||||||
|
//
|
||||||
|
// FirebaseGenerator.swift
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Created by Loris Perret on 05/12/2023.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
enum FirebaseGenerator {
|
||||||
|
|
||||||
|
static var service: String {
|
||||||
|
[
|
||||||
|
FirebaseGenerator.header,
|
||||||
|
FirebaseGenerator.logScreen,
|
||||||
|
FirebaseGenerator.logEvent,
|
||||||
|
FirebaseGenerator.footer
|
||||||
|
]
|
||||||
|
.joined(separator: "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Private vars
|
||||||
|
|
||||||
|
private static var header: String {
|
||||||
|
"""
|
||||||
|
// MARK: - Firebase
|
||||||
|
|
||||||
|
class FirebaseAnalyticsManager: AnalyticsManagerProtocol {
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
|
||||||
|
private static var logScreen: String {
|
||||||
|
"""
|
||||||
|
func logScreen(name: String, path: String) {
|
||||||
|
var parameters = [
|
||||||
|
AnalyticsParameterScreenName: name as NSObject
|
||||||
|
]
|
||||||
|
|
||||||
|
Analytics.logEvent(
|
||||||
|
AnalyticsEventScreenView,
|
||||||
|
parameters: parameters
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
|
||||||
|
private static var logEvent: String {
|
||||||
|
"""
|
||||||
|
func logEvent(
|
||||||
|
name: String,
|
||||||
|
action: String,
|
||||||
|
category: String,
|
||||||
|
params: [String: Any]?
|
||||||
|
) {
|
||||||
|
var parameters: [String:NSObject] = [
|
||||||
|
"action": action as NSObject,
|
||||||
|
"category": category as NSObject,
|
||||||
|
]
|
||||||
|
|
||||||
|
if let supplementaryParameters = params {
|
||||||
|
for (newKey, newValue) in supplementaryParameters {
|
||||||
|
if parameters.contains(where: { (key: String, value: NSObject) in
|
||||||
|
key == newKey
|
||||||
|
}) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
parameters[newKey] = newValue as? NSObject
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Analytics.logEvent(
|
||||||
|
name.replacingOccurrences(of: [" "], with: "_"),
|
||||||
|
parameters: parameters
|
||||||
|
)
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
|
||||||
|
private static var footer: String {
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
}
|
110
Sources/ResgenSwift/Analytics/Generator/MatomoGenerator.swift
Normal file
110
Sources/ResgenSwift/Analytics/Generator/MatomoGenerator.swift
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
//
|
||||||
|
// MatomoGenerator.swift
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Created by Loris Perret on 05/12/2023.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
enum MatomoGenerator {
|
||||||
|
|
||||||
|
static var service: String {
|
||||||
|
[
|
||||||
|
MatomoGenerator.header,
|
||||||
|
MatomoGenerator.setup,
|
||||||
|
MatomoGenerator.logScreen,
|
||||||
|
MatomoGenerator.logEvent,
|
||||||
|
MatomoGenerator.footer
|
||||||
|
]
|
||||||
|
.joined(separator: "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Private vars
|
||||||
|
|
||||||
|
private static var header: String {
|
||||||
|
"""
|
||||||
|
// MARK: - Matomo
|
||||||
|
|
||||||
|
class MatomoAnalyticsManager: AnalyticsManagerProtocol {
|
||||||
|
|
||||||
|
// MARK: - Properties
|
||||||
|
|
||||||
|
private var tracker: MatomoTracker
|
||||||
|
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
|
||||||
|
private static var setup: String {
|
||||||
|
"""
|
||||||
|
// MARK: - Init
|
||||||
|
|
||||||
|
init(siteId: String, url: String) {
|
||||||
|
debugPrint("[Matomo service] Server URL: \\(url)")
|
||||||
|
debugPrint("[Matomo service] Site ID: \\(siteId)")
|
||||||
|
tracker = MatomoTracker(
|
||||||
|
siteId: siteId,
|
||||||
|
baseURL: URL(string: url)!
|
||||||
|
)
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
tracker.dispatchInterval = 5
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
tracker.logger = DefaultLogger(minLevel: .verbose)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
debugPrint("[Matomo service] Configured with content base: \\(tracker.contentBase?.absoluteString ?? "-")")
|
||||||
|
debugPrint("[Matomo service] Opt out: \\(tracker.isOptedOut)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Methods
|
||||||
|
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
|
||||||
|
private static var logScreen: String {
|
||||||
|
"""
|
||||||
|
func logScreen(name: String, path: String) {
|
||||||
|
guard !tracker.isOptedOut else { return }
|
||||||
|
guard let trackerUrl = tracker.contentBase?.absoluteString else { return }
|
||||||
|
|
||||||
|
let urlString = URL(string: "\\(trackerUrl)" + "/" + "\\(path)" + "iOS")
|
||||||
|
tracker.track(
|
||||||
|
view: [name],
|
||||||
|
url: urlString
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
|
||||||
|
private static var logEvent: String {
|
||||||
|
"""
|
||||||
|
func logEvent(
|
||||||
|
name: String,
|
||||||
|
action: String,
|
||||||
|
category: String,
|
||||||
|
params: [String: Any]?
|
||||||
|
) {
|
||||||
|
guard !tracker.isOptedOut else { return }
|
||||||
|
|
||||||
|
tracker.track(
|
||||||
|
eventWithCategory: category,
|
||||||
|
action: action,
|
||||||
|
name: name,
|
||||||
|
number: nil,
|
||||||
|
url: nil
|
||||||
|
)
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
|
||||||
|
private static var footer: String {
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
}
|
32
Sources/ResgenSwift/Analytics/Model/AnalyticsCategory.swift
Normal file
32
Sources/ResgenSwift/Analytics/Model/AnalyticsCategory.swift
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
//
|
||||||
|
// AnalyticsCategory.swift
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Created by Loris Perret on 05/12/2023.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
class AnalyticsCategory {
|
||||||
|
let id: String // OnBoarding
|
||||||
|
var definitions = [AnalyticsDefinition]()
|
||||||
|
|
||||||
|
// MARK: - Init
|
||||||
|
|
||||||
|
init(id: String) {
|
||||||
|
self.id = id
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Methods
|
||||||
|
|
||||||
|
func hasOneOrMoreMatchingTags(tags: [String]) -> Bool {
|
||||||
|
let allTags = definitions.flatMap { $0.tags }
|
||||||
|
let allTagsSet = Set(allTags)
|
||||||
|
|
||||||
|
let intersection = Set(tags).intersection(allTagsSet)
|
||||||
|
if intersection.isEmpty {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
157
Sources/ResgenSwift/Analytics/Model/AnalyticsDefinition.swift
Normal file
157
Sources/ResgenSwift/Analytics/Model/AnalyticsDefinition.swift
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
//
|
||||||
|
// AnalyticsDefinition.swift
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Created by Loris Perret on 05/12/2023.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import ToolCore
|
||||||
|
|
||||||
|
class AnalyticsDefinition {
|
||||||
|
let id: String
|
||||||
|
var name: String
|
||||||
|
var path: String = ""
|
||||||
|
var category: String = ""
|
||||||
|
var action: String = ""
|
||||||
|
var comments: String = ""
|
||||||
|
var tags: [String] = []
|
||||||
|
var parameters: [AnalyticsParameter] = []
|
||||||
|
var type: TagType
|
||||||
|
|
||||||
|
// MARK: - Init
|
||||||
|
|
||||||
|
init(id: String, name: String, type: TagType) {
|
||||||
|
self.id = id
|
||||||
|
self.name = name
|
||||||
|
self.type = type
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Methods
|
||||||
|
|
||||||
|
func hasOneOrMoreMatchingTags(inputTags: [String]) -> Bool {
|
||||||
|
if Set(inputTags).isDisjoint(with: tags) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Private Methods
|
||||||
|
|
||||||
|
private func getFuncName() -> String {
|
||||||
|
var pascalCaseTitle: String = ""
|
||||||
|
id.components(separatedBy: "_").forEach { word in
|
||||||
|
pascalCaseTitle.append(contentsOf: word.uppercasedFirst())
|
||||||
|
}
|
||||||
|
|
||||||
|
return "log\(type == .screen ? "Screen" : "Event")\(pascalCaseTitle)"
|
||||||
|
}
|
||||||
|
|
||||||
|
private func getParameters() -> String {
|
||||||
|
var params = parameters
|
||||||
|
var result: String
|
||||||
|
|
||||||
|
if type == .screen {
|
||||||
|
params = params.filter { param in
|
||||||
|
!param.replaceIn.isEmpty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let paramsString = params.map { parameter in
|
||||||
|
"\(parameter.name): \(parameter.type)"
|
||||||
|
}
|
||||||
|
|
||||||
|
if paramsString.count > 2 {
|
||||||
|
result = """
|
||||||
|
(
|
||||||
|
\(paramsString.joined(separator: ",\n\t\t"))
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
} else {
|
||||||
|
result = """
|
||||||
|
(\(paramsString.joined(separator: ", ")))
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private func replaceIn() {
|
||||||
|
for parameter in parameters {
|
||||||
|
for rep in parameter.replaceIn {
|
||||||
|
switch rep {
|
||||||
|
case "name": name = name.replacingFirstOccurrence(of: "_\(parameter.name.uppercased())_", with: "\\(\(parameter.name))")
|
||||||
|
case "path": path = path.replacingFirstOccurrence(of: "_\(parameter.name.uppercased())_", with: "\\(\(parameter.name))")
|
||||||
|
case "category": category = category.replacingFirstOccurrence(of: "_\(parameter.name.uppercased())_", with: "\\(\(parameter.name))")
|
||||||
|
case "action": action = action.replacingFirstOccurrence(of: "_\(parameter.name.uppercased())_", with: "\\(\(parameter.name))")
|
||||||
|
default: break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func getlogFunction() -> String {
|
||||||
|
var params: [String] = []
|
||||||
|
var result: String
|
||||||
|
|
||||||
|
let supplementaryParams = parameters.filter { param in
|
||||||
|
param.replaceIn.isEmpty
|
||||||
|
}
|
||||||
|
|
||||||
|
supplementaryParams.forEach { param in
|
||||||
|
params.append("\"\(param.name)\": \(param.name)")
|
||||||
|
}
|
||||||
|
|
||||||
|
if params.count > 1 {
|
||||||
|
result = """
|
||||||
|
[
|
||||||
|
\(params.joined(separator: ",\n\t\t\t\t"))
|
||||||
|
]
|
||||||
|
"""
|
||||||
|
} else if params.count == 1 {
|
||||||
|
result = """
|
||||||
|
[\(params.joined(separator: ", "))]
|
||||||
|
"""
|
||||||
|
} else {
|
||||||
|
result = "[:]"
|
||||||
|
}
|
||||||
|
|
||||||
|
if type == .screen {
|
||||||
|
return """
|
||||||
|
logScreen(
|
||||||
|
name: "\(name)",
|
||||||
|
path: "\(path)"
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
} else {
|
||||||
|
return """
|
||||||
|
logEvent(
|
||||||
|
name: "\(name)",
|
||||||
|
action: "\(action)",
|
||||||
|
category: "\(category)",
|
||||||
|
params: \(result)
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Raw strings
|
||||||
|
|
||||||
|
func getProperty() -> String {
|
||||||
|
replaceIn()
|
||||||
|
return """
|
||||||
|
func \(getFuncName())\(getParameters()) {
|
||||||
|
\(getlogFunction())
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
|
||||||
|
func getStaticProperty() -> String {
|
||||||
|
replaceIn()
|
||||||
|
return """
|
||||||
|
static func \(getFuncName())\(getParameters()) {
|
||||||
|
\(getlogFunction())
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
}
|
45
Sources/ResgenSwift/Analytics/Model/AnalyticsFile.swift
Normal file
45
Sources/ResgenSwift/Analytics/Model/AnalyticsFile.swift
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
//
|
||||||
|
// AnalyticsFile.swift
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Created by Loris Perret on 06/12/2023.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct AnalyticsFile: Codable {
|
||||||
|
var categories: [AnalyticsCategoryDTO]
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AnalyticsCategoryDTO: Codable {
|
||||||
|
var id: String
|
||||||
|
var screens: [AnalyticsDefinitionScreenDTO]?
|
||||||
|
var events: [AnalyticsDefinitionEventDTO]?
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AnalyticsDefinitionScreenDTO: Codable {
|
||||||
|
var id: String
|
||||||
|
var name: String
|
||||||
|
var tags: String
|
||||||
|
var comments: String?
|
||||||
|
var parameters: [AnalyticsParameterDTO]?
|
||||||
|
|
||||||
|
var path: String?
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AnalyticsDefinitionEventDTO: Codable {
|
||||||
|
var id: String
|
||||||
|
var name: String
|
||||||
|
var tags: String
|
||||||
|
var comments: String?
|
||||||
|
var parameters: [AnalyticsParameterDTO]?
|
||||||
|
|
||||||
|
var category: String?
|
||||||
|
var action: String?
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AnalyticsParameterDTO: Codable {
|
||||||
|
var name: String
|
||||||
|
var type: String
|
||||||
|
var replaceIn: String?
|
||||||
|
}
|
21
Sources/ResgenSwift/Analytics/Model/AnalyticsParameter.swift
Normal file
21
Sources/ResgenSwift/Analytics/Model/AnalyticsParameter.swift
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
//
|
||||||
|
// AnalyticsParameter.swift
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Created by Loris Perret on 06/12/2023.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
class AnalyticsParameter {
|
||||||
|
var name: String
|
||||||
|
var type: String
|
||||||
|
var replaceIn: [String] = []
|
||||||
|
|
||||||
|
// MARK: - Init
|
||||||
|
|
||||||
|
init(name: String, type: String) {
|
||||||
|
self.name = name
|
||||||
|
self.type = type
|
||||||
|
}
|
||||||
|
}
|
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
|
||||||
|
}
|
||||||
|
}
|
178
Sources/ResgenSwift/Analytics/Parser/AnalyticsFileParser.swift
Normal file
178
Sources/ResgenSwift/Analytics/Parser/AnalyticsFileParser.swift
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
//
|
||||||
|
// AnalyticsFileParser.swift
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Created by Loris Perret on 06/12/2023.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import Yams
|
||||||
|
|
||||||
|
class AnalyticsFileParser {
|
||||||
|
private static var inputFile: String = ""
|
||||||
|
private static var target: String = ""
|
||||||
|
|
||||||
|
private static func parseYaml() -> AnalyticsFile {
|
||||||
|
guard let data = FileManager().contents(atPath: inputFile) else {
|
||||||
|
let error = AnalyticsError.fileNotExists(inputFile)
|
||||||
|
print(error.description)
|
||||||
|
Analytics.exit(withError: error)
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
let tagFile = try YAMLDecoder().decode(AnalyticsFile.self, from: data)
|
||||||
|
return tagFile
|
||||||
|
} catch {
|
||||||
|
let error = AnalyticsError.parseFailed(error.localizedDescription)
|
||||||
|
print(error.description)
|
||||||
|
Analytics.exit(withError: error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func getParameters(from parameters: [AnalyticsParameterDTO]) -> [AnalyticsParameter] {
|
||||||
|
parameters.map { dtoParameter in
|
||||||
|
// Type
|
||||||
|
|
||||||
|
let type = dtoParameter.type.uppercasedFirst()
|
||||||
|
|
||||||
|
guard
|
||||||
|
type == "String" ||
|
||||||
|
type == "Int" ||
|
||||||
|
type == "Double" ||
|
||||||
|
type == "Bool"
|
||||||
|
else {
|
||||||
|
let error = AnalyticsError.invalidParameter("type of \(dtoParameter.name)")
|
||||||
|
print(error.description)
|
||||||
|
Analytics.exit(withError: error)
|
||||||
|
}
|
||||||
|
|
||||||
|
let parameter = AnalyticsParameter(
|
||||||
|
name: dtoParameter.name,
|
||||||
|
type: type
|
||||||
|
)
|
||||||
|
|
||||||
|
if let replaceIn = dtoParameter.replaceIn {
|
||||||
|
parameter.replaceIn = replaceIn.components(separatedBy: ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
return parameter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func getTagDefinition(
|
||||||
|
id: String,
|
||||||
|
name: String,
|
||||||
|
type: AnalyticsDefinition.TagType,
|
||||||
|
tags: String,
|
||||||
|
comments: String?,
|
||||||
|
parameters: [AnalyticsParameterDTO]?
|
||||||
|
) -> AnalyticsDefinition {
|
||||||
|
let definition = AnalyticsDefinition(id: id, name: name, type: type)
|
||||||
|
definition.tags = tags
|
||||||
|
.components(separatedBy: ",")
|
||||||
|
.map { $0.removeLeadingTrailingWhitespace() }
|
||||||
|
|
||||||
|
if let comments = comments {
|
||||||
|
definition.comments = comments
|
||||||
|
}
|
||||||
|
|
||||||
|
if let parameters = parameters {
|
||||||
|
definition.parameters = Self.getParameters(from: parameters)
|
||||||
|
}
|
||||||
|
|
||||||
|
return definition
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func getTagDefinitionScreen(from screens: [AnalyticsDefinitionScreenDTO]) -> [AnalyticsDefinition] {
|
||||||
|
screens.map { screen in
|
||||||
|
let definition: AnalyticsDefinition = Self.getTagDefinition(
|
||||||
|
id: screen.id,
|
||||||
|
name: screen.name,
|
||||||
|
type: .screen,
|
||||||
|
tags: screen.tags,
|
||||||
|
comments: screen.comments,
|
||||||
|
parameters: screen.parameters
|
||||||
|
)
|
||||||
|
|
||||||
|
if target.contains(TrackerType.matomo.value) {
|
||||||
|
// Path
|
||||||
|
|
||||||
|
guard let path = screen.path else {
|
||||||
|
let error = AnalyticsError.missingElement("screen path")
|
||||||
|
print(error.description)
|
||||||
|
Analytics.exit(withError: error)
|
||||||
|
}
|
||||||
|
|
||||||
|
definition.path = path
|
||||||
|
}
|
||||||
|
|
||||||
|
return definition
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func getTagDefinitionEvent(from events: [AnalyticsDefinitionEventDTO]) -> [AnalyticsDefinition] {
|
||||||
|
events.map { event in
|
||||||
|
let definition: AnalyticsDefinition = Self.getTagDefinition(
|
||||||
|
id: event.id,
|
||||||
|
name: event.name,
|
||||||
|
type: .event,
|
||||||
|
tags: event.tags,
|
||||||
|
comments: event.comments,
|
||||||
|
parameters: event.parameters
|
||||||
|
)
|
||||||
|
|
||||||
|
if target.contains(TrackerType.matomo.value) {
|
||||||
|
// Category
|
||||||
|
guard let category = event.category else {
|
||||||
|
let error = AnalyticsError.missingElement("event category")
|
||||||
|
print(error.description)
|
||||||
|
Analytics.exit(withError: error)
|
||||||
|
}
|
||||||
|
|
||||||
|
definition.category = category
|
||||||
|
|
||||||
|
// Action
|
||||||
|
guard let action = event.action else {
|
||||||
|
let error = AnalyticsError.missingElement("event action")
|
||||||
|
print(error.description)
|
||||||
|
Analytics.exit(withError: error)
|
||||||
|
}
|
||||||
|
|
||||||
|
definition.action = action
|
||||||
|
}
|
||||||
|
|
||||||
|
return definition
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static func parse(_ inputFile: String, target: String) -> [AnalyticsCategory] {
|
||||||
|
self.inputFile = inputFile
|
||||||
|
self.target = target
|
||||||
|
|
||||||
|
let tagFile = Self.parseYaml()
|
||||||
|
|
||||||
|
return tagFile
|
||||||
|
.categories
|
||||||
|
.map { categorie in
|
||||||
|
let section: AnalyticsCategory = AnalyticsCategory(id: categorie.id)
|
||||||
|
|
||||||
|
if let screens = categorie.screens {
|
||||||
|
section
|
||||||
|
.definitions
|
||||||
|
.append(
|
||||||
|
contentsOf: Self.getTagDefinitionScreen(from: screens)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let events = categorie.events {
|
||||||
|
section
|
||||||
|
.definitions
|
||||||
|
.append(
|
||||||
|
contentsOf: Self.getTagDefinitionEvent(from: events)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return section
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -21,8 +21,8 @@ struct Colors: ParsableCommand {
|
|||||||
// MARK: - Static
|
// MARK: - Static
|
||||||
|
|
||||||
static let toolName = "Color"
|
static let toolName = "Color"
|
||||||
static let defaultExtensionName = "UIColor"
|
static let defaultExtensionName = "Color"
|
||||||
static let defaultExtensionNameSUI = "Color"
|
static let defaultExtensionNameUIKit = "UIColor"
|
||||||
static let assetsColorsFolderName = "Colors"
|
static let assetsColorsFolderName = "Colors"
|
||||||
|
|
||||||
// MARK: - Command options
|
// MARK: - Command options
|
||||||
@ -57,14 +57,14 @@ struct Colors: ParsableCommand {
|
|||||||
staticVar: options.staticMembers,
|
staticVar: options.staticMembers,
|
||||||
extensionName: options.extensionName,
|
extensionName: options.extensionName,
|
||||||
extensionFilePath: options.extensionFilePath,
|
extensionFilePath: options.extensionFilePath,
|
||||||
isSwiftUI: false)
|
isSwiftUI: true)
|
||||||
|
|
||||||
// Generate extension
|
// Generate extension
|
||||||
ColorExtensionGenerator.writeExtensionFile(colors: parsedColors,
|
ColorExtensionGenerator.writeExtensionFile(colors: parsedColors,
|
||||||
staticVar: options.staticMembers,
|
staticVar: options.staticMembers,
|
||||||
extensionName: options.extensionNameSwiftUI,
|
extensionName: options.extensionNameUIKit,
|
||||||
extensionFilePath: options.extensionFilePathSwiftUI,
|
extensionFilePath: options.extensionFilePathUIKit,
|
||||||
isSwiftUI: true)
|
isSwiftUI: false)
|
||||||
|
|
||||||
print("[\(Self.toolName)] Colors generated")
|
print("[\(Self.toolName)] Colors generated")
|
||||||
}
|
}
|
||||||
@ -89,7 +89,7 @@ struct Colors: ParsableCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Extension for UIKit and SwiftUI should have different name
|
// Extension for UIKit and SwiftUI should have different name
|
||||||
guard options.extensionName != options.extensionNameSwiftUI else {
|
guard options.extensionName != options.extensionNameUIKit else {
|
||||||
let error = ColorsToolError.extensionNamesCollision(options.extensionName)
|
let error = ColorsToolError.extensionNamesCollision(options.extensionName)
|
||||||
print(error.description)
|
print(error.description)
|
||||||
Colors.exit(withError: error)
|
Colors.exit(withError: error)
|
||||||
|
@ -27,11 +27,11 @@ struct ColorsToolOptions: 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: "Extension name. If not specified, it will generate an UIColor extension.")
|
@Option(help: "Extension name. If not specified, it will generate an Color extension.")
|
||||||
var extensionName: String = Colors.defaultExtensionName
|
var extensionName: String = Colors.defaultExtensionName
|
||||||
|
|
||||||
@Option(help: "SwiftUI Extension name. If not specified, it will generate an Color extension.")
|
@Option(help: "SwiftUI Extension name. If not specified, it will generate an UIColor extension.")
|
||||||
var extensionNameSwiftUI: String = Colors.defaultExtensionNameSUI
|
var extensionNameUIKit: String = Colors.defaultExtensionNameUIKit
|
||||||
|
|
||||||
@Option(help: "Extension suffix. Ex: MyApp, it will generate {extensionName}+ColorsMyApp.swift")
|
@Option(help: "Extension suffix. Ex: MyApp, it will generate {extensionName}+ColorsMyApp.swift")
|
||||||
var extensionSuffix: String?
|
var extensionSuffix: String?
|
||||||
@ -41,7 +41,7 @@ struct ColorsToolOptions: ParsableArguments {
|
|||||||
|
|
||||||
extension ColorsToolOptions {
|
extension ColorsToolOptions {
|
||||||
|
|
||||||
// MARK: - UIKit
|
// MARK: - SwiftUI
|
||||||
|
|
||||||
var extensionFileName: String {
|
var extensionFileName: String {
|
||||||
if let extensionSuffix = extensionSuffix {
|
if let extensionSuffix = extensionSuffix {
|
||||||
@ -54,16 +54,16 @@ extension ColorsToolOptions {
|
|||||||
"\(extensionOutputPath)/\(extensionFileName)"
|
"\(extensionOutputPath)/\(extensionFileName)"
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - SwiftUI
|
// MARK: - UIKit
|
||||||
|
|
||||||
var extensionFileNameSwiftUI: String {
|
var extensionFileNameUIKit: String {
|
||||||
if let extensionSuffix = extensionSuffix {
|
if let extensionSuffix = extensionSuffix {
|
||||||
return "\(extensionNameSwiftUI)+\(extensionSuffix).swift"
|
return "\(extensionNameUIKit)+\(extensionSuffix).swift"
|
||||||
}
|
}
|
||||||
return "\(extensionNameSwiftUI).swift"
|
return "\(extensionNameUIKit).swift"
|
||||||
}
|
}
|
||||||
|
|
||||||
var extensionFilePathSwiftUI: String {
|
var extensionFilePathUIKit: String {
|
||||||
"\(extensionOutputPath)/\(extensionFileNameSwiftUI)"
|
"\(extensionOutputPath)/\(extensionFileNameUIKit)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ struct ColorExtensionGenerator {
|
|||||||
let extensionFilePathURL = URL(fileURLWithPath: extensionFilePath)
|
let extensionFilePathURL = URL(fileURLWithPath: extensionFilePath)
|
||||||
do {
|
do {
|
||||||
try extensionContent.write(to: extensionFilePathURL, atomically: false, encoding: .utf8)
|
try extensionContent.write(to: extensionFilePathURL, atomically: false, encoding: .utf8)
|
||||||
} catch (let error) {
|
} catch let error {
|
||||||
let error = ColorsToolError.writeExtension(extensionFilePath, error.localizedDescription)
|
let error = ColorsToolError.writeExtension(extensionFilePath, error.localizedDescription)
|
||||||
print(error.description)
|
print(error.description)
|
||||||
Colors.exit(withError: error)
|
Colors.exit(withError: error)
|
||||||
|
@ -38,7 +38,7 @@ struct ColorXcassetHelper {
|
|||||||
let contentsJsonPathURL = URL(fileURLWithPath: contentsJsonPath)
|
let contentsJsonPathURL = URL(fileURLWithPath: contentsJsonPath)
|
||||||
do {
|
do {
|
||||||
try color.contentsJSON().write(to: contentsJsonPathURL, atomically: false, encoding: .utf8)
|
try color.contentsJSON().write(to: contentsJsonPathURL, atomically: false, encoding: .utf8)
|
||||||
} catch (let error) {
|
} catch let error {
|
||||||
let error = ColorsToolError.writeAsset(error.localizedDescription)
|
let error = ColorsToolError.writeAsset(error.localizedDescription)
|
||||||
print(error.description)
|
print(error.description)
|
||||||
Colors.exit(withError: error)
|
Colors.exit(withError: error)
|
||||||
|
@ -21,11 +21,11 @@ struct FontsOptions: ParsableArguments {
|
|||||||
@Option(help: "Tell if it will generate static properties or methods")
|
@Option(help: "Tell if it will generate static properties or methods")
|
||||||
var staticMembers: Bool = false
|
var staticMembers: Bool = false
|
||||||
|
|
||||||
@Option(help: "Extension name. If not specified, it will generate an UIFont extension.")
|
@Option(help: "Extension name. If not specified, it will generate an Font extension.")
|
||||||
var extensionName: String = Fonts.defaultExtensionName
|
var extensionName: String = Fonts.defaultExtensionName
|
||||||
|
|
||||||
@Option(help: "Extension name. If not specified, it will generate an Font extension.")
|
@Option(help: "Extension name. If not specified, it will generate an UIFont extension.")
|
||||||
var extensionNameSwiftUI: String = Fonts.defaultExtensionNameSUI
|
var extensionNameUIKit: String = Fonts.defaultExtensionNameUIKit
|
||||||
|
|
||||||
@Option(help: "Extension suffix. Ex: MyApp, it will generate {extensionName}+FontsMyApp.swift")
|
@Option(help: "Extension suffix. Ex: MyApp, it will generate {extensionName}+FontsMyApp.swift")
|
||||||
var extensionSuffix: String = ""
|
var extensionSuffix: String = ""
|
||||||
@ -38,7 +38,7 @@ struct FontsOptions: ParsableArguments {
|
|||||||
|
|
||||||
extension FontsOptions {
|
extension FontsOptions {
|
||||||
|
|
||||||
// MARK: - UIKit
|
// MARK: - SwiftUI
|
||||||
|
|
||||||
var extensionFileName: String {
|
var extensionFileName: String {
|
||||||
if extensionSuffix.isEmpty == false {
|
if extensionSuffix.isEmpty == false {
|
||||||
@ -51,17 +51,17 @@ extension FontsOptions {
|
|||||||
"\(extensionOutputPath)/\(extensionFileName)"
|
"\(extensionOutputPath)/\(extensionFileName)"
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - SwiftUI
|
// MARK: - UIKit
|
||||||
|
|
||||||
var extensionFileNameSwiftUI: String {
|
var extensionFileNameUIKit: String {
|
||||||
if extensionSuffix.isEmpty == false {
|
if extensionSuffix.isEmpty == false {
|
||||||
return "\(extensionNameSwiftUI)+\(extensionSuffix).swift"
|
return "\(extensionNameUIKit)+\(extensionSuffix).swift"
|
||||||
}
|
}
|
||||||
return "\(extensionNameSwiftUI).swift"
|
return "\(extensionNameUIKit).swift"
|
||||||
}
|
}
|
||||||
|
|
||||||
var extensionFilePathSwiftUI: String {
|
var extensionFilePathUIKit: String {
|
||||||
"\(extensionOutputPath)/\(extensionFileNameSwiftUI)"
|
"\(extensionOutputPath)/\(extensionFileNameUIKit)"
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: -
|
// MARK: -
|
||||||
|
@ -21,8 +21,8 @@ struct Fonts: ParsableCommand {
|
|||||||
// MARK: - Static
|
// MARK: - Static
|
||||||
|
|
||||||
static let toolName = "Fonts"
|
static let toolName = "Fonts"
|
||||||
static let defaultExtensionName = "UIFont"
|
static let defaultExtensionName = "Font"
|
||||||
static let defaultExtensionNameSUI = "Font"
|
static let defaultExtensionNameUIKit = "UIFont"
|
||||||
|
|
||||||
// MARK: - Command Options
|
// MARK: - Command Options
|
||||||
|
|
||||||
@ -52,13 +52,13 @@ struct Fonts: ParsableCommand {
|
|||||||
staticVar: options.staticMembers,
|
staticVar: options.staticMembers,
|
||||||
extensionName: options.extensionName,
|
extensionName: options.extensionName,
|
||||||
extensionFilePath: options.extensionFilePath,
|
extensionFilePath: options.extensionFilePath,
|
||||||
isSwiftUI: false)
|
isSwiftUI: true)
|
||||||
|
|
||||||
FontExtensionGenerator.writeExtensionFile(fontsNames: fontsNames,
|
FontExtensionGenerator.writeExtensionFile(fontsNames: fontsNames,
|
||||||
staticVar: options.staticMembers,
|
staticVar: options.staticMembers,
|
||||||
extensionName: options.extensionNameSwiftUI,
|
extensionName: options.extensionNameUIKit,
|
||||||
extensionFilePath: options.extensionFilePathSwiftUI,
|
extensionFilePath: options.extensionFilePathUIKit,
|
||||||
isSwiftUI: true)
|
isSwiftUI: false)
|
||||||
|
|
||||||
print("Info.plist has been updated with:")
|
print("Info.plist has been updated with:")
|
||||||
print("\(FontPlistGenerator.generatePlistUIAppsFontContent(for: fontsNames, infoPlistPaths: options.infoPlistPaths))")
|
print("\(FontPlistGenerator.generatePlistUIAppsFontContent(for: fontsNames, infoPlistPaths: options.infoPlistPaths))")
|
||||||
@ -79,7 +79,7 @@ struct Fonts: ParsableCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Extension for UIKit and SwiftUI should have different name
|
// Extension for UIKit and SwiftUI should have different name
|
||||||
guard options.extensionName != options.extensionNameSwiftUI else {
|
guard options.extensionName != options.extensionNameUIKit else {
|
||||||
let error = FontsToolError.extensionNamesCollision(options.extensionName)
|
let error = FontsToolError.extensionNamesCollision(options.extensionName)
|
||||||
print(error.description)
|
print(error.description)
|
||||||
Fonts.exit(withError: error)
|
Fonts.exit(withError: error)
|
||||||
|
@ -36,7 +36,7 @@ class FontExtensionGenerator {
|
|||||||
let extensionFilePathURL = URL(fileURLWithPath: extensionFilePath)
|
let extensionFilePathURL = URL(fileURLWithPath: extensionFilePath)
|
||||||
do {
|
do {
|
||||||
try extensionContent.write(to: extensionFilePathURL, atomically: false, encoding: .utf8)
|
try extensionContent.write(to: extensionFilePathURL, atomically: false, encoding: .utf8)
|
||||||
} catch (let error) {
|
} catch let error {
|
||||||
let error = FontsToolError.writeExtension(extensionFilePath, error.localizedDescription)
|
let error = FontsToolError.writeExtension(extensionFilePath, error.localizedDescription)
|
||||||
print(error.description)
|
print(error.description)
|
||||||
Fonts.exit(withError: error)
|
Fonts.exit(withError: error)
|
||||||
|
@ -34,6 +34,7 @@ struct Generate: ParsableCommand {
|
|||||||
// Parse
|
// Parse
|
||||||
let configuration = ConfigurationFileParser.parse(options.configurationFile)
|
let configuration = ConfigurationFileParser.parse(options.configurationFile)
|
||||||
print("Found configurations :")
|
print("Found configurations :")
|
||||||
|
print(" - \(configuration.analytics.count) analytics configuration(s)")
|
||||||
print(" - \(configuration.colors.count) colors configuration(s)")
|
print(" - \(configuration.colors.count) colors configuration(s)")
|
||||||
print(" - \(configuration.fonts.count) fonts configuration(s)")
|
print(" - \(configuration.fonts.count) fonts configuration(s)")
|
||||||
print(" - \(configuration.images.count) images configuration(s)")
|
print(" - \(configuration.images.count) images configuration(s)")
|
||||||
|
@ -9,7 +9,7 @@ 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)
|
||||||
|
|
||||||
@ -18,8 +18,8 @@ enum GenerateError: Error {
|
|||||||
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
|
||||||
|
@ -5,8 +5,6 @@
|
|||||||
// Created by Thibaut Schmitt on 30/08/2022.
|
// Created by Thibaut Schmitt on 30/08/2022.
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import ArgumentParser
|
import ArgumentParser
|
||||||
|
|
||||||
|
@ -11,12 +11,14 @@ import Foundation
|
|||||||
struct ArchitectureGenerator {
|
struct ArchitectureGenerator {
|
||||||
static func writeArchitecture(_ architecture: ConfigurationArchitecture, projectDirectory: String) {
|
static func writeArchitecture(_ architecture: ConfigurationArchitecture, projectDirectory: String) {
|
||||||
// Create extension content
|
// Create extension content
|
||||||
let architectureContent = [
|
var architectureContent = [
|
||||||
"// Generated by ResgenSwift.\(Generate.toolName) \(ResgenSwiftVersion)",
|
"// Generated by ResgenSwift.\(Generate.toolName) \(ResgenSwiftVersion)",
|
||||||
architecture.getClass()
|
architecture.getClass()
|
||||||
]
|
]
|
||||||
.joined(separator: "\n\n")
|
.joined(separator: "\n\n")
|
||||||
|
|
||||||
|
architectureContent += "\n"
|
||||||
|
|
||||||
let filename = "\(architecture.classname).swift"
|
let filename = "\(architecture.classname).swift"
|
||||||
guard let filePath = architecture.path?.prependIfRelativePath(projectDirectory) else {
|
guard let filePath = architecture.path?.prependIfRelativePath(projectDirectory) else {
|
||||||
let error = GenerateError.writeFile(filename, "Path of file is not defined.")
|
let error = GenerateError.writeFile(filename, "Path of file is not defined.")
|
||||||
@ -28,7 +30,7 @@ struct ArchitectureGenerator {
|
|||||||
let architectureFilePathURL = URL(fileURLWithPath: "\(filePath)/\(filename)")
|
let architectureFilePathURL = URL(fileURLWithPath: "\(filePath)/\(filename)")
|
||||||
do {
|
do {
|
||||||
try architectureContent.write(to: architectureFilePathURL, atomically: false, encoding: .utf8)
|
try architectureContent.write(to: architectureFilePathURL, atomically: false, encoding: .utf8)
|
||||||
} catch (let error) {
|
} catch let error {
|
||||||
let error = GenerateError.writeFile(filename, error.localizedDescription)
|
let error = GenerateError.writeFile(filename, error.localizedDescription)
|
||||||
print(error.description)
|
print(error.description)
|
||||||
Generate.exit(withError: error)
|
Generate.exit(withError: error)
|
||||||
|
@ -9,6 +9,7 @@ import Foundation
|
|||||||
|
|
||||||
struct ConfigurationFile: Codable, CustomDebugStringConvertible {
|
struct ConfigurationFile: Codable, CustomDebugStringConvertible {
|
||||||
var architecture: ConfigurationArchitecture?
|
var architecture: ConfigurationArchitecture?
|
||||||
|
var analytics: [AnalyticsConfiguration]
|
||||||
var colors: [ColorsConfiguration]
|
var colors: [ColorsConfiguration]
|
||||||
var fonts: [FontsConfiguration]
|
var fonts: [FontsConfiguration]
|
||||||
var images: [ImagesConfiguration]
|
var images: [ImagesConfiguration]
|
||||||
@ -16,12 +17,15 @@ struct ConfigurationFile: Codable, CustomDebugStringConvertible {
|
|||||||
var tags: [TagsConfiguration]
|
var tags: [TagsConfiguration]
|
||||||
|
|
||||||
var runnableConfigurations: [Runnable] {
|
var runnableConfigurations: [Runnable] {
|
||||||
let runnables: [[Runnable]] = [colors, fonts, images, strings, tags]
|
let runnables: [[Runnable]] = [analytics, colors, fonts, images, strings, tags]
|
||||||
return Array(runnables.joined())
|
return Array(runnables.joined())
|
||||||
}
|
}
|
||||||
|
|
||||||
var debugDescription: String {
|
var debugDescription: String {
|
||||||
"""
|
"""
|
||||||
|
\(analytics)
|
||||||
|
-----------
|
||||||
|
-----------
|
||||||
\(colors)
|
\(colors)
|
||||||
-----------
|
-----------
|
||||||
-----------
|
-----------
|
||||||
@ -51,7 +55,7 @@ struct ConfigurationArchitecture: Codable {
|
|||||||
|
|
||||||
func getClass(generateStaticProperty: Bool = true) -> String {
|
func getClass(generateStaticProperty: Bool = true) -> String {
|
||||||
guard children?.isEmpty == false else {
|
guard children?.isEmpty == false else {
|
||||||
return "class \(classname) {}"
|
return "final class \(classname): Sendable {}"
|
||||||
}
|
}
|
||||||
|
|
||||||
let classDefinition = [
|
let classDefinition = [
|
||||||
@ -76,13 +80,54 @@ struct ConfigurationArchitecture: Codable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct AnalyticsConfiguration: Codable, CustomDebugStringConvertible {
|
||||||
|
let inputFile: String
|
||||||
|
let target: String
|
||||||
|
let extensionOutputPath: String
|
||||||
|
let extensionName: String?
|
||||||
|
let extensionSuffix: String?
|
||||||
|
private let staticMembers: Bool?
|
||||||
|
|
||||||
|
var staticMembersOptions: Bool {
|
||||||
|
if let staticMembers = staticMembers {
|
||||||
|
return staticMembers
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
internal init(inputFile: String,
|
||||||
|
target: String,
|
||||||
|
extensionOutputPath: String,
|
||||||
|
extensionName: String?,
|
||||||
|
extensionSuffix: String?,
|
||||||
|
staticMembers: Bool?) {
|
||||||
|
self.inputFile = inputFile
|
||||||
|
self.target = target
|
||||||
|
self.extensionOutputPath = extensionOutputPath
|
||||||
|
self.extensionName = extensionName
|
||||||
|
self.extensionSuffix = extensionSuffix
|
||||||
|
self.staticMembers = staticMembers
|
||||||
|
}
|
||||||
|
|
||||||
|
var debugDescription: String {
|
||||||
|
"""
|
||||||
|
Analytics configuration:
|
||||||
|
- Input file: \(inputFile)
|
||||||
|
- Target: \(target)
|
||||||
|
- Extension output path: \(extensionOutputPath)
|
||||||
|
- Extension name: \(extensionName ?? "-")
|
||||||
|
- Extension suffix: \(extensionSuffix ?? "-")
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct ColorsConfiguration: Codable, CustomDebugStringConvertible {
|
struct ColorsConfiguration: Codable, CustomDebugStringConvertible {
|
||||||
let inputFile: String
|
let inputFile: String
|
||||||
let style: String
|
let style: String
|
||||||
let xcassetsPath: String
|
let xcassetsPath: String
|
||||||
let extensionOutputPath: String
|
let extensionOutputPath: String
|
||||||
let extensionName: String?
|
let extensionName: String?
|
||||||
let extensionNameSwiftUI: String?
|
let extensionNameUIKit: String?
|
||||||
let extensionSuffix: String?
|
let extensionSuffix: String?
|
||||||
private let staticMembers: Bool?
|
private let staticMembers: Bool?
|
||||||
|
|
||||||
@ -98,7 +143,7 @@ struct ColorsConfiguration: Codable, CustomDebugStringConvertible {
|
|||||||
xcassetsPath: String,
|
xcassetsPath: String,
|
||||||
extensionOutputPath: String,
|
extensionOutputPath: String,
|
||||||
extensionName: String?,
|
extensionName: String?,
|
||||||
extensionNameSwiftUI: String?,
|
extensionNameUIKit: String?,
|
||||||
extensionSuffix: String?,
|
extensionSuffix: String?,
|
||||||
staticMembers: Bool?) {
|
staticMembers: Bool?) {
|
||||||
self.inputFile = inputFile
|
self.inputFile = inputFile
|
||||||
@ -106,7 +151,7 @@ struct ColorsConfiguration: Codable, CustomDebugStringConvertible {
|
|||||||
self.xcassetsPath = xcassetsPath
|
self.xcassetsPath = xcassetsPath
|
||||||
self.extensionOutputPath = extensionOutputPath
|
self.extensionOutputPath = extensionOutputPath
|
||||||
self.extensionName = extensionName
|
self.extensionName = extensionName
|
||||||
self.extensionNameSwiftUI = extensionNameSwiftUI
|
self.extensionNameUIKit = extensionNameUIKit
|
||||||
self.extensionSuffix = extensionSuffix
|
self.extensionSuffix = extensionSuffix
|
||||||
self.staticMembers = staticMembers
|
self.staticMembers = staticMembers
|
||||||
}
|
}
|
||||||
@ -119,7 +164,7 @@ struct ColorsConfiguration: Codable, CustomDebugStringConvertible {
|
|||||||
- Xcassets path: \(xcassetsPath)
|
- Xcassets path: \(xcassetsPath)
|
||||||
- Extension output path: \(extensionOutputPath)
|
- Extension output path: \(extensionOutputPath)
|
||||||
- Extension name: \(extensionName ?? "-")
|
- Extension name: \(extensionName ?? "-")
|
||||||
- Extension name SwiftUI: \(extensionNameSwiftUI ?? "-")
|
- Extension name UIKit: \(extensionNameUIKit ?? "-")
|
||||||
- Extension suffix: \(extensionSuffix ?? "-")
|
- Extension suffix: \(extensionSuffix ?? "-")
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
@ -129,7 +174,7 @@ struct FontsConfiguration: Codable, CustomDebugStringConvertible {
|
|||||||
let inputFile: String
|
let inputFile: String
|
||||||
let extensionOutputPath: String
|
let extensionOutputPath: String
|
||||||
let extensionName: String?
|
let extensionName: String?
|
||||||
let extensionNameSwiftUI: String?
|
let extensionNameUIKit: String?
|
||||||
let extensionSuffix: String?
|
let extensionSuffix: String?
|
||||||
let infoPlistPaths: String?
|
let infoPlistPaths: String?
|
||||||
private let staticMembers: Bool?
|
private let staticMembers: Bool?
|
||||||
@ -144,14 +189,14 @@ struct FontsConfiguration: Codable, CustomDebugStringConvertible {
|
|||||||
internal init(inputFile: String,
|
internal init(inputFile: String,
|
||||||
extensionOutputPath: String,
|
extensionOutputPath: String,
|
||||||
extensionName: String?,
|
extensionName: String?,
|
||||||
extensionNameSwiftUI: String?,
|
extensionNameUIKit: String?,
|
||||||
extensionSuffix: String?,
|
extensionSuffix: String?,
|
||||||
infoPlistPaths: String?,
|
infoPlistPaths: String?,
|
||||||
staticMembers: Bool?) {
|
staticMembers: Bool?) {
|
||||||
self.inputFile = inputFile
|
self.inputFile = inputFile
|
||||||
self.extensionOutputPath = extensionOutputPath
|
self.extensionOutputPath = extensionOutputPath
|
||||||
self.extensionName = extensionName
|
self.extensionName = extensionName
|
||||||
self.extensionNameSwiftUI = extensionNameSwiftUI
|
self.extensionNameUIKit = extensionNameUIKit
|
||||||
self.extensionSuffix = extensionSuffix
|
self.extensionSuffix = extensionSuffix
|
||||||
self.infoPlistPaths = infoPlistPaths
|
self.infoPlistPaths = infoPlistPaths
|
||||||
self.staticMembers = staticMembers
|
self.staticMembers = staticMembers
|
||||||
@ -163,7 +208,7 @@ struct FontsConfiguration: Codable, CustomDebugStringConvertible {
|
|||||||
- Input file: \(inputFile)
|
- Input file: \(inputFile)
|
||||||
- Extension output path: \(extensionOutputPath)
|
- Extension output path: \(extensionOutputPath)
|
||||||
- Extension name: \(extensionName ?? "-")
|
- Extension name: \(extensionName ?? "-")
|
||||||
- Extension name SwiftUI: \(extensionNameSwiftUI ?? "-")
|
- Extension name UIKit: \(extensionNameUIKit ?? "-")
|
||||||
- Extension suffix: \(extensionSuffix ?? "-")
|
- Extension suffix: \(extensionSuffix ?? "-")
|
||||||
- InfoPlistPaths: \(infoPlistPaths ?? "-")
|
- InfoPlistPaths: \(infoPlistPaths ?? "-")
|
||||||
"""
|
"""
|
||||||
@ -175,7 +220,7 @@ struct ImagesConfiguration: Codable, CustomDebugStringConvertible {
|
|||||||
let xcassetsPath: String
|
let xcassetsPath: String
|
||||||
let extensionOutputPath: String
|
let extensionOutputPath: String
|
||||||
let extensionName: String?
|
let extensionName: String?
|
||||||
let extensionNameSwiftUI: String?
|
let extensionNameUIKit: String?
|
||||||
let extensionSuffix: String?
|
let extensionSuffix: String?
|
||||||
private let staticMembers: Bool?
|
private let staticMembers: Bool?
|
||||||
|
|
||||||
@ -190,14 +235,14 @@ struct ImagesConfiguration: Codable, CustomDebugStringConvertible {
|
|||||||
xcassetsPath: String,
|
xcassetsPath: String,
|
||||||
extensionOutputPath: String,
|
extensionOutputPath: String,
|
||||||
extensionName: String?,
|
extensionName: String?,
|
||||||
extensionNameSwiftUI: String?,
|
extensionNameUIKit: String?,
|
||||||
extensionSuffix: String?,
|
extensionSuffix: String?,
|
||||||
staticMembers: Bool?) {
|
staticMembers: Bool?) {
|
||||||
self.inputFile = inputFile
|
self.inputFile = inputFile
|
||||||
self.xcassetsPath = xcassetsPath
|
self.xcassetsPath = xcassetsPath
|
||||||
self.extensionOutputPath = extensionOutputPath
|
self.extensionOutputPath = extensionOutputPath
|
||||||
self.extensionName = extensionName
|
self.extensionName = extensionName
|
||||||
self.extensionNameSwiftUI = extensionNameSwiftUI
|
self.extensionNameUIKit = extensionNameUIKit
|
||||||
self.extensionSuffix = extensionSuffix
|
self.extensionSuffix = extensionSuffix
|
||||||
self.staticMembers = staticMembers
|
self.staticMembers = staticMembers
|
||||||
}
|
}
|
||||||
@ -209,7 +254,7 @@ struct ImagesConfiguration: Codable, CustomDebugStringConvertible {
|
|||||||
- Xcassets path: \(xcassetsPath)
|
- Xcassets path: \(xcassetsPath)
|
||||||
- Extension output path: \(extensionOutputPath)
|
- Extension output path: \(extensionOutputPath)
|
||||||
- Extension name: \(extensionName ?? "-")
|
- Extension name: \(extensionName ?? "-")
|
||||||
- Extension name SwiftUI: \(extensionNameSwiftUI ?? "-")
|
- Extension name UIKit: \(extensionNameUIKit ?? "-")
|
||||||
- Extension suffix: \(extensionSuffix ?? "-")
|
- Extension suffix: \(extensionSuffix ?? "-")
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
@ -224,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,
|
||||||
@ -239,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
|
||||||
@ -248,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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,43 @@
|
|||||||
|
//
|
||||||
|
// AnalyticsConfiguration+Runnable.swift
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Created by Loris Perret on 08/12/2023.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
extension AnalyticsConfiguration: Runnable {
|
||||||
|
func run(projectDirectory: String, force: Bool) {
|
||||||
|
var args = [String]()
|
||||||
|
|
||||||
|
if force {
|
||||||
|
args += ["-f"]
|
||||||
|
}
|
||||||
|
|
||||||
|
args += [
|
||||||
|
inputFile.prependIfRelativePath(projectDirectory),
|
||||||
|
"--target",
|
||||||
|
target,
|
||||||
|
"--extension-output-path",
|
||||||
|
extensionOutputPath.prependIfRelativePath(projectDirectory),
|
||||||
|
"--static-members",
|
||||||
|
"\(staticMembersOptions)"
|
||||||
|
]
|
||||||
|
|
||||||
|
if let extensionName = extensionName {
|
||||||
|
args += [
|
||||||
|
"--extension-name",
|
||||||
|
extensionName
|
||||||
|
]
|
||||||
|
}
|
||||||
|
if let extensionSuffix = extensionSuffix {
|
||||||
|
args += [
|
||||||
|
"--extension-suffix",
|
||||||
|
extensionSuffix
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
Analytics.main(args)
|
||||||
|
}
|
||||||
|
}
|
@ -38,10 +38,10 @@ extension ColorsConfiguration: Runnable {
|
|||||||
extensionName
|
extensionName
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
if let extensionNameSwiftUI = extensionNameSwiftUI {
|
if let extensionNameUIKit = extensionNameUIKit {
|
||||||
args += [
|
args += [
|
||||||
"--extension-name-swift-ui",
|
"--extension-name-ui-kit",
|
||||||
extensionNameSwiftUI
|
extensionNameUIKit
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
if let extensionSuffix = extensionSuffix {
|
if let extensionSuffix = extensionSuffix {
|
||||||
|
@ -34,10 +34,10 @@ extension FontsConfiguration: Runnable {
|
|||||||
extensionName
|
extensionName
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
if let extensionNameSwiftUI = extensionNameSwiftUI {
|
if let extensionNameUIKit = extensionNameUIKit {
|
||||||
args += [
|
args += [
|
||||||
"--extension-name-swift-ui",
|
"--extension-name-ui-kit",
|
||||||
extensionNameSwiftUI
|
extensionNameUIKit
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,10 +36,10 @@ extension ImagesConfiguration: Runnable {
|
|||||||
extensionName
|
extensionName
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
if let extensionNameSwiftUI = extensionNameSwiftUI {
|
if let extensionNameUIKit = extensionNameUIKit {
|
||||||
args += [
|
args += [
|
||||||
"--extension-name-swift-ui",
|
"--extension-name-ui-kit",
|
||||||
extensionNameSwiftUI
|
extensionNameUIKit
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
if let extensionSuffix = extensionSuffix {
|
if let extensionSuffix = extensionSuffix {
|
||||||
|
@ -10,4 +10,3 @@ import Foundation
|
|||||||
protocol Runnable {
|
protocol Runnable {
|
||||||
func run(projectDirectory: String, force: Bool)
|
func run(projectDirectory: String, force: Bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
@ -18,7 +18,7 @@ extension FileManager {
|
|||||||
|
|
||||||
for case let fileURL as URL in enumerator {
|
for case let fileURL as URL in enumerator {
|
||||||
do {
|
do {
|
||||||
let fileAttributes = try fileURL.resourceValues(forKeys:[.isRegularFileKey])
|
let fileAttributes = try fileURL.resourceValues(forKeys: [.isRegularFileKey])
|
||||||
if fileAttributes.isRegularFile! {
|
if fileAttributes.isRegularFile! {
|
||||||
files.append(fileURL.relativePath)
|
files.append(fileURL.relativePath)
|
||||||
}
|
}
|
||||||
@ -41,7 +41,7 @@ extension FileManager {
|
|||||||
|
|
||||||
for case let fileURL as URL in enumerator {
|
for case let fileURL as URL in enumerator {
|
||||||
do {
|
do {
|
||||||
let fileAttributes = try fileURL.resourceValues(forKeys:[.isDirectoryKey])
|
let fileAttributes = try fileURL.resourceValues(forKeys: [.isDirectoryKey])
|
||||||
if fileAttributes.isDirectory! && fileURL.lastPathComponent.hasSuffix(".imageset") {
|
if fileAttributes.isDirectory! && fileURL.lastPathComponent.hasSuffix(".imageset") {
|
||||||
files.append(fileURL.lastPathComponent)
|
files.append(fileURL.lastPathComponent)
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@ class ImageExtensionGenerator {
|
|||||||
let extensionFilePathURL = URL(fileURLWithPath: extensionFilePath)
|
let extensionFilePathURL = URL(fileURLWithPath: extensionFilePath)
|
||||||
do {
|
do {
|
||||||
try extensionContent.write(to: extensionFilePathURL, atomically: false, encoding: .utf8)
|
try extensionContent.write(to: extensionFilePathURL, atomically: false, encoding: .utf8)
|
||||||
} catch (let error) {
|
} catch let error {
|
||||||
let error = ImagesError.writeFile(extensionFilePath, error.localizedDescription)
|
let error = ImagesError.writeFile(extensionFilePath, error.localizedDescription)
|
||||||
print(error.description)
|
print(error.description)
|
||||||
Images.exit(withError: error)
|
Images.exit(withError: error)
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,8 +21,8 @@ struct Images: ParsableCommand {
|
|||||||
// MARK: - Static
|
// MARK: - Static
|
||||||
|
|
||||||
static let toolName = "Images"
|
static let toolName = "Images"
|
||||||
static let defaultExtensionName = "UIImage"
|
static let defaultExtensionName = "Image"
|
||||||
static let defaultExtensionNameSUI = "Image"
|
static let defaultExtensionNameUIKit = "UIImage"
|
||||||
|
|
||||||
// MARK: - Command Options
|
// MARK: - Command Options
|
||||||
|
|
||||||
@ -58,14 +58,14 @@ struct Images: ParsableCommand {
|
|||||||
inputFilename: options.inputFilenameWithoutExt,
|
inputFilename: options.inputFilenameWithoutExt,
|
||||||
extensionName: options.extensionName,
|
extensionName: options.extensionName,
|
||||||
extensionFilePath: options.extensionFilePath,
|
extensionFilePath: options.extensionFilePath,
|
||||||
isSwiftUI: false)
|
isSwiftUI: true)
|
||||||
|
|
||||||
ImageExtensionGenerator.generateExtensionFile(images: imagesToGenerate,
|
ImageExtensionGenerator.generateExtensionFile(images: imagesToGenerate,
|
||||||
staticVar: options.staticMembers,
|
staticVar: options.staticMembers,
|
||||||
inputFilename: options.inputFilenameWithoutExt,
|
inputFilename: options.inputFilenameWithoutExt,
|
||||||
extensionName: options.extensionNameSwiftUI,
|
extensionName: options.extensionNameUIKit,
|
||||||
extensionFilePath: options.extensionFilePathSwiftUI,
|
extensionFilePath: options.extensionFilePathUIKit,
|
||||||
isSwiftUI: true)
|
isSwiftUI: false)
|
||||||
|
|
||||||
print("[\(Self.toolName)] Images generated")
|
print("[\(Self.toolName)] Images generated")
|
||||||
}
|
}
|
||||||
@ -90,7 +90,7 @@ struct Images: ParsableCommand {
|
|||||||
_ = Images.getSvgConverterPath()
|
_ = Images.getSvgConverterPath()
|
||||||
|
|
||||||
// Extension for UIKit and SwiftUI should have different name
|
// Extension for UIKit and SwiftUI should have different name
|
||||||
guard options.extensionName != options.extensionNameSwiftUI else {
|
guard options.extensionName != options.extensionNameUIKit else {
|
||||||
let error = ImagesError.extensionNamesCollision(options.extensionName)
|
let error = ImagesError.extensionNamesCollision(options.extensionName)
|
||||||
print(error.description)
|
print(error.description)
|
||||||
Images.exit(withError: error)
|
Images.exit(withError: error)
|
||||||
|
@ -36,7 +36,7 @@ enum ImagesError: Error {
|
|||||||
return "error: [\(Images.toolName)] Getting file attributes of \(filename) failed with error: \(errorDescription)"
|
return "error: [\(Images.toolName)] Getting file attributes of \(filename) failed with error: \(errorDescription)"
|
||||||
|
|
||||||
case .rsvgConvertNotFound:
|
case .rsvgConvertNotFound:
|
||||||
return "error: [\(Images.toolName)] Can't find rsvg-convert (can be installed with 'brew remove imagemagick && brew install imagemagick --with-librsvg')"
|
return "error: [\(Images.toolName)] Can't find rsvg-convert (can be installed with 'brew remove imagemagick && brew install librsvg')"
|
||||||
|
|
||||||
case .writeFile(let subErrorDescription, let filename):
|
case .writeFile(let subErrorDescription, let filename):
|
||||||
return "error: [\(Images.toolName)] An error occured while writing content to \(filename): \(subErrorDescription)"
|
return "error: [\(Images.toolName)] An error occured while writing content to \(filename): \(subErrorDescription)"
|
||||||
|
@ -27,11 +27,11 @@ struct ImagesOptions: 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: "Extension name. If not specified, it will generate an UIImage extension.")
|
@Option(help: "Extension name. If not specified, it will generate an Image extension.")
|
||||||
var extensionName: String = Images.defaultExtensionName
|
var extensionName: String = Images.defaultExtensionName
|
||||||
|
|
||||||
@Option(help: "Extension name. If not specified, it will generate an Image extension.")
|
@Option(help: "Extension name. If not specified, it will generate an UIImage extension.")
|
||||||
var extensionNameSwiftUI: String = Images.defaultExtensionNameSUI
|
var extensionNameUIKit: String = Images.defaultExtensionNameUIKit
|
||||||
|
|
||||||
@Option(help: "Extension suffix. Ex: MyApp, it will generate {extensionName}+Image{extensionSuffix}.swift")
|
@Option(help: "Extension suffix. Ex: MyApp, it will generate {extensionName}+Image{extensionSuffix}.swift")
|
||||||
var extensionSuffix: String?
|
var extensionSuffix: String?
|
||||||
@ -41,7 +41,7 @@ struct ImagesOptions: ParsableArguments {
|
|||||||
|
|
||||||
extension ImagesOptions {
|
extension ImagesOptions {
|
||||||
|
|
||||||
// MARK: - UIKit
|
// MARK: - SwiftUI
|
||||||
|
|
||||||
var extensionFileName: String {
|
var extensionFileName: String {
|
||||||
if let extensionSuffix = extensionSuffix {
|
if let extensionSuffix = extensionSuffix {
|
||||||
@ -54,17 +54,17 @@ extension ImagesOptions {
|
|||||||
"\(extensionOutputPath)/\(extensionFileName)"
|
"\(extensionOutputPath)/\(extensionFileName)"
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - SwiftUI
|
// MARK: - UIKit
|
||||||
|
|
||||||
var extensionFileNameSwiftUI: String {
|
var extensionFileNameUIKit: String {
|
||||||
if let extensionSuffix = extensionSuffix {
|
if let extensionSuffix = extensionSuffix {
|
||||||
return "\(extensionNameSwiftUI)+\(extensionSuffix).swift"
|
return "\(extensionNameUIKit)+\(extensionSuffix).swift"
|
||||||
}
|
}
|
||||||
return "\(extensionNameSwiftUI).swift"
|
return "\(extensionNameUIKit).swift"
|
||||||
}
|
}
|
||||||
|
|
||||||
var extensionFilePathSwiftUI: String {
|
var extensionFilePathUIKit: String {
|
||||||
"\(extensionOutputPath)/\(extensionFileNameSwiftUI)"
|
"\(extensionOutputPath)/\(extensionFileNameUIKit)"
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: -
|
// MARK: -
|
||||||
|
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(decoding: data, as: UTF8.self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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,23 +26,48 @@ 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 {
|
||||||
try fileContent.write(to: stringsFilePathURL, atomically: false, encoding: .utf8)
|
try fileContent.write(to: stringsFilePathURL, atomically: false, encoding: .utf8)
|
||||||
} catch (let error) {
|
} catch let error {
|
||||||
let error = StringiumError.writeFile(error.localizedDescription, stringsFilePath)
|
let error = StringiumError.writeFile(error.localizedDescription, stringsFilePath)
|
||||||
print(error.description)
|
print(error.description)
|
||||||
Stringium.exit(withError: error)
|
Stringium.exit(withError: error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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,121 @@ 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)
|
||||||
|
|
||||||
|
return String(decoding: json, as: UTF8.self)
|
||||||
|
|
||||||
|
} 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,20 +245,20 @@ 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 {
|
||||||
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 = StringiumError.writeFile(extensionFilePath, error.localizedDescription)
|
||||||
print(error.description)
|
print(error.description)
|
||||||
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 +274,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 +307,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 +317,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 +329,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 +356,11 @@ class StringsFileGenerator {
|
|||||||
}
|
}
|
||||||
.joined(separator: "\n")
|
.joined(separator: "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func getFooter() -> String {
|
private static func getFooter() -> String {
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ class TagsGenerator {
|
|||||||
let extensionFilePathURL = URL(fileURLWithPath: extensionFilePath)
|
let extensionFilePathURL = URL(fileURLWithPath: extensionFilePath)
|
||||||
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 = StringiumError.writeFile(extensionFilePath, error.localizedDescription)
|
||||||
print(error.description)
|
print(error.description)
|
||||||
Stringium.exit(withError: error)
|
Stringium.exit(withError: error)
|
||||||
|
@ -37,7 +37,7 @@ class Definition {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func hasOneOrMoreMatchingTags(inputTags: [String]) -> Bool {
|
func hasOneOrMoreMatchingTags(inputTags: [String]) -> Bool {
|
||||||
if Set(inputTags).intersection(Set(self.tags)).isEmpty {
|
if Set(inputTags).isDisjoint(with: tags) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
@ -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
|
||||||
|
|
||||||
|
@ -27,4 +27,3 @@ struct Strings: ParsableCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Strings.main()
|
//Strings.main()
|
||||||
|
|
||||||
|
@ -18,7 +18,6 @@ struct Tags: ParsableCommand {
|
|||||||
version: ResgenSwiftVersion
|
version: ResgenSwiftVersion
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
// MARK: - Static
|
// MARK: - Static
|
||||||
|
|
||||||
static let toolName = "Tags"
|
static let toolName = "Tags"
|
||||||
|
@ -19,10 +19,12 @@ struct ResgenSwift: ParsableCommand {
|
|||||||
// With language support for type-level introspection, this could be
|
// With language support for type-level introspection, this could be
|
||||||
// provided by automatically finding nested `ParsableCommand` types.
|
// provided by automatically finding nested `ParsableCommand` types.
|
||||||
subcommands: [
|
subcommands: [
|
||||||
|
Analytics.self,
|
||||||
Colors.self,
|
Colors.self,
|
||||||
Fonts.self,
|
Fonts.self,
|
||||||
Images.self,
|
Images.self,
|
||||||
Strings.self,
|
Strings.self,
|
||||||
|
Tags.self,
|
||||||
Generate.self
|
Generate.self
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
137
Tests/ResgenSwiftTests/Analytics/AnalyticsDefinitionTests.swift
Normal file
137
Tests/ResgenSwiftTests/Analytics/AnalyticsDefinitionTests.swift
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
//
|
||||||
|
// AnalyticsDefinitionTests.swift
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Created by Loris Perret on 06/12/2023.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import XCTest
|
||||||
|
|
||||||
|
@testable import ResgenSwift
|
||||||
|
|
||||||
|
final class AnalyticsDefinitionTests: XCTestCase {
|
||||||
|
|
||||||
|
// MARK: - Matching tags
|
||||||
|
|
||||||
|
func testMatchingAnalyticss() {
|
||||||
|
// Given
|
||||||
|
let definition = AnalyticsDefinition(id: "definition_name", name: "", type: .screen)
|
||||||
|
definition.tags = ["ios","iosonly","notranslation"]
|
||||||
|
|
||||||
|
// When
|
||||||
|
let match1 = definition.hasOneOrMoreMatchingTags(inputTags: ["ios"])
|
||||||
|
let match2 = definition.hasOneOrMoreMatchingTags(inputTags: ["iosonly"])
|
||||||
|
let match3 = definition.hasOneOrMoreMatchingTags(inputTags: ["notranslation"])
|
||||||
|
|
||||||
|
|
||||||
|
// Expect
|
||||||
|
XCTAssertTrue(match1)
|
||||||
|
XCTAssertTrue(match2)
|
||||||
|
XCTAssertTrue(match3)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testNotMatchingAnalyticss() {
|
||||||
|
// Given
|
||||||
|
let definition = AnalyticsDefinition(id: "definition_name", name: "", type: .screen)
|
||||||
|
definition.tags = ["ios","iosonly","notranslation"]
|
||||||
|
|
||||||
|
// When
|
||||||
|
let match1 = definition.hasOneOrMoreMatchingTags(inputTags: ["droid"])
|
||||||
|
let match2 = definition.hasOneOrMoreMatchingTags(inputTags: ["droidonly"])
|
||||||
|
let match3 = definition.hasOneOrMoreMatchingTags(inputTags: ["azerty"])
|
||||||
|
|
||||||
|
// Expect
|
||||||
|
XCTAssertFalse(match1)
|
||||||
|
XCTAssertFalse(match2)
|
||||||
|
XCTAssertFalse(match3)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Raw properties
|
||||||
|
|
||||||
|
func testGeneratedRawPropertyScreen() {
|
||||||
|
// Given
|
||||||
|
let definition = AnalyticsDefinition(id: "definition_name", name: "Ecran un", type: .screen)
|
||||||
|
definition.path = "ecran_un/"
|
||||||
|
|
||||||
|
// When
|
||||||
|
let propertyScreen = definition.getProperty()
|
||||||
|
|
||||||
|
// Expect
|
||||||
|
let expectScreen = """
|
||||||
|
func logScreenDefinitionName() {
|
||||||
|
logScreen(
|
||||||
|
name: "Ecran un",
|
||||||
|
path: "ecran_un/"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
XCTAssertEqual(propertyScreen.adaptForXCTest(), expectScreen.adaptForXCTest())
|
||||||
|
}
|
||||||
|
|
||||||
|
func testGeneratedRawPropertyEvent() {
|
||||||
|
// Given
|
||||||
|
let definition = AnalyticsDefinition(id: "definition_name", name: "Ecran un", type: .event)
|
||||||
|
|
||||||
|
// When
|
||||||
|
let propertyEvent = definition.getProperty()
|
||||||
|
|
||||||
|
// Expect
|
||||||
|
let expectEvent = """
|
||||||
|
func logEventDefinitionName() {
|
||||||
|
logEvent(
|
||||||
|
name: "Ecran un",
|
||||||
|
action: "",
|
||||||
|
category: "",
|
||||||
|
params: []
|
||||||
|
)
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
XCTAssertEqual(propertyEvent.adaptForXCTest(), expectEvent.adaptForXCTest())
|
||||||
|
}
|
||||||
|
|
||||||
|
func testGeneratedRawStaticPropertyScreen() {
|
||||||
|
// Given
|
||||||
|
let definition = AnalyticsDefinition(id: "definition_name", name: "Ecran un", type: .screen)
|
||||||
|
definition.path = "ecran_un/"
|
||||||
|
|
||||||
|
// When
|
||||||
|
let propertyScreen = definition.getStaticProperty()
|
||||||
|
|
||||||
|
// Expect
|
||||||
|
let expectScreen = """
|
||||||
|
static func logScreenDefinitionName() {
|
||||||
|
logScreen(
|
||||||
|
name: "Ecran un",
|
||||||
|
path: "ecran_un/"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
XCTAssertEqual(propertyScreen.adaptForXCTest(), expectScreen.adaptForXCTest())
|
||||||
|
}
|
||||||
|
|
||||||
|
func testGeneratedRawStaticPropertyEvent() {
|
||||||
|
// Given
|
||||||
|
let definition = AnalyticsDefinition(id: "definition_name", name: "Ecran un", type: .event)
|
||||||
|
|
||||||
|
// When
|
||||||
|
let propertyEvent = definition.getStaticProperty()
|
||||||
|
|
||||||
|
// Expect
|
||||||
|
let expectEvent = """
|
||||||
|
static func logEventDefinitionName() {
|
||||||
|
logEvent(
|
||||||
|
name: "Ecran un",
|
||||||
|
action: "",
|
||||||
|
category: "",
|
||||||
|
params: []
|
||||||
|
)
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
XCTAssertEqual(propertyEvent.adaptForXCTest(), expectEvent.adaptForXCTest())
|
||||||
|
}
|
||||||
|
}
|
623
Tests/ResgenSwiftTests/Analytics/AnalyticsGeneratorTests.swift
Normal file
623
Tests/ResgenSwiftTests/Analytics/AnalyticsGeneratorTests.swift
Normal file
@ -0,0 +1,623 @@
|
|||||||
|
//
|
||||||
|
// AnalyticsGeneratorTests.swift
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Created by Thibaut Schmitt on 06/09/2022.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import XCTest
|
||||||
|
import ToolCore
|
||||||
|
|
||||||
|
@testable import ResgenSwift
|
||||||
|
|
||||||
|
final class AnalyticsGeneratorTests: XCTestCase {
|
||||||
|
|
||||||
|
private func getAnalyticsDefinition(
|
||||||
|
id: String,
|
||||||
|
path: String = "",
|
||||||
|
action: String = "",
|
||||||
|
category: String = "",
|
||||||
|
name: String,
|
||||||
|
type: AnalyticsDefinition.TagType,
|
||||||
|
tags: [String]
|
||||||
|
) -> AnalyticsDefinition {
|
||||||
|
let definition = AnalyticsDefinition(id: id, name: name, type: type)
|
||||||
|
definition.tags = tags
|
||||||
|
definition.path = path
|
||||||
|
definition.action = action
|
||||||
|
definition.category = category
|
||||||
|
return definition
|
||||||
|
}
|
||||||
|
|
||||||
|
func testGeneratedExtensionContentFirebase() {
|
||||||
|
// Given
|
||||||
|
let sectionOne = AnalyticsCategory(id: "section_one")
|
||||||
|
sectionOne.definitions = [
|
||||||
|
getAnalyticsDefinition(id: "s1_def_one", name: "s1 def one", type: .screen, tags: ["ios", "iosonly"]),
|
||||||
|
getAnalyticsDefinition(id: "s1_def_two", name: "s1 def two", type: .event, tags: ["ios", "iosonly"]),
|
||||||
|
]
|
||||||
|
|
||||||
|
let sectionTwo = AnalyticsCategory(id: "section_two")
|
||||||
|
sectionTwo.definitions = [
|
||||||
|
getAnalyticsDefinition(id: "s2_def_one", name: "s2 def one", type: .screen, tags: ["ios","iosonly"]),
|
||||||
|
getAnalyticsDefinition(id: "s2_def_two", name: "s2 def two", type: .event, tags: ["droid","droidonly"]),
|
||||||
|
]
|
||||||
|
|
||||||
|
let sectionThree = AnalyticsCategory(id: "section_three")
|
||||||
|
sectionThree.definitions = [
|
||||||
|
getAnalyticsDefinition(id: "s3_def_one", name: "s3 def one", type: .screen, tags: ["droid","droidonly"]),
|
||||||
|
getAnalyticsDefinition(id: "s3_def_two", name: "s3 def two", type: .event, tags: ["droid","droidonly"]),
|
||||||
|
]
|
||||||
|
|
||||||
|
// When
|
||||||
|
AnalyticsGenerator.targets = [TrackerType.firebase]
|
||||||
|
let extensionContent = AnalyticsGenerator.getExtensionContent(sections: [sectionOne, sectionTwo, sectionThree],
|
||||||
|
tags: ["ios", "iosonly"],
|
||||||
|
staticVar: false,
|
||||||
|
extensionName: "GenAnalytics")
|
||||||
|
// Expect Analytics
|
||||||
|
let expect = """
|
||||||
|
// Generated by ResgenSwift.Analytics 1.2
|
||||||
|
|
||||||
|
import Firebase
|
||||||
|
|
||||||
|
// MARK: - Protocol
|
||||||
|
|
||||||
|
protocol AnalyticsManagerProtocol {
|
||||||
|
func logScreen(name: String, path: String)
|
||||||
|
func logEvent(
|
||||||
|
name: String,
|
||||||
|
action: String,
|
||||||
|
category: String,
|
||||||
|
params: [String: Any]?
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Firebase
|
||||||
|
|
||||||
|
class FirebaseAnalyticsManager: AnalyticsManagerProtocol {
|
||||||
|
func logScreen(name: String, path: String) {
|
||||||
|
var parameters = [
|
||||||
|
AnalyticsParameterScreenName: name
|
||||||
|
]
|
||||||
|
|
||||||
|
Analytics.logEvent(
|
||||||
|
AnalyticsEventScreenView,
|
||||||
|
parameters: parameters
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func logEvent(
|
||||||
|
name: String,
|
||||||
|
action: String,
|
||||||
|
category: String,
|
||||||
|
params: [String: Any]?
|
||||||
|
) {
|
||||||
|
var parameters: [String:Any] = [
|
||||||
|
"action": action,
|
||||||
|
"category": category,
|
||||||
|
]
|
||||||
|
|
||||||
|
if let supplementaryParameters = params {
|
||||||
|
parameters.merge(supplementaryParameters) { (origin, new) -> Any in
|
||||||
|
return origin
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Analytics.logEvent(
|
||||||
|
name,
|
||||||
|
parameters: parameters
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Manager
|
||||||
|
|
||||||
|
class AnalyticsManager {
|
||||||
|
static var shared = AnalyticsManager()
|
||||||
|
|
||||||
|
// MARK: - Properties
|
||||||
|
|
||||||
|
var managers: [AnalyticsManagerProtocol] = []
|
||||||
|
|
||||||
|
private var isEnabled: Bool = true
|
||||||
|
|
||||||
|
// MARK: - Methods
|
||||||
|
|
||||||
|
func setAnalyticsEnabled(_ enable: Bool) {
|
||||||
|
isEnabled = enable
|
||||||
|
}
|
||||||
|
|
||||||
|
func configure() {
|
||||||
|
managers.append(FirebaseAnalyticsManager())
|
||||||
|
}
|
||||||
|
|
||||||
|
private func logScreen(name: String, path: String) {
|
||||||
|
guard isEnabled else { return }
|
||||||
|
|
||||||
|
managers.forEach { manager in
|
||||||
|
manager.logScreen(name: name, path: path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func logEvent(
|
||||||
|
name: String,
|
||||||
|
action: String,
|
||||||
|
category: String,
|
||||||
|
params: [String: Any]?
|
||||||
|
) {
|
||||||
|
guard isEnabled else { return }
|
||||||
|
|
||||||
|
managers.forEach { manager in
|
||||||
|
manager.logEvent(
|
||||||
|
name: name,
|
||||||
|
action: action,
|
||||||
|
category: category,
|
||||||
|
params: params
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - section_one
|
||||||
|
|
||||||
|
func logScreenS1DefOne() {
|
||||||
|
logScreen(
|
||||||
|
name: "s1 def one",
|
||||||
|
path: ""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func logEventS1DefTwo() {
|
||||||
|
logEvent(
|
||||||
|
name: "s1 def two",
|
||||||
|
action: "",
|
||||||
|
category: "",
|
||||||
|
params: []
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - section_two
|
||||||
|
|
||||||
|
func logScreenS2DefOne() {
|
||||||
|
logScreen(
|
||||||
|
name: "s2 def one",
|
||||||
|
path: ""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
if extensionContent != expect {
|
||||||
|
print(prettyFirstDifferenceBetweenStrings(s1: extensionContent, s2: expect))
|
||||||
|
}
|
||||||
|
XCTAssertEqual(extensionContent.adaptForXCTest(), expect.adaptForXCTest())
|
||||||
|
}
|
||||||
|
|
||||||
|
func testGeneratedExtensionContentMatomo() {
|
||||||
|
// Given
|
||||||
|
let sectionOne = AnalyticsCategory(id: "section_one")
|
||||||
|
sectionOne.definitions = [
|
||||||
|
getAnalyticsDefinition(id: "s1_def_one", path: "s1_def_one/", name: "s1 def one", type: .screen, tags: ["ios", "iosonly"]),
|
||||||
|
getAnalyticsDefinition(id: "s1_def_two", action: "test", category: "test", name: "s1 def two", type: .event, tags: ["ios", "iosonly"]),
|
||||||
|
]
|
||||||
|
|
||||||
|
let sectionTwo = AnalyticsCategory(id: "section_two")
|
||||||
|
sectionTwo.definitions = [
|
||||||
|
getAnalyticsDefinition(id: "s2_def_one", path: "s2_def_one/", name: "s2 def one", type: .screen, tags: ["ios","iosonly"]),
|
||||||
|
getAnalyticsDefinition(id: "s2_def_two", action: "test", category: "test", name: "s2 def two", type: .event, tags: ["droid","droidonly"]),
|
||||||
|
]
|
||||||
|
|
||||||
|
let sectionThree = AnalyticsCategory(id: "section_three")
|
||||||
|
sectionThree.definitions = [
|
||||||
|
getAnalyticsDefinition(id: "s3_def_one", path: "s3_def_one/", name: "s3 def one", type: .screen, tags: ["droid","droidonly"]),
|
||||||
|
getAnalyticsDefinition(id: "s3_def_two", action: "test", category: "test", name: "s3 def two", type: .event, tags: ["droid","droidonly"]),
|
||||||
|
]
|
||||||
|
|
||||||
|
// When
|
||||||
|
AnalyticsGenerator.targets = [TrackerType.matomo]
|
||||||
|
let extensionContent = AnalyticsGenerator.getExtensionContent(sections: [sectionOne, sectionTwo, sectionThree],
|
||||||
|
tags: ["ios", "iosonly"],
|
||||||
|
staticVar: false,
|
||||||
|
extensionName: "GenAnalytics")
|
||||||
|
// Expect Analytics
|
||||||
|
let expect = """
|
||||||
|
// Generated by ResgenSwift.Analytics 1.2
|
||||||
|
|
||||||
|
import MatomoTracker
|
||||||
|
|
||||||
|
// MARK: - Protocol
|
||||||
|
|
||||||
|
protocol AnalyticsManagerProtocol {
|
||||||
|
func logScreen(name: String, path: String)
|
||||||
|
func logEvent(
|
||||||
|
name: String,
|
||||||
|
action: String,
|
||||||
|
category: String,
|
||||||
|
params: [String: Any]?
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Matomo
|
||||||
|
|
||||||
|
class MatomoAnalyticsManager: AnalyticsManagerProtocol {
|
||||||
|
|
||||||
|
// MARK: - Properties
|
||||||
|
|
||||||
|
private var tracker: MatomoTracker
|
||||||
|
|
||||||
|
// MARK: - Init
|
||||||
|
|
||||||
|
init(siteId: String, url: String) {
|
||||||
|
debugPrint("[Matomo service] Server URL: \\(url)")
|
||||||
|
debugPrint("[Matomo service] Site ID: \\(siteId)")
|
||||||
|
tracker = MatomoTracker(
|
||||||
|
siteId: siteId,
|
||||||
|
baseURL: URL(string: url)!
|
||||||
|
)
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
tracker.dispatchInterval = 5
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
tracker.logger = DefaultLogger(minLevel: .verbose)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
debugPrint("[Matomo service] Configured with content base: \\(tracker.contentBase?.absoluteString ?? "-")")
|
||||||
|
debugPrint("[Matomo service] Opt out: \\(tracker.isOptedOut)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Methods
|
||||||
|
|
||||||
|
func logScreen(name: String, path: String) {
|
||||||
|
guard !tracker.isOptedOut else { return }
|
||||||
|
guard let trackerUrl = tracker.contentBase?.absoluteString else { return }
|
||||||
|
|
||||||
|
let urlString = URL(string: "\\(trackerUrl)" + "/" + "\\(path)" + "iOS")
|
||||||
|
tracker.track(
|
||||||
|
view: [name],
|
||||||
|
url: urlString
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func logEvent(
|
||||||
|
name: String,
|
||||||
|
action: String,
|
||||||
|
category: String,
|
||||||
|
params: [String: Any]?
|
||||||
|
) {
|
||||||
|
guard !tracker.isOptedOut else { return }
|
||||||
|
|
||||||
|
tracker.track(
|
||||||
|
eventWithCategory: category,
|
||||||
|
action: action,
|
||||||
|
name: name,
|
||||||
|
number: nil,
|
||||||
|
url: nil
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Manager
|
||||||
|
|
||||||
|
class AnalyticsManager {
|
||||||
|
static var shared = AnalyticsManager()
|
||||||
|
|
||||||
|
// MARK: - Properties
|
||||||
|
|
||||||
|
var managers: [AnalyticsManagerProtocol] = []
|
||||||
|
|
||||||
|
private var isEnabled: Bool = true
|
||||||
|
|
||||||
|
// MARK: - Methods
|
||||||
|
|
||||||
|
func setAnalyticsEnabled(_ enable: Bool) {
|
||||||
|
isEnabled = enable
|
||||||
|
}
|
||||||
|
|
||||||
|
func configure(siteId: String, url: String) {
|
||||||
|
managers.append(
|
||||||
|
MatomoAnalyticsManager(
|
||||||
|
siteId: siteId,
|
||||||
|
url: url
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func logScreen(name: String, path: String) {
|
||||||
|
guard isEnabled else { return }
|
||||||
|
|
||||||
|
managers.forEach { manager in
|
||||||
|
manager.logScreen(name: name, path: path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func logEvent(
|
||||||
|
name: String,
|
||||||
|
action: String,
|
||||||
|
category: String,
|
||||||
|
params: [String: Any]?
|
||||||
|
) {
|
||||||
|
guard isEnabled else { return }
|
||||||
|
|
||||||
|
managers.forEach { manager in
|
||||||
|
manager.logEvent(
|
||||||
|
name: name,
|
||||||
|
action: action,
|
||||||
|
category: category,
|
||||||
|
params: params
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - section_one
|
||||||
|
|
||||||
|
func logScreenS1DefOne() {
|
||||||
|
logScreen(
|
||||||
|
name: "s1 def one",
|
||||||
|
path: "s1_def_one/"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func logEventS1DefTwo() {
|
||||||
|
logEvent(
|
||||||
|
name: "s1 def two",
|
||||||
|
action: "test",
|
||||||
|
category: "test",
|
||||||
|
params: []
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - section_two
|
||||||
|
|
||||||
|
func logScreenS2DefOne() {
|
||||||
|
logScreen(
|
||||||
|
name: "s2 def one",
|
||||||
|
path: "s2_def_one/"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
if extensionContent != expect {
|
||||||
|
print(prettyFirstDifferenceBetweenStrings(s1: extensionContent, s2: expect))
|
||||||
|
}
|
||||||
|
XCTAssertEqual(extensionContent.adaptForXCTest(), expect.adaptForXCTest())
|
||||||
|
}
|
||||||
|
|
||||||
|
func testGeneratedExtensionContentMatomoAndFirebase() {
|
||||||
|
// Given
|
||||||
|
let sectionOne = AnalyticsCategory(id: "section_one")
|
||||||
|
sectionOne.definitions = [
|
||||||
|
getAnalyticsDefinition(id: "s1_def_one", path: "s1_def_one/", name: "s1 def one", type: .screen, tags: ["ios", "iosonly"]),
|
||||||
|
getAnalyticsDefinition(id: "s1_def_two", action: "test", category: "test", name: "s1 def two", type: .event, tags: ["ios", "iosonly"]),
|
||||||
|
]
|
||||||
|
|
||||||
|
let sectionTwo = AnalyticsCategory(id: "section_two")
|
||||||
|
sectionTwo.definitions = [
|
||||||
|
getAnalyticsDefinition(id: "s2_def_one", path: "s2_def_one/", name: "s2 def one", type: .screen, tags: ["ios","iosonly"]),
|
||||||
|
getAnalyticsDefinition(id: "s2_def_two", action: "test", category: "test", name: "s2 def two", type: .event, tags: ["droid","droidonly"]),
|
||||||
|
]
|
||||||
|
|
||||||
|
let sectionThree = AnalyticsCategory(id: "section_three")
|
||||||
|
sectionThree.definitions = [
|
||||||
|
getAnalyticsDefinition(id: "s3_def_one", path: "s3_def_one/", name: "s3 def one", type: .screen, tags: ["droid","droidonly"]),
|
||||||
|
getAnalyticsDefinition(id: "s3_def_two", action: "test", category: "test", name: "s3 def two", type: .event, tags: ["droid","droidonly"]),
|
||||||
|
]
|
||||||
|
|
||||||
|
// When
|
||||||
|
AnalyticsGenerator.targets = [TrackerType.matomo, TrackerType.firebase]
|
||||||
|
let extensionContent = AnalyticsGenerator.getExtensionContent(sections: [sectionOne, sectionTwo, sectionThree],
|
||||||
|
tags: ["ios", "iosonly"],
|
||||||
|
staticVar: false,
|
||||||
|
extensionName: "GenAnalytics")
|
||||||
|
// Expect Analytics
|
||||||
|
let expect = """
|
||||||
|
// Generated by ResgenSwift.Analytics 1.2
|
||||||
|
|
||||||
|
import MatomoTracker
|
||||||
|
import Firebase
|
||||||
|
|
||||||
|
// MARK: - Protocol
|
||||||
|
|
||||||
|
protocol AnalyticsManagerProtocol {
|
||||||
|
func logScreen(name: String, path: String)
|
||||||
|
func logEvent(
|
||||||
|
name: String,
|
||||||
|
action: String,
|
||||||
|
category: String,
|
||||||
|
params: [String: Any]?
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Matomo
|
||||||
|
|
||||||
|
class MatomoAnalyticsManager: AnalyticsManagerProtocol {
|
||||||
|
|
||||||
|
// MARK: - Properties
|
||||||
|
|
||||||
|
private var tracker: MatomoTracker
|
||||||
|
|
||||||
|
// MARK: - Init
|
||||||
|
|
||||||
|
init(siteId: String, url: String) {
|
||||||
|
debugPrint("[Matomo service] Server URL: \\(url)")
|
||||||
|
debugPrint("[Matomo service] Site ID: \\(siteId)")
|
||||||
|
tracker = MatomoTracker(
|
||||||
|
siteId: siteId,
|
||||||
|
baseURL: URL(string: url)!
|
||||||
|
)
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
tracker.dispatchInterval = 5
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
tracker.logger = DefaultLogger(minLevel: .verbose)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
debugPrint("[Matomo service] Configured with content base: \\(tracker.contentBase?.absoluteString ?? "-")")
|
||||||
|
debugPrint("[Matomo service] Opt out: \\(tracker.isOptedOut)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Methods
|
||||||
|
|
||||||
|
func logScreen(name: String, path: String) {
|
||||||
|
guard !tracker.isOptedOut else { return }
|
||||||
|
guard let trackerUrl = tracker.contentBase?.absoluteString else { return }
|
||||||
|
|
||||||
|
let urlString = URL(string: "\\(trackerUrl)" + "/" + "\\(path)" + "iOS")
|
||||||
|
tracker.track(
|
||||||
|
view: [name],
|
||||||
|
url: urlString
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func logEvent(
|
||||||
|
name: String,
|
||||||
|
action: String,
|
||||||
|
category: String,
|
||||||
|
params: [String: Any]?
|
||||||
|
) {
|
||||||
|
guard !tracker.isOptedOut else { return }
|
||||||
|
|
||||||
|
tracker.track(
|
||||||
|
eventWithCategory: category,
|
||||||
|
action: action,
|
||||||
|
name: name,
|
||||||
|
number: nil,
|
||||||
|
url: nil
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Firebase
|
||||||
|
|
||||||
|
class FirebaseAnalyticsManager: AnalyticsManagerProtocol {
|
||||||
|
func logScreen(name: String, path: String) {
|
||||||
|
var parameters = [
|
||||||
|
AnalyticsParameterScreenName: name
|
||||||
|
]
|
||||||
|
|
||||||
|
Analytics.logEvent(
|
||||||
|
AnalyticsEventScreenView,
|
||||||
|
parameters: parameters
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func logEvent(
|
||||||
|
name: String,
|
||||||
|
action: String,
|
||||||
|
category: String,
|
||||||
|
params: [String: Any]?
|
||||||
|
) {
|
||||||
|
var parameters: [String:Any] = [
|
||||||
|
"action": action,
|
||||||
|
"category": category,
|
||||||
|
]
|
||||||
|
|
||||||
|
if let supplementaryParameters = params {
|
||||||
|
parameters.merge(supplementaryParameters) { (origin, new) -> Any in
|
||||||
|
return origin
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Analytics.logEvent(
|
||||||
|
name,
|
||||||
|
parameters: parameters
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Manager
|
||||||
|
|
||||||
|
class AnalyticsManager {
|
||||||
|
static var shared = AnalyticsManager()
|
||||||
|
|
||||||
|
// MARK: - Properties
|
||||||
|
|
||||||
|
var managers: [AnalyticsManagerProtocol] = []
|
||||||
|
|
||||||
|
private var isEnabled: Bool = true
|
||||||
|
|
||||||
|
// MARK: - Methods
|
||||||
|
|
||||||
|
func setAnalyticsEnabled(_ enable: Bool) {
|
||||||
|
isEnabled = enable
|
||||||
|
}
|
||||||
|
|
||||||
|
func configure(siteId: String, url: String) {
|
||||||
|
managers.append(
|
||||||
|
MatomoAnalyticsManager(
|
||||||
|
siteId: siteId,
|
||||||
|
url: url
|
||||||
|
)
|
||||||
|
)
|
||||||
|
managers.append(FirebaseAnalyticsManager())
|
||||||
|
}
|
||||||
|
|
||||||
|
private func logScreen(name: String, path: String) {
|
||||||
|
guard isEnabled else { return }
|
||||||
|
|
||||||
|
managers.forEach { manager in
|
||||||
|
manager.logScreen(name: name, path: path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func logEvent(
|
||||||
|
name: String,
|
||||||
|
action: String,
|
||||||
|
category: String,
|
||||||
|
params: [String: Any]?
|
||||||
|
) {
|
||||||
|
guard isEnabled else { return }
|
||||||
|
|
||||||
|
managers.forEach { manager in
|
||||||
|
manager.logEvent(
|
||||||
|
name: name,
|
||||||
|
action: action,
|
||||||
|
category: category,
|
||||||
|
params: params
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - section_one
|
||||||
|
|
||||||
|
func logScreenS1DefOne() {
|
||||||
|
logScreen(
|
||||||
|
name: "s1 def one",
|
||||||
|
path: "s1_def_one/"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func logEventS1DefTwo() {
|
||||||
|
logEvent(
|
||||||
|
name: "s1 def two",
|
||||||
|
action: "test",
|
||||||
|
category: "test",
|
||||||
|
params: []
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - section_two
|
||||||
|
|
||||||
|
func logScreenS2DefOne() {
|
||||||
|
logScreen(
|
||||||
|
name: "s2 def one",
|
||||||
|
path: "s2_def_one/"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
if extensionContent != expect {
|
||||||
|
print(prettyFirstDifferenceBetweenStrings(s1: extensionContent, s2: expect))
|
||||||
|
}
|
||||||
|
XCTAssertEqual(extensionContent.adaptForXCTest(), expect.adaptForXCTest())
|
||||||
|
}
|
||||||
|
}
|
72
Tests/ResgenSwiftTests/Analytics/AnalyticsSectionTests.swift
Normal file
72
Tests/ResgenSwiftTests/Analytics/AnalyticsSectionTests.swift
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
//
|
||||||
|
// AnalyticsSectionTests.swift
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Created by Loris Perret on 06/12/2023.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import XCTest
|
||||||
|
|
||||||
|
@testable import ResgenSwift
|
||||||
|
|
||||||
|
final class AnalyticsSectionTests: XCTestCase {
|
||||||
|
|
||||||
|
// MARK: - Matching tags
|
||||||
|
|
||||||
|
func testMatchingAnalytics() {
|
||||||
|
// Given
|
||||||
|
let section = AnalyticsCategory(id: "section_name")
|
||||||
|
section.definitions = [
|
||||||
|
{
|
||||||
|
let def = AnalyticsDefinition(id: "definition_name", name: "", type: .screen)
|
||||||
|
def.tags = ["ios","iosonly"]
|
||||||
|
return def
|
||||||
|
}(),
|
||||||
|
{
|
||||||
|
let def = AnalyticsDefinition(id: "definition_name_two", name: "", type: .screen)
|
||||||
|
def.tags = ["droid","droidonly"]
|
||||||
|
return def
|
||||||
|
}()
|
||||||
|
]
|
||||||
|
|
||||||
|
// When
|
||||||
|
let match1 = section.hasOneOrMoreMatchingTags(tags: ["ios"])
|
||||||
|
let match2 = section.hasOneOrMoreMatchingTags(tags: ["iosonly"])
|
||||||
|
let match3 = section.hasOneOrMoreMatchingTags(tags: ["droid"])
|
||||||
|
let match4 = section.hasOneOrMoreMatchingTags(tags: ["droidonly"])
|
||||||
|
|
||||||
|
// Expect
|
||||||
|
XCTAssertTrue(match1)
|
||||||
|
XCTAssertTrue(match2)
|
||||||
|
XCTAssertTrue(match3)
|
||||||
|
XCTAssertTrue(match4)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testNotMatchingAnalytics() {
|
||||||
|
// Given
|
||||||
|
let section = AnalyticsCategory(id: "section_name")
|
||||||
|
section.definitions = [
|
||||||
|
{
|
||||||
|
let def = AnalyticsDefinition(id: "definition_name", name: "", type: .screen)
|
||||||
|
def.tags = ["ios","iosonly"]
|
||||||
|
return def
|
||||||
|
}(),
|
||||||
|
{
|
||||||
|
let def = AnalyticsDefinition(id: "definition_name_two", name: "", type: .screen)
|
||||||
|
def.tags = ["droid","droidonly"]
|
||||||
|
return def
|
||||||
|
}()
|
||||||
|
]
|
||||||
|
|
||||||
|
// When
|
||||||
|
let match1 = section.hasOneOrMoreMatchingTags(tags: ["web"])
|
||||||
|
let match2 = section.hasOneOrMoreMatchingTags(tags: ["webonly"])
|
||||||
|
let match3 = section.hasOneOrMoreMatchingTags(tags: ["azerty"])
|
||||||
|
|
||||||
|
// Expect
|
||||||
|
XCTAssertFalse(match1)
|
||||||
|
XCTAssertFalse(match2)
|
||||||
|
XCTAssertFalse(match3)
|
||||||
|
}
|
||||||
|
}
|
134
Tests/ResgenSwiftTests/Analytics/DiffString.swift
Normal file
134
Tests/ResgenSwiftTests/Analytics/DiffString.swift
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
//
|
||||||
|
// DiffString.swift
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Created by Loris Perret on 06/12/2023.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
/// Find first differing character between two strings
|
||||||
|
///
|
||||||
|
/// :param: s1 First String
|
||||||
|
/// :param: s2 Second String
|
||||||
|
///
|
||||||
|
/// :returns: .DifferenceAtIndex(i) or .NoDifference
|
||||||
|
public func firstDifferenceBetweenStrings(s1: NSString, s2: NSString) -> FirstDifferenceResult {
|
||||||
|
let len1 = s1.length
|
||||||
|
let len2 = s2.length
|
||||||
|
|
||||||
|
let lenMin = min(len1, len2)
|
||||||
|
|
||||||
|
for i in 0..<lenMin {
|
||||||
|
if s1.character(at: i) != s2.character(at: i) {
|
||||||
|
return .DifferenceAtIndex(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len1 < len2 {
|
||||||
|
return .DifferenceAtIndex(len1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len2 < len1 {
|
||||||
|
return .DifferenceAtIndex(len2)
|
||||||
|
}
|
||||||
|
|
||||||
|
return .NoDifference
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Create a formatted String representation of difference between strings
|
||||||
|
///
|
||||||
|
/// :param: s1 First string
|
||||||
|
/// :param: s2 Second string
|
||||||
|
///
|
||||||
|
/// :returns: a string, possibly containing significant whitespace and newlines
|
||||||
|
public func prettyFirstDifferenceBetweenStrings(s1: String, s2: String) -> String {
|
||||||
|
let firstDifferenceResult = firstDifferenceBetweenStrings(s1: s1 as NSString, s2: s2 as NSString)
|
||||||
|
return prettyDescriptionOfFirstDifferenceResult(firstDifferenceResult: firstDifferenceResult, s1: s1 as NSString, s2: s2 as NSString) as String
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Create a formatted String representation of a FirstDifferenceResult for two strings
|
||||||
|
///
|
||||||
|
/// :param: firstDifferenceResult FirstDifferenceResult
|
||||||
|
/// :param: s1 First string used in generation of firstDifferenceResult
|
||||||
|
/// :param: s2 Second string used in generation of firstDifferenceResult
|
||||||
|
///
|
||||||
|
/// :returns: a printable string, possibly containing significant whitespace and newlines
|
||||||
|
public func prettyDescriptionOfFirstDifferenceResult(firstDifferenceResult: FirstDifferenceResult, s1: NSString, s2: NSString) -> NSString {
|
||||||
|
|
||||||
|
func diffString(index: Int, s1: NSString, s2: NSString) -> NSString {
|
||||||
|
let markerArrow = "\u{2b06}" // "⬆"
|
||||||
|
let ellipsis = "\u{2026}" // "…"
|
||||||
|
/// Given a string and a range, return a string representing that substring.
|
||||||
|
///
|
||||||
|
/// If the range starts at a position other than 0, an ellipsis
|
||||||
|
/// will be included at the beginning.
|
||||||
|
///
|
||||||
|
/// If the range ends before the actual end of the string,
|
||||||
|
/// an ellipsis is added at the end.
|
||||||
|
func windowSubstring(s: NSString, range: NSRange) -> String {
|
||||||
|
let validRange = NSMakeRange(range.location, min(range.length, s.length - range.location))
|
||||||
|
let substring = s.substring(with: validRange)
|
||||||
|
|
||||||
|
let prefix = range.location > 0 ? ellipsis : ""
|
||||||
|
let suffix = (s.length - range.location > range.length) ? ellipsis : ""
|
||||||
|
|
||||||
|
return "\(prefix)\(substring)\(suffix)"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show this many characters before and after the first difference
|
||||||
|
let windowPrefixLength = 10
|
||||||
|
let windowSuffixLength = 10
|
||||||
|
let windowLength = windowPrefixLength + 1 + windowSuffixLength
|
||||||
|
|
||||||
|
let windowIndex = max(index - windowPrefixLength, 0)
|
||||||
|
let windowRange = NSMakeRange(windowIndex, windowLength)
|
||||||
|
|
||||||
|
let sub1 = windowSubstring(s: s1, range: windowRange)
|
||||||
|
let sub2 = windowSubstring(s: s2, range: windowRange)
|
||||||
|
|
||||||
|
let markerPosition = min(windowSuffixLength, index) + (windowIndex > 0 ? 1 : 0)
|
||||||
|
|
||||||
|
let markerPrefix = String(repeating: " " as Character, count: markerPosition)
|
||||||
|
let markerLine = "\(markerPrefix)\(markerArrow)"
|
||||||
|
|
||||||
|
return "Difference at index \(index):\n\(sub1)\n\(sub2)\n\(markerLine)" as NSString
|
||||||
|
}
|
||||||
|
|
||||||
|
switch firstDifferenceResult {
|
||||||
|
case .NoDifference: return "No difference"
|
||||||
|
case .DifferenceAtIndex(let index): return diffString(index: index, s1: s1, s2: s2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Result type for firstDifferenceBetweenStrings()
|
||||||
|
public enum FirstDifferenceResult {
|
||||||
|
/// Strings are identical
|
||||||
|
case NoDifference
|
||||||
|
|
||||||
|
/// Strings differ at the specified index.
|
||||||
|
///
|
||||||
|
/// This could mean that characters at the specified index are different,
|
||||||
|
/// or that one string is longer than the other
|
||||||
|
case DifferenceAtIndex(Int)
|
||||||
|
}
|
||||||
|
|
||||||
|
extension FirstDifferenceResult {
|
||||||
|
/// Textual representation of a FirstDifferenceResult
|
||||||
|
public var description: String {
|
||||||
|
switch self {
|
||||||
|
case .NoDifference:
|
||||||
|
return "NoDifference"
|
||||||
|
case .DifferenceAtIndex(let index):
|
||||||
|
return "DifferenceAtIndex(\(index))"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Textual representation of a FirstDifferenceResult for debugging purposes
|
||||||
|
public var debugDescription: String {
|
||||||
|
return self.description
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ final class ColorsConfigurationTests: XCTestCase {
|
|||||||
xcassetsPath: "path/to/assets.xcassets",
|
xcassetsPath: "path/to/assets.xcassets",
|
||||||
extensionOutputPath: "Colors/Generated",
|
extensionOutputPath: "Colors/Generated",
|
||||||
extensionName: nil,
|
extensionName: nil,
|
||||||
extensionNameSwiftUI: nil,
|
extensionNameUIKit: nil,
|
||||||
extensionSuffix: nil,
|
extensionSuffix: nil,
|
||||||
staticMembers: false)
|
staticMembers: false)
|
||||||
// When
|
// When
|
||||||
@ -50,7 +50,7 @@ final class ColorsConfigurationTests: XCTestCase {
|
|||||||
xcassetsPath: "path/to/assets.xcassets",
|
xcassetsPath: "path/to/assets.xcassets",
|
||||||
extensionOutputPath: "Colors/Generated",
|
extensionOutputPath: "Colors/Generated",
|
||||||
extensionName: "AppUIColor",
|
extensionName: "AppUIColor",
|
||||||
extensionNameSwiftUI: "AppColor",
|
extensionNameUIKit: "AppColor",
|
||||||
extensionSuffix: "Testing",
|
extensionSuffix: "Testing",
|
||||||
staticMembers: false)
|
staticMembers: false)
|
||||||
// When
|
// When
|
||||||
@ -70,7 +70,7 @@ final class ColorsConfigurationTests: XCTestCase {
|
|||||||
"false",
|
"false",
|
||||||
"--extension-name",
|
"--extension-name",
|
||||||
"AppUIColor",
|
"AppUIColor",
|
||||||
"--extension-name-swift-ui",
|
"--extension-name-ui-kit",
|
||||||
"AppColor",
|
"AppColor",
|
||||||
"--extension-suffix",
|
"--extension-suffix",
|
||||||
"Testing",
|
"Testing",
|
||||||
|
@ -19,7 +19,7 @@ final class FontsConfigurationTests: XCTestCase {
|
|||||||
let testingConfiguration = FontsConfiguration(inputFile: "path/to/fonts.txt",
|
let testingConfiguration = FontsConfiguration(inputFile: "path/to/fonts.txt",
|
||||||
extensionOutputPath: "Fonts/Generated",
|
extensionOutputPath: "Fonts/Generated",
|
||||||
extensionName: nil,
|
extensionName: nil,
|
||||||
extensionNameSwiftUI: nil,
|
extensionNameUIKit: nil,
|
||||||
extensionSuffix: nil,
|
extensionSuffix: nil,
|
||||||
infoPlistPaths: nil,
|
infoPlistPaths: nil,
|
||||||
staticMembers: nil)
|
staticMembers: nil)
|
||||||
@ -43,7 +43,7 @@ final class FontsConfigurationTests: XCTestCase {
|
|||||||
let testingConfiguration = FontsConfiguration(inputFile: "path/to/fonts.txt",
|
let testingConfiguration = FontsConfiguration(inputFile: "path/to/fonts.txt",
|
||||||
extensionOutputPath: "Fonts/Generated",
|
extensionOutputPath: "Fonts/Generated",
|
||||||
extensionName: "AppUIFont",
|
extensionName: "AppUIFont",
|
||||||
extensionNameSwiftUI: "AppFont",
|
extensionNameUIKit: "AppFont",
|
||||||
extensionSuffix: "Testing",
|
extensionSuffix: "Testing",
|
||||||
infoPlistPaths: "path/to/plist1.plist path/to/plist2.plist",
|
infoPlistPaths: "path/to/plist1.plist path/to/plist2.plist",
|
||||||
staticMembers: true)
|
staticMembers: true)
|
||||||
@ -60,7 +60,7 @@ final class FontsConfigurationTests: XCTestCase {
|
|||||||
"true",
|
"true",
|
||||||
"--extension-name",
|
"--extension-name",
|
||||||
"AppUIFont",
|
"AppUIFont",
|
||||||
"--extension-name-swift-ui",
|
"--extension-name-ui-kit",
|
||||||
"AppFont",
|
"AppFont",
|
||||||
"--extension-suffix",
|
"--extension-suffix",
|
||||||
"Testing",
|
"Testing",
|
||||||
|
@ -20,7 +20,7 @@ final class ImagesConfigurationTests: XCTestCase {
|
|||||||
xcassetsPath: "path/to/assets.xcassets",
|
xcassetsPath: "path/to/assets.xcassets",
|
||||||
extensionOutputPath: "Images/Generated",
|
extensionOutputPath: "Images/Generated",
|
||||||
extensionName: nil,
|
extensionName: nil,
|
||||||
extensionNameSwiftUI: nil,
|
extensionNameUIKit: nil,
|
||||||
extensionSuffix: nil,
|
extensionSuffix: nil,
|
||||||
staticMembers: nil)
|
staticMembers: nil)
|
||||||
|
|
||||||
@ -47,7 +47,7 @@ final class ImagesConfigurationTests: XCTestCase {
|
|||||||
xcassetsPath: "path/to/assets.xcassets",
|
xcassetsPath: "path/to/assets.xcassets",
|
||||||
extensionOutputPath: "Images/Generated",
|
extensionOutputPath: "Images/Generated",
|
||||||
extensionName: "AppUIImage",
|
extensionName: "AppUIImage",
|
||||||
extensionNameSwiftUI: "AppImage",
|
extensionNameUIKit: "AppImage",
|
||||||
extensionSuffix: "Testing",
|
extensionSuffix: "Testing",
|
||||||
staticMembers: true)
|
staticMembers: true)
|
||||||
|
|
||||||
@ -66,7 +66,7 @@ final class ImagesConfigurationTests: XCTestCase {
|
|||||||
"true",
|
"true",
|
||||||
"--extension-name",
|
"--extension-name",
|
||||||
"AppUIImage",
|
"AppUIImage",
|
||||||
"--extension-name-swift-ui",
|
"--extension-name-ui-kit",
|
||||||
"AppImage",
|
"AppImage",
|
||||||
"--extension-suffix",
|
"--extension-suffix",
|
||||||
"Testing",
|
"Testing",
|
||||||
|
@ -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
48
script/swiftlint.sh
Executable file
48
script/swiftlint.sh
Executable file
@ -0,0 +1,48 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Go to git repo root level
|
||||||
|
cd $(git rev-parse --show-toplevel)
|
||||||
|
|
||||||
|
if [[ "$BUILD_DIR" == *"IBDesignables"* ]] || [[ "$BUILD_DIR" == *"Previews"* ]] ; then
|
||||||
|
echo "not linting for IBDesignables/SwiftUI Previews builds";
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
SWIFT_LINT=$(which swiftlint)
|
||||||
|
|
||||||
|
if [[ -z $SWIFT_LINT ]] ; then
|
||||||
|
echo "warning: SwiftLint not installed, please download it from https://github.com/realm/SwiftLint"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $RUN_CLANG_STATIC_ANALYZER == "YES" ]] ; then
|
||||||
|
time $SWIFT_LINT
|
||||||
|
else
|
||||||
|
COUNT=0
|
||||||
|
|
||||||
|
##### Check for modified git files #####
|
||||||
|
FILES=$(git diff --name-only | grep -iv "^carthage" | grep -iv "^pods" | grep -iv "^vendor" | grep -v "R2" | grep ".swift$")
|
||||||
|
if [ ! -z "$FILES" ]; then
|
||||||
|
while read FILE_PATH; do
|
||||||
|
export SCRIPT_INPUT_FILE_$COUNT=$FILE_PATH
|
||||||
|
COUNT=$((COUNT + 1))
|
||||||
|
done <<< "$FILES"
|
||||||
|
fi
|
||||||
|
|
||||||
|
##### Check for modified files in unstaged/Staged area #####
|
||||||
|
FILES=$(git diff --name-only --cached --diff-filter=d | grep -iv "^carthage" | grep -iv "^pods" | grep -iv "^vendor" | grep -v "R2" | grep ".swift$")
|
||||||
|
if [ ! -z "$FILES" ]; then
|
||||||
|
while read FILE_PATH; do
|
||||||
|
export SCRIPT_INPUT_FILE_$COUNT=$FILE_PATH
|
||||||
|
COUNT=$((COUNT + 1))
|
||||||
|
done <<< "$FILES"
|
||||||
|
fi
|
||||||
|
|
||||||
|
##### Make the count avilable as global variable #####
|
||||||
|
export SCRIPT_INPUT_FILE_COUNT=$COUNT
|
||||||
|
env | grep SCRIPT_INPUT_FILE_
|
||||||
|
|
||||||
|
if [[ COUNT -ne 0 ]] ; then
|
||||||
|
time $SWIFT_LINT --use-script-input-files
|
||||||
|
fi
|
||||||
|
fi
|
Loading…
x
Reference in New Issue
Block a user