Compare commits
43 Commits
1.2.1
...
129eb135f1
Author | SHA1 | Date | |
---|---|---|---|
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 | |||
7d6bb4fcb9 | |||
844a8aec45 | |||
beb28e652d |
@ -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.3.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
|
||||||
|
93
README.md
93
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
|
||||||
|
|
||||||
@ -133,6 +133,71 @@ 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.
|
||||||
@ -142,7 +207,7 @@ 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,8 +218,8 @@ 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
|
||||||
|
|
||||||
@ -176,7 +241,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 +250,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 +261,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 +301,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 +310,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?
|
||||||
...
|
...
|
||||||
|
@ -6,13 +6,24 @@ fileprivate let kStringsFileName = "sampleStrings"
|
|||||||
|
|
||||||
extension String {
|
extension String {
|
||||||
|
|
||||||
enum Key: String {
|
enum KeyGenAllScript: String {
|
||||||
case param_lang = "param_lang"
|
case param_lang = "param_lang"
|
||||||
case generic_back = "generic_back"
|
case generic_back = "generic_back"
|
||||||
case generic_loading_data = "generic_loading_data"
|
case generic_loading_data = "generic_loading_data"
|
||||||
case generic_welcome_firstname_format = "generic_welcome_firstname_format"
|
case generic_welcome_firstname_format = "generic_welcome_firstname_format"
|
||||||
case test_equal_symbol = "test_equal_symbol"
|
case test_equal_symbol = "test_equal_symbol"
|
||||||
case placeholders_test_one = "placeholders_test_one"
|
case placeholders_test_one = "placeholders_test_one"
|
||||||
|
|
||||||
|
var keyPath: KeyPath<String, String> {
|
||||||
|
switch self {
|
||||||
|
case .param_lang: return \String.param_lang
|
||||||
|
case .generic_back: return \String.generic_back
|
||||||
|
case .generic_loading_data: return \String.generic_loading_data
|
||||||
|
case .generic_welcome_firstname_format: return \String.generic_welcome_firstname_format
|
||||||
|
case .test_equal_symbol: return \String.test_equal_symbol
|
||||||
|
case .placeholders_test_one: return \String.placeholders_test_one
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Webservice
|
// MARK: - Webservice
|
||||||
|
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
|
@ -2,60 +2,69 @@
|
|||||||
|
|
||||||
FORCE_FLAG="$1"
|
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"
|
||||||
|
#
|
||||||
|
#echo "\n-------------------------\n"
|
||||||
|
#
|
||||||
|
## Color
|
||||||
|
#swift run -c release ResgenSwift colors $FORCE_FLAG "./Colors/sampleColors1.txt" \
|
||||||
|
# --style all \
|
||||||
|
# --xcassets-path "./Colors/colors.xcassets" \
|
||||||
|
# --extension-output-path "./Colors/Generated/" \
|
||||||
|
# --extension-name "ColorYolo" \
|
||||||
|
# --extension-name-ui-kit "UIhkjhkColorYolo" \
|
||||||
|
# --extension-suffix "GenAllScript"
|
||||||
|
#
|
||||||
|
#echo "\n-------------------------\n"
|
||||||
|
#
|
||||||
|
## Twine
|
||||||
|
#swift run -c release ResgenSwift strings twine $FORCE_FLAG "./Twine/sampleStrings.txt" \
|
||||||
|
# --output-path "./Twine/Generated" \
|
||||||
|
# --langs "fr en en-us" \
|
||||||
|
# --default-lang "en" \
|
||||||
|
# --extension-output-path "./Twine/Generated"
|
||||||
|
|
||||||
echo "\n-------------------------\n"
|
#echo "\n-------------------------\n"
|
||||||
|
|
||||||
# Color
|
## Strings
|
||||||
swift run -c release ResgenSwift colors $FORCE_FLAG "./Colors/sampleColors1.txt" \
|
#swift run -c release ResgenSwift strings stringium $FORCE_FLAG "./Strings/sampleStrings.txt" \
|
||||||
--style all \
|
# --output-path "./Strings/Generated" \
|
||||||
--xcassets-path "./Colors/colors.xcassets" \
|
# --langs "fr en en-us" \
|
||||||
--extension-output-path "./Colors/Generated/" \
|
# --default-lang "en" \
|
||||||
--extension-name "UIColorYolo" \
|
# --extension-output-path "./Strings/Generated" \
|
||||||
--extension-name-swift-ui "ColorYolo" \
|
# --extension-name "String" \
|
||||||
--extension-suffix "GenAllScript"
|
# --extension-suffix "GenAllScript"
|
||||||
|
|
||||||
echo "\n-------------------------\n"
|
#echo "\n-------------------------\n"
|
||||||
|
|
||||||
# Twine
|
## Tags
|
||||||
swift run -c release ResgenSwift strings twine $FORCE_FLAG "./Twine/sampleStrings.txt" \
|
#swift run -c release ResgenSwift strings tags $FORCE_FLAG "./Tags/sampleTags.txt" \
|
||||||
--output-path "./Twine/Generated" \
|
# --lang "ium" \
|
||||||
--langs "fr en en-us" \
|
# --extension-output-path "./Tags/Generated" \
|
||||||
--default-lang "en" \
|
# --extension-name "Tags" \
|
||||||
--extension-output-path "./Twine/Generated"
|
# --extension-suffix "GenAllScript"
|
||||||
|
|
||||||
echo "\n-------------------------\n"
|
#echo "\n-------------------------\n"
|
||||||
|
|
||||||
# Strings
|
# Analytics
|
||||||
swift run -c release ResgenSwift strings stringium $FORCE_FLAG "./Strings/sampleStrings.txt" \
|
swift run -c release ResgenSwift analytics $FORCE_FLAG "./Tags/sampleTags.yml" \
|
||||||
--output-path "./Strings/Generated" \
|
--target "matomo firebase" \
|
||||||
--langs "fr en en-us" \
|
|
||||||
--default-lang "en" \
|
|
||||||
--extension-output-path "./Strings/Generated" \
|
|
||||||
--extension-name "String" \
|
|
||||||
--extension-suffix "GenAllScript"
|
|
||||||
|
|
||||||
echo "\n-------------------------\n"
|
|
||||||
|
|
||||||
# Tags
|
|
||||||
swift run -c release ResgenSwift strings tags $FORCE_FLAG "./Tags/sampleTags.txt" \
|
|
||||||
--lang "ium" \
|
|
||||||
--extension-output-path "./Tags/Generated" \
|
--extension-output-path "./Tags/Generated" \
|
||||||
--extension-name "Tags" \
|
--extension-name "Analytics" \
|
||||||
--extension-suffix "GenAllScript"
|
--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)
|
||||||
-----------
|
-----------
|
||||||
-----------
|
-----------
|
||||||
@ -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)
|
||||||
|
@ -119,7 +119,7 @@ class XcassetsGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Write Content.json
|
// Write Content.json
|
||||||
let imagesetContentJson = parsedImage.contentJson
|
guard let imagesetContentJson = parsedImage.contentJson else { return }
|
||||||
let contentJsonFilePath = "\(imagesetPath)/Contents.json"
|
let contentJsonFilePath = "\(imagesetPath)/Contents.json"
|
||||||
|
|
||||||
let contentJsonFilePathURL = URL(fileURLWithPath: contentJsonFilePath)
|
let contentJsonFilePathURL = URL(fileURLWithPath: contentJsonFilePath)
|
||||||
|
@ -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)
|
||||||
|
@ -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: -
|
||||||
|
32
Sources/ResgenSwift/Images/Model/ImageContent.swift
Normal file
32
Sources/ResgenSwift/Images/Model/ImageContent.swift
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
//
|
||||||
|
// ImageContent.swift
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Created by Quentin Bandera on 19/04/2024.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct AssetContent: Codable, Equatable {
|
||||||
|
let images: [AssetImageDescription]
|
||||||
|
let info: AssetInfo
|
||||||
|
|
||||||
|
static func == (lhs: AssetContent, rhs: AssetContent) -> Bool {
|
||||||
|
guard lhs.images.count == rhs.images.count else { return false }
|
||||||
|
let lhsImages = lhs.images.sorted(by: { $0.scale < $1.scale })
|
||||||
|
let rhsImages = rhs.images.sorted(by: { $0.scale < $1.scale })
|
||||||
|
|
||||||
|
return lhsImages == rhsImages
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AssetImageDescription: Codable, Equatable {
|
||||||
|
let idiom: String
|
||||||
|
let scale: String
|
||||||
|
let filename: String
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AssetInfo: Codable, Equatable {
|
||||||
|
let version: Int
|
||||||
|
let author: String
|
||||||
|
}
|
@ -42,34 +42,45 @@ struct ParsedImage {
|
|||||||
|
|
||||||
// MARK: - Assets
|
// MARK: - Assets
|
||||||
|
|
||||||
var contentJson: String {
|
var contentJson: String? {
|
||||||
"""
|
let encoder = JSONEncoder()
|
||||||
{
|
encoder.outputFormatting = .prettyPrinted
|
||||||
"images" : [
|
|
||||||
{
|
guard let data = try? encoder.encode(imageContent) else {
|
||||||
"idiom" : "universal",
|
let error = ImagesError.writeFile("Contents.json", "Error encoding json file")
|
||||||
"scale" : "1x",
|
print(error.description)
|
||||||
"filename" : "\(name).\(XcassetsGenerator.outputImageExtension)"
|
Images.exit(withError: error)
|
||||||
},
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "2x",
|
|
||||||
"filename" : "\(name)@2x.\(XcassetsGenerator.outputImageExtension)"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "3x",
|
|
||||||
"filename" : "\(name)@3x.\(XcassetsGenerator.outputImageExtension)"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"version" : 1,
|
|
||||||
"author" : "ResgenSwift-Imagium"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
"""
|
|
||||||
|
return String(data: data, encoding: .utf8)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var imageContent: AssetContent {
|
||||||
|
return AssetContent(
|
||||||
|
images: [
|
||||||
|
AssetImageDescription(
|
||||||
|
idiom: "universal",
|
||||||
|
scale: "1x",
|
||||||
|
filename: "\(name).\(XcassetsGenerator.outputImageExtension)"
|
||||||
|
),
|
||||||
|
AssetImageDescription(
|
||||||
|
idiom: "universal",
|
||||||
|
scale: "2x",
|
||||||
|
filename: "\(name)@2x.\(XcassetsGenerator.outputImageExtension)"
|
||||||
|
),
|
||||||
|
AssetImageDescription(
|
||||||
|
idiom: "universal",
|
||||||
|
scale: "3x",
|
||||||
|
filename: "\(name)@3x.\(XcassetsGenerator.outputImageExtension)"
|
||||||
|
)
|
||||||
|
],
|
||||||
|
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 {
|
||||||
|
@ -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,123 @@ class StringsFileGenerator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return stringsFileContent
|
return stringsFileContent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - XcStrings Generation
|
||||||
|
|
||||||
|
static func generateXcStringsFileContent(langs: [String],
|
||||||
|
defaultLang: String,
|
||||||
|
tags inputTags: [String],
|
||||||
|
sections: [Section]) -> String {
|
||||||
|
let rootObject = generateRootObject(langs: langs, defaultLang: defaultLang, tags: inputTags, sections: sections)
|
||||||
|
let file = generateXcStringsFileContentFromRootObject(rootObject: rootObject)
|
||||||
|
|
||||||
|
return file
|
||||||
|
}
|
||||||
|
|
||||||
|
static func generateXcStringsFileContentFromRootObject(rootObject: Root) -> String {
|
||||||
|
do {
|
||||||
|
let encoder = JSONEncoder()
|
||||||
|
encoder.outputFormatting = [.prettyPrinted]
|
||||||
|
|
||||||
|
let json = try encoder.encode(rootObject)
|
||||||
|
|
||||||
|
if let jsonString = String(data: json, encoding: .utf8) {
|
||||||
|
return jsonString
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch {
|
||||||
|
debugPrint("Failed to encode: \(error)")
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
static func generateRootObject(langs: [String],
|
||||||
|
defaultLang: String,
|
||||||
|
tags inputTags: [String],
|
||||||
|
sections: [Section]) -> Root {
|
||||||
|
|
||||||
|
var xcStringDefinitionTab: [XCStringDefinition] = []
|
||||||
|
|
||||||
|
sections.forEach { section in
|
||||||
|
// Check that at least one string will be generated
|
||||||
|
guard section.hasOneOrMoreMatchingTags(tags: inputTags) else {
|
||||||
|
return // Go to next section
|
||||||
|
}
|
||||||
|
|
||||||
|
section.definitions.forEach { definition in
|
||||||
|
var skipDefinition = false
|
||||||
|
var isNoTranslation = false
|
||||||
|
|
||||||
|
var localizationTab: [XCStringLocalization] = []
|
||||||
|
|
||||||
|
if definition.hasOneOrMoreMatchingTags(inputTags: inputTags) == false {
|
||||||
|
skipDefinition = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if definition.tags.contains(Stringium.noTranslationTag) {
|
||||||
|
isNoTranslation = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if !skipDefinition {
|
||||||
|
if isNoTranslation {
|
||||||
|
// Search for langs in yaml
|
||||||
|
for lang in langs {
|
||||||
|
if let value = definition.translations[defaultLang], !value.isEmpty {
|
||||||
|
let localization = XCStringLocalization(
|
||||||
|
lang: lang,
|
||||||
|
content: XCStringLocalizationLangContent(
|
||||||
|
stringUnit: DefaultStringUnit(state: "translated", value: value)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
localizationTab.append(localization)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Search for langs in twine
|
||||||
|
for (lang, value) in definition.translations where !value.isEmpty {
|
||||||
|
|
||||||
|
let localization = XCStringLocalization(
|
||||||
|
lang: lang,
|
||||||
|
content: XCStringLocalizationLangContent(
|
||||||
|
stringUnit: DefaultStringUnit(state: "translated", value: value)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
localizationTab.append(localization)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let xcStringDefinition = XCStringDefinition(
|
||||||
|
title: definition.name,
|
||||||
|
content: XCStringDefinitionContent(
|
||||||
|
comment: definition.comment,
|
||||||
|
extractionState: "manual",
|
||||||
|
localizations: XCStringLocalizationContainer(
|
||||||
|
localizations: localizationTab
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
xcStringDefinitionTab.append(xcStringDefinition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let xcStringContainer = XCStringDefinitionContainer(strings: xcStringDefinitionTab)
|
||||||
|
|
||||||
|
return Root(
|
||||||
|
sourceLanguage: defaultLang,
|
||||||
|
strings: xcStringContainer,
|
||||||
|
version: "1.0"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Extension file
|
// MARK: - Extension file
|
||||||
|
|
||||||
static func writeExtensionFiles(sections: [Section],
|
static func writeExtensionFiles(sections: [Section],
|
||||||
defaultLang lang: String,
|
defaultLang lang: String,
|
||||||
tags: [String],
|
tags: [String],
|
||||||
@ -110,20 +247,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],
|
||||||
@ -133,36 +270,37 @@ class StringsFileGenerator {
|
|||||||
extensionSuffix: String) -> String {
|
extensionSuffix: String) -> String {
|
||||||
[
|
[
|
||||||
Self.getHeader(stringsFilename: inputFilename, extensionClassname: extensionName),
|
Self.getHeader(stringsFilename: inputFilename, extensionClassname: extensionName),
|
||||||
Self.getEnumKey(sections: sections, tags: tags, extensionSuffix: extensionSuffix),
|
Self.getEnumKey(sections: sections, tags: tags, extensionClassname: extensionName, extensionSuffix: extensionSuffix),
|
||||||
Self.getProperties(sections: sections, defaultLang: lang, tags: tags, staticVar: staticVar),
|
Self.getProperties(sections: sections, defaultLang: lang, tags: tags, staticVar: staticVar),
|
||||||
Self.getFooter()
|
Self.getFooter()
|
||||||
]
|
]
|
||||||
.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], 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
|
||||||
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
|
||||||
@ -171,25 +309,45 @@ class StringsFileGenerator {
|
|||||||
enumDefinition += " case \(definition.name) = \"\(definition.name)\"\n"
|
enumDefinition += " case \(definition.name) = \"\(definition.name)\"\n"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enumDefinition += " }"
|
// KeyPath accessors
|
||||||
|
enumDefinition += "\n"
|
||||||
|
enumDefinition += " var keyPath: KeyPath<\(extensionClassname), String> {\n"
|
||||||
|
enumDefinition += " switch self {\n"
|
||||||
|
sections.forEach { section in
|
||||||
|
// Check that at least one string will be generated
|
||||||
|
guard section.hasOneOrMoreMatchingTags(tags: tags) else {
|
||||||
|
return // Go to next section
|
||||||
|
}
|
||||||
|
|
||||||
|
section.definitions.forEach { definition in
|
||||||
|
guard definition.hasOneOrMoreMatchingTags(inputTags: tags) == true else {
|
||||||
|
return // Go to next definition
|
||||||
|
}
|
||||||
|
debugPrint("Found definition")
|
||||||
|
enumDefinition += " case .\(definition.name): return \\\(extensionClassname).\(definition.name)\n"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
enumDefinition += " }\n" // Switch
|
||||||
|
enumDefinition += " }\n" // var keyPath
|
||||||
|
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))"
|
||||||
}
|
}
|
||||||
@ -200,11 +358,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",
|
||||||
|
@ -127,35 +127,33 @@ final class ParsedImageTests: XCTestCase {
|
|||||||
height: 10)
|
height: 10)
|
||||||
|
|
||||||
// When
|
// When
|
||||||
let property = parsedImage.contentJson
|
let property = parsedImage.imageContent
|
||||||
|
|
||||||
// Expect
|
// Expect
|
||||||
let expect = """
|
let expect = AssetContent(
|
||||||
{
|
images: [
|
||||||
"images" : [
|
AssetImageDescription(
|
||||||
{
|
idiom: "universal",
|
||||||
"idiom" : "universal",
|
scale: "1x",
|
||||||
"scale" : "1x",
|
filename: "\(parsedImage.name).\(XcassetsGenerator.outputImageExtension)"
|
||||||
"filename" : "\(imageName).\(XcassetsGenerator.outputImageExtension)"
|
),
|
||||||
},
|
AssetImageDescription(
|
||||||
{
|
idiom: "universal",
|
||||||
"idiom" : "universal",
|
scale: "2x",
|
||||||
"scale" : "2x",
|
filename: "\(parsedImage.name)@2x.\(XcassetsGenerator.outputImageExtension)"
|
||||||
"filename" : "\(imageName)@2x.\(XcassetsGenerator.outputImageExtension)"
|
),
|
||||||
},
|
AssetImageDescription(
|
||||||
{
|
idiom: "universal",
|
||||||
"idiom" : "universal",
|
scale: "3x",
|
||||||
"scale" : "3x",
|
filename: "\(parsedImage.name)@3x.\(XcassetsGenerator.outputImageExtension)"
|
||||||
"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())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
Reference in New Issue
Block a user