Compare commits
65 Commits
Author | SHA1 | Date | |
---|---|---|---|
8442c89944 | |||
57cedd37bb | |||
09556ba6e0 | |||
dea57dc1e2 | |||
07575bd2bf | |||
8686ae974c | |||
be4c561ea8 | |||
2357a40fff | |||
d4afa9c9e9 | |||
76ef0a2d59 | |||
129eb135f1 | |||
4ad15fcded | |||
fb2ddb2227 | |||
27f86f5c4d | |||
209ba49e3f | |||
ba07005b13 | |||
6c3f3a8982 | |||
0d651b810f | |||
1d7fc76340 | |||
5d4e461933 | |||
55264d61ad | |||
d21ad9d1ea | |||
0bd6c3c2d4 | |||
eed20367b9 | |||
43b5111d79 | |||
2983093a9c | |||
b4bbaa3bfd | |||
498c8fa4ae | |||
2957da6233 | |||
d79af06c38 | |||
d8937f2de6 | |||
9b27f24197 | |||
1d58fd5510 | |||
f6c49bf626 | |||
f1b62d83c4 | |||
ee5055efa5 | |||
6f8e3b6664 | |||
1f2933950b | |||
3b90387e10 | |||
1ee4998ec6 | |||
ca763cd5d0 | |||
3fc2fd9bac | |||
09c153ba65 | |||
2a144fc00e | |||
6aef8bc2de | |||
3e133773a9 | |||
5fd680110c | |||
ce274219fc | |||
fa5bf192e8 | |||
1a45ec7b0d | |||
7d6bb4fcb9 | |||
844a8aec45 | |||
beb28e652d | |||
78be15d57d | |||
d6c4702390 | |||
1e073af5df | |||
188178fe6a | |||
c31d0b1618 | |||
b662fc64f3 | |||
9ab7e74991 | |||
fc427733ee | |||
5a3d273acc | |||
a7a850799d | |||
7d3652f1f9 | |||
41733d2680 |
@ -102,6 +102,20 @@
|
||||
ReferencedContainer = "container:">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<CommandLineArguments>
|
||||
<CommandLineArgument
|
||||
argument = "generate"
|
||||
isEnabled = "YES">
|
||||
</CommandLineArgument>
|
||||
<CommandLineArgument
|
||||
argument = ""$(PROJECT_DIR)/../SampleFiles/resgenConfiguration.yml""
|
||||
isEnabled = "YES">
|
||||
</CommandLineArgument>
|
||||
<CommandLineArgument
|
||||
argument = "--project-directory "$(PROJECT_DIR)""
|
||||
isEnabled = "YES">
|
||||
</CommandLineArgument>
|
||||
</CommandLineArguments>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
|
34
CHANGELOG.md
34
CHANGELOG.md
@ -0,0 +1,34 @@
|
||||
# v1.2 - Architecture generation
|
||||
|
||||
## New
|
||||
- New section in configuration file: `architecture`. Define your ressources accessors achitecture and let ResgenSwift generate it for you. Check out Readme to know more.
|
||||
|
||||
## Fixes
|
||||
- Errors and Warnings are now shown in Xcode Issue Navigator
|
||||
|
||||
---
|
||||
# v1.1 - SwiftUI compatibility
|
||||
|
||||
## New
|
||||
- Update plist `UIAppFonts` when generated fonts (use plistBuddy)
|
||||
- New parameter: `infoPlistPaths`
|
||||
- Generate SwiftUI extensions for colors, fonts and images
|
||||
- New parameter: `extensionNameUIKit`
|
||||
- Adding Makefile to install, unsintall and create man page.
|
||||
|
||||
## Fixes
|
||||
Fix SwiftLint rule `trailing_newline`
|
||||
|
||||
---
|
||||
# v1.0 - Configuration file
|
||||
|
||||
## Major
|
||||
- A new command has been added: `generate`. Instead of runnning every single command, it will run all necessary command based on a `yaml` configuration file. Check out Readme to know more.
|
||||
|
||||
## Minors
|
||||
- Code refactoring
|
||||
- Huge performance improvements
|
||||
- Readme.md update
|
||||
- Add option to generate static properties/methods (`staticMembers`)
|
||||
- Add option to specify the project directory (`--project-directory`). It allows to run ResgenSwift from anywhere
|
||||
- Add `install.sh` script to install ResgenSwift in `/usr/local/bin`
|
2
Jenkinsfile
vendored
2
Jenkinsfile
vendored
@ -1,6 +1,6 @@
|
||||
library "openiumpipeline"
|
||||
|
||||
env.DEVELOPER_DIR="/Applications/Xcode_13.3.0.app/Contents/Developer"
|
||||
env.DEVELOPER_DIR="/Applications/Xcode-15.4.0.app/Contents/Developer"
|
||||
//env.SIMULATOR_DEVICE_TYPES="iPad--7th-generation-"
|
||||
env.IS_PACKAGE_SWIFT=1
|
||||
env.TARGETS_MACOS=1
|
||||
|
63
Makefile
Normal file
63
Makefile
Normal file
@ -0,0 +1,63 @@
|
||||
SHELL = /bin/zsh
|
||||
|
||||
#INSTALL_DIR ?= /usr/local/bin
|
||||
INSTALL_DIR = /tmp/ResgenYolo
|
||||
|
||||
MAN_DIR := /usr/local/share/man
|
||||
MAN_PAGE_NAME = resgen-swift.1
|
||||
REPO_DIR = $(shell pwd)
|
||||
BUILD_DIR = $(REPO_DIR)/.build
|
||||
|
||||
#
|
||||
# Man pages
|
||||
#
|
||||
|
||||
# create-man-files:
|
||||
# swift package plugin generate-manual
|
||||
# cp $(BUILDDIR)/plugins/GenerateManualPlugin/outputs/ResgenSwift/resgen-swift.1 $(REPODIR)/man/resgen-swift.1
|
||||
|
||||
# install-man-files:
|
||||
# mkdir -p ${DESTDIR}${mandir}/man1
|
||||
# cp $(REPODIR)/man/resgen-swift.1 ${DESTDIR}${mandir}/man1/resgen-swift.1
|
||||
|
||||
create-and-install-man-files:
|
||||
swift package plugin generate-manual
|
||||
mkdir -p ${MAN_DIR}/man1
|
||||
cp $(BUILD_DIR)/plugins/GenerateManualPlugin/outputs/ResgenSwift/${MAN_PAGE_NAME} ${MAN_DIR}/man1/${MAN_PAGE_NAME}
|
||||
|
||||
#
|
||||
# Build and install
|
||||
#
|
||||
|
||||
build-debug:
|
||||
@swift build \
|
||||
-c debug \
|
||||
--build-path "$(BUILD_DIR)"
|
||||
|
||||
build-release:
|
||||
@swift build \
|
||||
-c release \
|
||||
--build-path "$(BUILD_DIR)"
|
||||
|
||||
.PHONY: install
|
||||
install: build-release
|
||||
@install -d "$(INSTALL_DIR)"
|
||||
@install "$(wildcard $(BUILD_DIR)/**/release/ResgenSwift)" "$(INSTALL_DIR)/resgen-swift"
|
||||
@make create-and-install-man-files
|
||||
|
||||
#
|
||||
# Uninstall and cleaning
|
||||
#
|
||||
|
||||
.PHONY: uninstall
|
||||
uninstall:
|
||||
@rm -rf "$(INSTALL_DIR)/resgen-swift"
|
||||
@rm -rf ${MAN_DIR}/man1/${MAN_PAGE_NAME}
|
||||
|
||||
.PHONY: clean
|
||||
distclean:
|
||||
@rm -f $(BUILD_DIR)/release
|
||||
|
||||
.PHONY: clean
|
||||
clean: distclean
|
||||
@rm -rf $(BUILD_DIR)
|
105
Package.resolved
105
Package.resolved
@ -1,25 +1,86 @@
|
||||
{
|
||||
"object": {
|
||||
"pins": [
|
||||
{
|
||||
"package": "swift-argument-parser",
|
||||
"repositoryURL": "https://github.com/apple/swift-argument-parser",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "e1465042f195f374b94f915ba8ca49de24300a0d",
|
||||
"version": "1.0.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "Yams",
|
||||
"repositoryURL": "https://github.com/jpsim/Yams.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "01835dc202670b5bb90d07f3eae41867e9ed29f6",
|
||||
"version": "5.0.1"
|
||||
}
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "collectionconcurrencykit",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/JohnSundell/CollectionConcurrencyKit.git",
|
||||
"state" : {
|
||||
"revision" : "b4f23e24b5a1bff301efc5e70871083ca029ff95",
|
||||
"version" : "0.2.0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"identity" : "cryptoswift",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/krzyzanowskim/CryptoSwift.git",
|
||||
"state" : {
|
||||
"revision" : "c9c3df6ab812de32bae61fc0cd1bf6d45170ebf0",
|
||||
"version" : "1.8.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "sourcekitten",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/jpsim/SourceKitten.git",
|
||||
"state" : {
|
||||
"revision" : "b6dc09ee51dfb0c66e042d2328c017483a1a5d56",
|
||||
"version" : "0.34.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-argument-parser",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-argument-parser",
|
||||
"state" : {
|
||||
"revision" : "8f4d2753f0e4778c76d5f05ad16c74f707390531",
|
||||
"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"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "yams",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/jpsim/Yams.git",
|
||||
"state" : {
|
||||
"revision" : "0d9ee7ea8c4ebd4a489ad7a73d5c6cad55d6fed3",
|
||||
"version" : "5.0.6"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 2
|
||||
}
|
||||
|
@ -9,7 +9,8 @@ let package = Package(
|
||||
dependencies: [
|
||||
// Dependencies declare other packages that this package depends on.
|
||||
.package(url: "https://github.com/apple/swift-argument-parser", from: "1.0.0"),
|
||||
.package(url: "https://github.com/jpsim/Yams.git", from: "5.0.1")
|
||||
.package(url: "https://github.com/jpsim/Yams.git", from: "5.0.1"),
|
||||
.package(url: "https://github.com/realm/SwiftLint.git", .upToNextMajor(from: "0.54.0")),
|
||||
],
|
||||
targets: [
|
||||
// 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",
|
||||
.product(name: "ArgumentParser", package: "swift-argument-parser"),
|
||||
"Yams"
|
||||
]
|
||||
],
|
||||
plugins: [.plugin(name: "SwiftLintPlugin", package: "SwiftLint")]
|
||||
),
|
||||
|
||||
// Helper targets
|
||||
|
190
README.md
190
README.md
@ -6,8 +6,9 @@ ResgenSwift is a package, fully written in Swift, to help you automatize ressour
|
||||
|
||||
## Fonts
|
||||
|
||||
Font generator generates an extension of `UIFont` (or a custom class). It also prints `UIAppFonts` to put in your project `.plist`.
|
||||
iOS required to use the **real name** of the font, this name can be different from its filename. To get the **real name**, it uses `fc-scan`.
|
||||
Font generator generates an extension of `UIFont` and `Font` (or custom classes). It also prints content of `UIAppFonts` from your project `.plist`. If project `.plist` is specified, it will update `UIAppFonts` content of all `.plist`.
|
||||
|
||||
iOS required to use the **real name** of the font, this name can be different from its filename. To get the **real name**, it uses `fc-scan`. So, be sure that the `$PATH` contains path of `fc-scan`.
|
||||
|
||||
**Example**
|
||||
|
||||
@ -15,8 +16,10 @@ 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" \
|
||||
--extension-output-path "./Fonts/Generated" \
|
||||
--extension-name "AppFont" \
|
||||
--extension-name-ui-kit "UIAppFont" \
|
||||
--extension-suffix "GreatApp" \
|
||||
--static-members true
|
||||
--static-members true \
|
||||
--info-plist-paths "./path/one/to/Info.plist ./path/two/to/Info.plist"
|
||||
```
|
||||
|
||||
**Parameters**
|
||||
@ -24,9 +27,11 @@ swift run -c release ResgenSwift fonts $FORCE_FLAG "./Fonts/fonts.txt" \
|
||||
1. `-f`: force generation
|
||||
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
|
||||
4. `--extension-name` *(optional)* : name of thee class to add the extension
|
||||
5. `--extension-suffix` *(optional)* : additional text which is added to the filename (ex: `AppFont+GreatApp.swift`)
|
||||
6. `--static-members` *(optional)*: generate static properties or not
|
||||
4. `--extension-name` *(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`)
|
||||
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
|
||||
|
||||
|
||||
## Colors
|
||||
@ -39,6 +44,7 @@ swift run -c release ResgenSwift colors $FORCE_FLAG "./Colors/colors.txt" \
|
||||
--xcassets-path "./Colors/colors.xcassets" \
|
||||
--extension-output-path "./Colors/Generated/" \
|
||||
--extension-name "AppColor" \
|
||||
--extension-name-ui-kit "UIAppColor" \
|
||||
--extension-suffix "GreatApp" \
|
||||
--static-members true
|
||||
```
|
||||
@ -49,9 +55,10 @@ swift run -c release ResgenSwift colors $FORCE_FLAG "./Colors/colors.txt" \
|
||||
2. Input colors file
|
||||
3. `--style` can be `all` or `light`
|
||||
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: `AppColor+GreatApp.swift`)
|
||||
7. `--static-members` *(optional)*: generate static properties or not
|
||||
5. `--extension-name` *(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`)
|
||||
8. `--static-members` *(optional)*: generate static properties or not
|
||||
|
||||
|
||||
## Strings
|
||||
@ -74,7 +81,7 @@ swift run -c release ResgenSwift strings twine $FORCE_FLAG "./Twine/strings.txt"
|
||||
2. Input translations file (must be Twine formatted)
|
||||
3. `--langs`: langs to generate (string with space between each lang)
|
||||
4. `--default-lang`: default lang that will be in `Base.lproj`. It must be in `langs` as well
|
||||
4. `--extension-output-path`: path where to generate generated extension
|
||||
5. `--extension-output-path`: path where to generate generated extension
|
||||
|
||||
### Stringium (recommended)
|
||||
|
||||
@ -86,6 +93,7 @@ swift run -c release ResgenSwift strings stringium $FORCE_FLAG "./Strings/string
|
||||
--extension-output-path "./Strings/Generated" \
|
||||
--extension-name "AppString" \
|
||||
--extension-suffix "GreatApp" \
|
||||
--xcStrings true
|
||||
--static-members true
|
||||
```
|
||||
|
||||
@ -98,6 +106,7 @@ swift run -c release ResgenSwift strings stringium $FORCE_FLAG "./Strings/string
|
||||
4. `--extension-output-path`: path where to generate generated extension
|
||||
5. `--extension-name` *(optional)* : name of class to add the extension
|
||||
6. `--extension-suffix` *(optional)* : additional text which is added to filename (ex: `AppString+GreatApp.swift`)
|
||||
6. `--xcStrings`*(optional)* : generate string catalog
|
||||
7. `--static-members` *(optional)*: generate static properties or not
|
||||
|
||||
|
||||
@ -126,15 +135,81 @@ swift run -c release ResgenSwift strings tags $FORCE_FLAG "./Tags/tags.txt" \
|
||||
|
||||
> ⚠️ If extension name is not set or is `Tags`, it will generate the following typaloas `typealias Tags = String`.
|
||||
|
||||
|
||||
## 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 generator will generate images assets along with extensions to access those images easily.
|
||||
Images generator will generate images assets along with extensions to access those images easily.
|
||||
|
||||
```sh
|
||||
swift run -c release ResgenSwift images $FORCE_FLAG "./Images/images.txt" \
|
||||
--xcassets-path "./Images/app.xcassets" \
|
||||
--extension-output-path "./Images/Generated" \
|
||||
--extension-name "AppImage" \
|
||||
--extension-name-ui-kit "UIAppImage" \
|
||||
--extension-suffix "GreatApp" \
|
||||
--static-members true
|
||||
```
|
||||
@ -145,10 +220,12 @@ swift run -c release ResgenSwift images $FORCE_FLAG "./Images/images.txt" \
|
||||
2. Input images definitions file
|
||||
3. `--xcassets-path`: xcasset path where to generate imagesets
|
||||
4. `--extension-output-path`: path where to generate generated extension
|
||||
5. `--extension-name` *(optional)* : name of class to add the extension
|
||||
5. `--extension-name` *(optional)* : name of 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`)
|
||||
7. `--static-members` *(optional)*: generate static properties or not
|
||||
|
||||
> ⚠️ Svg images will be copied in the assets and rendered as "Original", however if those images are not rendered correctly you can force the png generation by adding the key word "png" like this: id arrow_back 15 ? png
|
||||
|
||||
## All at once
|
||||
|
||||
@ -167,6 +244,7 @@ colors:
|
||||
xcassetsPath: String
|
||||
extensionOutputPath: String
|
||||
extensionName: String?
|
||||
extensionNameUIKit: String?
|
||||
extensionSuffix: String?
|
||||
staticMembers: Bool?
|
||||
|
||||
@ -175,15 +253,18 @@ fonts:
|
||||
inputFile: String
|
||||
extensionOutputPath: String
|
||||
extensionName: String?
|
||||
extensionNameUIKit: String?
|
||||
extensionSuffix: String?
|
||||
staticMembers: Bool?
|
||||
|
||||
infoPlistPaths: [String]
|
||||
|
||||
images:
|
||||
-
|
||||
inputFile: String
|
||||
xcassetsPath: String
|
||||
extensionOutputPath: String
|
||||
extensionName: String?
|
||||
extensionNameUIKit: String?
|
||||
extensionSuffix: String?
|
||||
staticMembers: Bool?
|
||||
|
||||
@ -223,6 +304,7 @@ colors:
|
||||
xcassetsPath: String
|
||||
extensionOutputPath: String
|
||||
extensionName: String?
|
||||
extensionNameUIKit: String?
|
||||
extensionSuffix: String?
|
||||
staticMembers: Bool?
|
||||
-
|
||||
@ -231,6 +313,7 @@ colors:
|
||||
xcassetsPath: String
|
||||
extensionOutputPath: String
|
||||
extensionName: String?
|
||||
extensionNameUIKit: String?
|
||||
extensionSuffix: String?
|
||||
staticMembers: Bool?
|
||||
...
|
||||
@ -246,6 +329,67 @@ tags: []
|
||||
...
|
||||
```
|
||||
|
||||
### File architecture
|
||||
|
||||
ResgenSwift generate extension of classes. Those classes must exists in your project. You can create them yourself OR you can let ResgenSwift create them by specifying what you want. Do as follow:
|
||||
|
||||
```
|
||||
architecture:
|
||||
property: R *(required but not used)*
|
||||
classname: R
|
||||
path: ./path/to/generate
|
||||
children:
|
||||
- property: images
|
||||
classname: R2Image
|
||||
- property: strings
|
||||
classname: R2String
|
||||
- property: fonts
|
||||
classname: R2Font
|
||||
- property: images
|
||||
classname: R2Image
|
||||
- property: uikit
|
||||
classname: R2UI
|
||||
children:
|
||||
- property: images
|
||||
classname: R2UIImage
|
||||
- property: fonts
|
||||
classname: R2UIFont
|
||||
- property: images
|
||||
classname: R2UIImage
|
||||
```
|
||||
|
||||
This will generate a file named as the architecture classname: `R.swift`. Based on the previous architecture, it will generate:
|
||||
```
|
||||
class R {
|
||||
static let images = R2Image()
|
||||
static let strings = R2String()
|
||||
static let fonts = R2Font()
|
||||
static let images = R2Image()
|
||||
static let uikit = R2UI()
|
||||
}
|
||||
|
||||
class R2Image {}
|
||||
|
||||
class R2String {}
|
||||
|
||||
class R2Font {}
|
||||
|
||||
class R2Image {}
|
||||
|
||||
class R2UI {
|
||||
let images = R2UIImage()
|
||||
let fonts = R2UIFont()
|
||||
let images = R2UIImage()
|
||||
}
|
||||
|
||||
class R2UIImage {}
|
||||
|
||||
class R2UIFont {}
|
||||
|
||||
class R2UIImage {}
|
||||
```
|
||||
|
||||
|
||||
### Usage
|
||||
|
||||
```sh
|
||||
@ -255,12 +399,22 @@ swift run -c release ResgenSwift generate path/to/configuration.yml --project-di
|
||||
> ⚠️ Every path in `configuration.yml` will be prepended by content of `--project-directory` if they are relative path (not starting with `/`)
|
||||
|
||||
|
||||
## Installation
|
||||
## Binary usage
|
||||
|
||||
Simple run `./install.sh`. Binary will be install in `/usr/local/bin`. Then, use ResgenSwift like any other command:
|
||||
### Installation
|
||||
|
||||
Example:
|
||||
Run `make install`. Binary will be install in `/usr/local/bin`.
|
||||
|
||||
Usage:
|
||||
|
||||
```sh
|
||||
ResgenSwift generate path/to/configuration.yml --project-directory ${PROJECT_DIR}
|
||||
```
|
||||
resgen-swift generate path/to/configuration.yml --project-directory ${PROJECT_DIR}
|
||||
```
|
||||
|
||||
### Man page
|
||||
|
||||
Commands parameters and details can be find by running `resgen-swift --help`. If you prefer, a man page is also available. Run `man resgen-swift`. Man page is installed on `make install` but you can install manually by running `make create-and-install-man-file`.
|
||||
|
||||
### Uninstallation
|
||||
|
||||
To uninstall ResgenSwift: `make uninstall`.
|
||||
|
21
SampleFiles/Colors/Generated/ColorYolo+GenAllScript.swift
Normal file
21
SampleFiles/Colors/Generated/ColorYolo+GenAllScript.swift
Normal file
@ -0,0 +1,21 @@
|
||||
// Generated by ResgenSwift.Color 1.2
|
||||
|
||||
import SwiftUI
|
||||
|
||||
extension ColorYolo {
|
||||
|
||||
/// Color red is #FF0000 (light) or #FF0000 (dark)"
|
||||
var red: Color {
|
||||
Color("red")
|
||||
}
|
||||
|
||||
/// Color green_alpha_50 is #A000FF00 (light) or #A000FF00 (dark)"
|
||||
var green_alpha_50: Color {
|
||||
Color("green_alpha_50")
|
||||
}
|
||||
|
||||
/// Color blue_light_dark is #0000FF (light) or #0000AA (dark)"
|
||||
var blue_light_dark: Color {
|
||||
Color("blue_light_dark")
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
// Generated from ColorToolCore at 2021-12-20 15:16:18 +0000
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UIColor {
|
||||
|
||||
/// Color red is #FF0000 (light) or #FF0000 (dark)"
|
||||
static var red: UIColor {
|
||||
UIColor(named: "red")!
|
||||
}
|
||||
|
||||
/// Color green_alpha_50 is #A000FF00 (light) or #A000FF00 (dark)"
|
||||
static var green_alpha_50: UIColor {
|
||||
UIColor(named: "green_alpha_50")!
|
||||
}
|
||||
|
||||
/// Color blue_light_dark is #0000FF (light) or #0000AA (dark)"
|
||||
static var blue_light_dark: UIColor {
|
||||
UIColor(named: "blue_light_dark")!
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
// Generated from ColorToolCore at 2022-02-14 09:30:19 +0000
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UIColor {
|
||||
|
||||
/// Color red is #FF0000 (light) or #FF0000 (dark)"
|
||||
static var red: UIColor {
|
||||
UIColor(named: "red")!
|
||||
}
|
||||
|
||||
/// Color green_alpha_50 is #A000FF00 (light) or #A000FF00 (dark)"
|
||||
static var green_alpha_50: UIColor {
|
||||
UIColor(named: "green_alpha_50")!
|
||||
}
|
||||
|
||||
/// Color blue_light_dark is #0000FF (light) or #0000AA (dark)"
|
||||
static var blue_light_dark: UIColor {
|
||||
UIColor(named: "blue_light_dark")!
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
// Generated from ColorToolCore at 2021-12-20 15:17:10 +0000
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UIColor {
|
||||
|
||||
/// Color red is #FF0000 (light) or #FF0000 (dark)"
|
||||
static var red: UIColor {
|
||||
UIColor(named: "red")!
|
||||
}
|
||||
|
||||
/// Color green_alpha_50 is #A000FF00 (light) or #A000FF00 (dark)"
|
||||
static var green_alpha_50: UIColor {
|
||||
UIColor(named: "green_alpha_50")!
|
||||
}
|
||||
|
||||
/// Color blue_light_dark is #0000FF (light) or #0000AA (dark)"
|
||||
static var blue_light_dark: UIColor {
|
||||
UIColor(named: "blue_light_dark")!
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
// Generated by ResgenSwift.ColorTool 1.0.0
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UIColor {
|
||||
|
||||
/// Color red is #FF0000 (light) or #FF0000 (dark)"
|
||||
static var red: UIColor {
|
||||
UIColor(named: "red")!
|
||||
}
|
||||
|
||||
/// Color green_alpha_50 is #A000FF00 (light) or #A000FF00 (dark)"
|
||||
static var green_alpha_50: UIColor {
|
||||
UIColor(named: "green_alpha_50")!
|
||||
}
|
||||
|
||||
/// Color blue_light_dark is #0000FF (light) or #0000AA (dark)"
|
||||
static var blue_light_dark: UIColor {
|
||||
UIColor(named: "blue_light_dark")!
|
||||
}
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
// Generated from ColorToolCore at 2021-12-20 15:34:55 +0000
|
||||
// Generated by ResgenSwift.Color 1.2
|
||||
|
||||
import UIKit
|
||||
|
||||
extension R2Color {
|
||||
extension UIColorYolo {
|
||||
|
||||
/// Color red is #FF0000 (light) or #FF0000 (dark)"
|
||||
@objc var red: UIColor {
|
||||
@ -14,8 +14,8 @@ extension R2Color {
|
||||
UIColor(named: "green_alpha_50")!
|
||||
}
|
||||
|
||||
/// Color blue_light_dark is #0000FF (light) or #0000FF (dark)"
|
||||
/// Color blue_light_dark is #0000FF (light) or #0000AA (dark)"
|
||||
@objc var blue_light_dark: UIColor {
|
||||
UIColor(named: "blue_light_dark")!
|
||||
}
|
||||
}
|
||||
}
|
61
SampleFiles/Fonts/Generated/FontYolo+GenAllScript.swift
Normal file
61
SampleFiles/Fonts/Generated/FontYolo+GenAllScript.swift
Normal file
@ -0,0 +1,61 @@
|
||||
// Generated by ResgenSwift.Fonts 1.2
|
||||
|
||||
import SwiftUI
|
||||
|
||||
extension FontYolo {
|
||||
|
||||
enum FontName: String {
|
||||
case LatoItalic = "Lato-Italic"
|
||||
case LatoLightItalic = "Lato-LightItalic"
|
||||
case LatoHairline = "Lato-Hairline"
|
||||
case LatoBold = "Lato-Bold"
|
||||
case LatoBlack = "Lato-Black"
|
||||
case LatoRegular = "Lato-Regular"
|
||||
case LatoBlackItalic = "Lato-BlackItalic"
|
||||
case LatoBoldItalic = "Lato-BoldItalic"
|
||||
case LatoLight = "Lato-Light"
|
||||
case LatoHairlineItalic = "Lato-HairlineItalic"
|
||||
}
|
||||
|
||||
// MARK: - Getter
|
||||
|
||||
func LatoItalic(withSize size: CGFloat) -> Font {
|
||||
Font.custom(FontName.LatoItalic.rawValue, size: size)
|
||||
}
|
||||
|
||||
func LatoLightItalic(withSize size: CGFloat) -> Font {
|
||||
Font.custom(FontName.LatoLightItalic.rawValue, size: size)
|
||||
}
|
||||
|
||||
func LatoHairline(withSize size: CGFloat) -> Font {
|
||||
Font.custom(FontName.LatoHairline.rawValue, size: size)
|
||||
}
|
||||
|
||||
func LatoBold(withSize size: CGFloat) -> Font {
|
||||
Font.custom(FontName.LatoBold.rawValue, size: size)
|
||||
}
|
||||
|
||||
func LatoBlack(withSize size: CGFloat) -> Font {
|
||||
Font.custom(FontName.LatoBlack.rawValue, size: size)
|
||||
}
|
||||
|
||||
func LatoRegular(withSize size: CGFloat) -> Font {
|
||||
Font.custom(FontName.LatoRegular.rawValue, size: size)
|
||||
}
|
||||
|
||||
func LatoBlackItalic(withSize size: CGFloat) -> Font {
|
||||
Font.custom(FontName.LatoBlackItalic.rawValue, size: size)
|
||||
}
|
||||
|
||||
func LatoBoldItalic(withSize size: CGFloat) -> Font {
|
||||
Font.custom(FontName.LatoBoldItalic.rawValue, size: size)
|
||||
}
|
||||
|
||||
func LatoLight(withSize size: CGFloat) -> Font {
|
||||
Font.custom(FontName.LatoLight.rawValue, size: size)
|
||||
}
|
||||
|
||||
func LatoHairlineItalic(withSize size: CGFloat) -> Font {
|
||||
Font.custom(FontName.LatoHairlineItalic.rawValue, size: size)
|
||||
}
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
// Generated from FontToolCore
|
||||
|
||||
import UIKit
|
||||
|
||||
extension R2Font {
|
||||
|
||||
enum FontName: String {
|
||||
case LatoItalic = "Lato-Italic"
|
||||
case LatoLightItalic = "Lato-LightItalic"
|
||||
case LatoHairline = "Lato-Hairline"
|
||||
case LatoBold = "Lato-Bold"
|
||||
case LatoBlack = "Lato-Black"
|
||||
case LatoRegular = "Lato-Regular"
|
||||
case LatoBlackItalic = "Lato-BlackItalic"
|
||||
case LatoBoldItalic = "Lato-BoldItalic"
|
||||
case LatoLight = "Lato-Light"
|
||||
case LatoHairlineItalic = "Lato-HairlineItalic"
|
||||
}
|
||||
|
||||
// MARK: - Getter
|
||||
|
||||
func LatoItalic(withSize size: CGFloat) -> UIFont {
|
||||
UIFont(name: FontName.LatoItalic.rawValue, size: size)!
|
||||
}
|
||||
|
||||
func LatoLightItalic(withSize size: CGFloat) -> UIFont {
|
||||
UIFont(name: FontName.LatoLightItalic.rawValue, size: size)!
|
||||
}
|
||||
|
||||
func LatoHairline(withSize size: CGFloat) -> UIFont {
|
||||
UIFont(name: FontName.LatoHairline.rawValue, size: size)!
|
||||
}
|
||||
|
||||
func LatoBold(withSize size: CGFloat) -> UIFont {
|
||||
UIFont(name: FontName.LatoBold.rawValue, size: size)!
|
||||
}
|
||||
|
||||
func LatoBlack(withSize size: CGFloat) -> UIFont {
|
||||
UIFont(name: FontName.LatoBlack.rawValue, size: size)!
|
||||
}
|
||||
|
||||
func LatoRegular(withSize size: CGFloat) -> UIFont {
|
||||
UIFont(name: FontName.LatoRegular.rawValue, size: size)!
|
||||
}
|
||||
|
||||
func LatoBlackItalic(withSize size: CGFloat) -> UIFont {
|
||||
UIFont(name: FontName.LatoBlackItalic.rawValue, size: size)!
|
||||
}
|
||||
|
||||
func LatoBoldItalic(withSize size: CGFloat) -> UIFont {
|
||||
UIFont(name: FontName.LatoBoldItalic.rawValue, size: size)!
|
||||
}
|
||||
|
||||
func LatoLight(withSize size: CGFloat) -> UIFont {
|
||||
UIFont(name: FontName.LatoLight.rawValue, size: size)!
|
||||
}
|
||||
|
||||
func LatoHairlineItalic(withSize size: CGFloat) -> UIFont {
|
||||
UIFont(name: FontName.LatoHairlineItalic.rawValue, size: size)!
|
||||
}
|
||||
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
// Generated from FontToolCore
|
||||
|
||||
import UIKit
|
||||
|
||||
extension R2Font {
|
||||
|
||||
enum FontName: String {
|
||||
case LatoItalic = "Lato-Italic"
|
||||
case LatoLightItalic = "Lato-LightItalic"
|
||||
case LatoHairline = "Lato-Hairline"
|
||||
case LatoBold = "Lato-Bold"
|
||||
case LatoBlack = "Lato-Black"
|
||||
case LatoRegular = "Lato-Regular"
|
||||
case LatoBlackItalic = "Lato-BlackItalic"
|
||||
case LatoBoldItalic = "Lato-BoldItalic"
|
||||
case LatoLight = "Lato-Light"
|
||||
case LatoHairlineItalic = "Lato-HairlineItalic"
|
||||
}
|
||||
|
||||
// MARK: - Getter
|
||||
|
||||
func LatoItalic(withSize size: CGFloat) -> UIFont {
|
||||
UIFont(name: FontName.LatoItalic.rawValue, size: size)!
|
||||
}
|
||||
|
||||
func LatoLightItalic(withSize size: CGFloat) -> UIFont {
|
||||
UIFont(name: FontName.LatoLightItalic.rawValue, size: size)!
|
||||
}
|
||||
|
||||
func LatoHairline(withSize size: CGFloat) -> UIFont {
|
||||
UIFont(name: FontName.LatoHairline.rawValue, size: size)!
|
||||
}
|
||||
|
||||
func LatoBold(withSize size: CGFloat) -> UIFont {
|
||||
UIFont(name: FontName.LatoBold.rawValue, size: size)!
|
||||
}
|
||||
|
||||
func LatoBlack(withSize size: CGFloat) -> UIFont {
|
||||
UIFont(name: FontName.LatoBlack.rawValue, size: size)!
|
||||
}
|
||||
|
||||
func LatoRegular(withSize size: CGFloat) -> UIFont {
|
||||
UIFont(name: FontName.LatoRegular.rawValue, size: size)!
|
||||
}
|
||||
|
||||
func LatoBlackItalic(withSize size: CGFloat) -> UIFont {
|
||||
UIFont(name: FontName.LatoBlackItalic.rawValue, size: size)!
|
||||
}
|
||||
|
||||
func LatoBoldItalic(withSize size: CGFloat) -> UIFont {
|
||||
UIFont(name: FontName.LatoBoldItalic.rawValue, size: size)!
|
||||
}
|
||||
|
||||
func LatoLight(withSize size: CGFloat) -> UIFont {
|
||||
UIFont(name: FontName.LatoLight.rawValue, size: size)!
|
||||
}
|
||||
|
||||
func LatoHairlineItalic(withSize size: CGFloat) -> UIFont {
|
||||
UIFont(name: FontName.LatoHairlineItalic.rawValue, size: size)!
|
||||
}
|
||||
|
||||
}
|
@ -1,63 +0,0 @@
|
||||
// Lato/Lato-Italic.ttf Lato/Lato-LightItalic.ttf Lato/Lato-Thin.ttf Lato/Lato-Bold.ttf Lato/Lato-Black.ttf Lato/Lato-Regular.ttf Lato/Lato-BlackItalic.ttf Lato/Lato-BoldItalic.ttf Lato/Lato-Light.ttf Lato/Lato-ThinItalic.ttf
|
||||
// Generated from FontToolCore
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UIFont {
|
||||
|
||||
enum FontName: String {
|
||||
case LatoItalic = "Lato-Italic"
|
||||
case LatoLightItalic = "Lato-LightItalic"
|
||||
case LatoHairline = "Lato-Hairline"
|
||||
case LatoBold = "Lato-Bold"
|
||||
case LatoBlack = "Lato-Black"
|
||||
case LatoRegular = "Lato-Regular"
|
||||
case LatoBlackItalic = "Lato-BlackItalic"
|
||||
case LatoBoldItalic = "Lato-BoldItalic"
|
||||
case LatoLight = "Lato-Light"
|
||||
case LatoHairlineItalic = "Lato-HairlineItalic"
|
||||
}
|
||||
|
||||
// MARK: - Getter
|
||||
|
||||
static let LatoItalic: ((_ size: CGFloat) -> UIFont) = { size in
|
||||
UIFont(name: FontName.LatoItalic.rawValue, size: size)!
|
||||
}
|
||||
|
||||
static let LatoLightItalic: ((_ size: CGFloat) -> UIFont) = { size in
|
||||
UIFont(name: FontName.LatoLightItalic.rawValue, size: size)!
|
||||
}
|
||||
|
||||
static let LatoHairline: ((_ size: CGFloat) -> UIFont) = { size in
|
||||
UIFont(name: FontName.LatoHairline.rawValue, size: size)!
|
||||
}
|
||||
|
||||
static let LatoBold: ((_ size: CGFloat) -> UIFont) = { size in
|
||||
UIFont(name: FontName.LatoBold.rawValue, size: size)!
|
||||
}
|
||||
|
||||
static let LatoBlack: ((_ size: CGFloat) -> UIFont) = { size in
|
||||
UIFont(name: FontName.LatoBlack.rawValue, size: size)!
|
||||
}
|
||||
|
||||
static let LatoRegular: ((_ size: CGFloat) -> UIFont) = { size in
|
||||
UIFont(name: FontName.LatoRegular.rawValue, size: size)!
|
||||
}
|
||||
|
||||
static let LatoBlackItalic: ((_ size: CGFloat) -> UIFont) = { size in
|
||||
UIFont(name: FontName.LatoBlackItalic.rawValue, size: size)!
|
||||
}
|
||||
|
||||
static let LatoBoldItalic: ((_ size: CGFloat) -> UIFont) = { size in
|
||||
UIFont(name: FontName.LatoBoldItalic.rawValue, size: size)!
|
||||
}
|
||||
|
||||
static let LatoLight: ((_ size: CGFloat) -> UIFont) = { size in
|
||||
UIFont(name: FontName.LatoLight.rawValue, size: size)!
|
||||
}
|
||||
|
||||
static let LatoHairlineItalic: ((_ size: CGFloat) -> UIFont) = { size in
|
||||
UIFont(name: FontName.LatoHairlineItalic.rawValue, size: size)!
|
||||
}
|
||||
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
// Generated from FontToolCore
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UIFont {
|
||||
|
||||
enum FontName: String {
|
||||
case LatoItalic = "Lato-Italic"
|
||||
case LatoLightItalic = "Lato-LightItalic"
|
||||
case LatoHairline = "Lato-Hairline"
|
||||
case LatoBold = "Lato-Bold"
|
||||
case LatoBlack = "Lato-Black"
|
||||
case LatoRegular = "Lato-Regular"
|
||||
case LatoBlackItalic = "Lato-BlackItalic"
|
||||
case LatoBoldItalic = "Lato-BoldItalic"
|
||||
case LatoLight = "Lato-Light"
|
||||
case LatoHairlineItalic = "Lato-HairlineItalic"
|
||||
}
|
||||
|
||||
// MARK: - Getter
|
||||
|
||||
static let LatoItalic: ((_ size: CGFloat) -> UIFont) = { size in
|
||||
UIFont(name: FontName.LatoItalic.rawValue, size: size)!
|
||||
}
|
||||
|
||||
static let LatoLightItalic: ((_ size: CGFloat) -> UIFont) = { size in
|
||||
UIFont(name: FontName.LatoLightItalic.rawValue, size: size)!
|
||||
}
|
||||
|
||||
static let LatoHairline: ((_ size: CGFloat) -> UIFont) = { size in
|
||||
UIFont(name: FontName.LatoHairline.rawValue, size: size)!
|
||||
}
|
||||
|
||||
static let LatoBold: ((_ size: CGFloat) -> UIFont) = { size in
|
||||
UIFont(name: FontName.LatoBold.rawValue, size: size)!
|
||||
}
|
||||
|
||||
static let LatoBlack: ((_ size: CGFloat) -> UIFont) = { size in
|
||||
UIFont(name: FontName.LatoBlack.rawValue, size: size)!
|
||||
}
|
||||
|
||||
static let LatoRegular: ((_ size: CGFloat) -> UIFont) = { size in
|
||||
UIFont(name: FontName.LatoRegular.rawValue, size: size)!
|
||||
}
|
||||
|
||||
static let LatoBlackItalic: ((_ size: CGFloat) -> UIFont) = { size in
|
||||
UIFont(name: FontName.LatoBlackItalic.rawValue, size: size)!
|
||||
}
|
||||
|
||||
static let LatoBoldItalic: ((_ size: CGFloat) -> UIFont) = { size in
|
||||
UIFont(name: FontName.LatoBoldItalic.rawValue, size: size)!
|
||||
}
|
||||
|
||||
static let LatoLight: ((_ size: CGFloat) -> UIFont) = { size in
|
||||
UIFont(name: FontName.LatoLight.rawValue, size: size)!
|
||||
}
|
||||
|
||||
static let LatoHairlineItalic: ((_ size: CGFloat) -> UIFont) = { size in
|
||||
UIFont(name: FontName.LatoHairlineItalic.rawValue, size: size)!
|
||||
}
|
||||
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
// Generated by ResgenSwift.FontTool 1.0.0
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UIFont {
|
||||
|
||||
enum FontName: String {
|
||||
case LatoItalic = "Lato-Italic"
|
||||
case LatoLightItalic = "Lato-LightItalic"
|
||||
case LatoHairline = "Lato-Hairline"
|
||||
case LatoBold = "Lato-Bold"
|
||||
case LatoBlack = "Lato-Black"
|
||||
case LatoRegular = "Lato-Regular"
|
||||
case LatoBlackItalic = "Lato-BlackItalic"
|
||||
case LatoBoldItalic = "Lato-BoldItalic"
|
||||
case LatoLight = "Lato-Light"
|
||||
case LatoHairlineItalic = "Lato-HairlineItalic"
|
||||
}
|
||||
|
||||
// MARK: - Getter
|
||||
|
||||
static let LatoItalic: ((_ size: CGFloat) -> UIFont) = { size in
|
||||
UIFont(name: FontName.LatoItalic.rawValue, size: size)!
|
||||
}
|
||||
|
||||
static let LatoLightItalic: ((_ size: CGFloat) -> UIFont) = { size in
|
||||
UIFont(name: FontName.LatoLightItalic.rawValue, size: size)!
|
||||
}
|
||||
|
||||
static let LatoHairline: ((_ size: CGFloat) -> UIFont) = { size in
|
||||
UIFont(name: FontName.LatoHairline.rawValue, size: size)!
|
||||
}
|
||||
|
||||
static let LatoBold: ((_ size: CGFloat) -> UIFont) = { size in
|
||||
UIFont(name: FontName.LatoBold.rawValue, size: size)!
|
||||
}
|
||||
|
||||
static let LatoBlack: ((_ size: CGFloat) -> UIFont) = { size in
|
||||
UIFont(name: FontName.LatoBlack.rawValue, size: size)!
|
||||
}
|
||||
|
||||
static let LatoRegular: ((_ size: CGFloat) -> UIFont) = { size in
|
||||
UIFont(name: FontName.LatoRegular.rawValue, size: size)!
|
||||
}
|
||||
|
||||
static let LatoBlackItalic: ((_ size: CGFloat) -> UIFont) = { size in
|
||||
UIFont(name: FontName.LatoBlackItalic.rawValue, size: size)!
|
||||
}
|
||||
|
||||
static let LatoBoldItalic: ((_ size: CGFloat) -> UIFont) = { size in
|
||||
UIFont(name: FontName.LatoBoldItalic.rawValue, size: size)!
|
||||
}
|
||||
|
||||
static let LatoLight: ((_ size: CGFloat) -> UIFont) = { size in
|
||||
UIFont(name: FontName.LatoLight.rawValue, size: size)!
|
||||
}
|
||||
|
||||
static let LatoHairlineItalic: ((_ size: CGFloat) -> UIFont) = { size in
|
||||
UIFont(name: FontName.LatoHairlineItalic.rawValue, size: size)!
|
||||
}
|
||||
}
|
61
SampleFiles/Fonts/Generated/UIFontYolo+GenAllScript.swift
Normal file
61
SampleFiles/Fonts/Generated/UIFontYolo+GenAllScript.swift
Normal file
@ -0,0 +1,61 @@
|
||||
// Generated by ResgenSwift.Fonts 1.2
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UIFontYolo {
|
||||
|
||||
enum FontName: String {
|
||||
case LatoItalic = "Lato-Italic"
|
||||
case LatoLightItalic = "Lato-LightItalic"
|
||||
case LatoHairline = "Lato-Hairline"
|
||||
case LatoBold = "Lato-Bold"
|
||||
case LatoBlack = "Lato-Black"
|
||||
case LatoRegular = "Lato-Regular"
|
||||
case LatoBlackItalic = "Lato-BlackItalic"
|
||||
case LatoBoldItalic = "Lato-BoldItalic"
|
||||
case LatoLight = "Lato-Light"
|
||||
case LatoHairlineItalic = "Lato-HairlineItalic"
|
||||
}
|
||||
|
||||
// MARK: - Getter
|
||||
|
||||
func LatoItalic(withSize size: CGFloat) -> UIFont {
|
||||
UIFont(name: FontName.LatoItalic.rawValue, size: size)!
|
||||
}
|
||||
|
||||
func LatoLightItalic(withSize size: CGFloat) -> UIFont {
|
||||
UIFont(name: FontName.LatoLightItalic.rawValue, size: size)!
|
||||
}
|
||||
|
||||
func LatoHairline(withSize size: CGFloat) -> UIFont {
|
||||
UIFont(name: FontName.LatoHairline.rawValue, size: size)!
|
||||
}
|
||||
|
||||
func LatoBold(withSize size: CGFloat) -> UIFont {
|
||||
UIFont(name: FontName.LatoBold.rawValue, size: size)!
|
||||
}
|
||||
|
||||
func LatoBlack(withSize size: CGFloat) -> UIFont {
|
||||
UIFont(name: FontName.LatoBlack.rawValue, size: size)!
|
||||
}
|
||||
|
||||
func LatoRegular(withSize size: CGFloat) -> UIFont {
|
||||
UIFont(name: FontName.LatoRegular.rawValue, size: size)!
|
||||
}
|
||||
|
||||
func LatoBlackItalic(withSize size: CGFloat) -> UIFont {
|
||||
UIFont(name: FontName.LatoBlackItalic.rawValue, size: size)!
|
||||
}
|
||||
|
||||
func LatoBoldItalic(withSize size: CGFloat) -> UIFont {
|
||||
UIFont(name: FontName.LatoBoldItalic.rawValue, size: size)!
|
||||
}
|
||||
|
||||
func LatoLight(withSize size: CGFloat) -> UIFont {
|
||||
UIFont(name: FontName.LatoLight.rawValue, size: size)!
|
||||
}
|
||||
|
||||
func LatoHairlineItalic(withSize size: CGFloat) -> UIFont {
|
||||
UIFont(name: FontName.LatoHairlineItalic.rawValue, size: size)!
|
||||
}
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
// Generated from FontToolCore
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UIFont {
|
||||
|
||||
enum FontName: String {
|
||||
case LatoItalic = "Lato-Italic"
|
||||
case LatoLightItalic = "Lato-LightItalic"
|
||||
case LatoHairline = "Lato-Hairline"
|
||||
case LatoBold = "Lato-Bold"
|
||||
case LatoBlack = "Lato-Black"
|
||||
case LatoRegular = "Lato-Regular"
|
||||
case LatoBlackItalic = "Lato-BlackItalic"
|
||||
case LatoBoldItalic = "Lato-BoldItalic"
|
||||
case LatoLight = "Lato-Light"
|
||||
case LatoHairlineItalic = "Lato-HairlineItalic"
|
||||
}
|
||||
|
||||
// MARK: - Getter
|
||||
|
||||
static let LatoItalic: ((_ size: CGFloat) -> UIFont) = { size in
|
||||
UIFont(name: FontName.LatoItalic.rawValue, size: size)!
|
||||
}
|
||||
|
||||
static let LatoLightItalic: ((_ size: CGFloat) -> UIFont) = { size in
|
||||
UIFont(name: FontName.LatoLightItalic.rawValue, size: size)!
|
||||
}
|
||||
|
||||
static let LatoHairline: ((_ size: CGFloat) -> UIFont) = { size in
|
||||
UIFont(name: FontName.LatoHairline.rawValue, size: size)!
|
||||
}
|
||||
|
||||
static let LatoBold: ((_ size: CGFloat) -> UIFont) = { size in
|
||||
UIFont(name: FontName.LatoBold.rawValue, size: size)!
|
||||
}
|
||||
|
||||
static let LatoBlack: ((_ size: CGFloat) -> UIFont) = { size in
|
||||
UIFont(name: FontName.LatoBlack.rawValue, size: size)!
|
||||
}
|
||||
|
||||
static let LatoRegular: ((_ size: CGFloat) -> UIFont) = { size in
|
||||
UIFont(name: FontName.LatoRegular.rawValue, size: size)!
|
||||
}
|
||||
|
||||
static let LatoBlackItalic: ((_ size: CGFloat) -> UIFont) = { size in
|
||||
UIFont(name: FontName.LatoBlackItalic.rawValue, size: size)!
|
||||
}
|
||||
|
||||
static let LatoBoldItalic: ((_ size: CGFloat) -> UIFont) = { size in
|
||||
UIFont(name: FontName.LatoBoldItalic.rawValue, size: size)!
|
||||
}
|
||||
|
||||
static let LatoLight: ((_ size: CGFloat) -> UIFont) = { size in
|
||||
UIFont(name: FontName.LatoLight.rawValue, size: size)!
|
||||
}
|
||||
|
||||
static let LatoHairlineItalic: ((_ size: CGFloat) -> UIFont) = { size in
|
||||
UIFont(name: FontName.LatoHairlineItalic.rawValue, size: size)!
|
||||
}
|
||||
|
||||
}
|
21
SampleFiles/Fonts/Generated/test.plist
Normal file
21
SampleFiles/Fonts/Generated/test.plist
Normal file
@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NewArr</key>
|
||||
<array/>
|
||||
<key>UIAppFonts</key>
|
||||
<array>
|
||||
<string>Lato-Italic</string>
|
||||
<string>Lato-LightItalic</string>
|
||||
<string>Lato-Hairline</string>
|
||||
<string>Lato-Bold</string>
|
||||
<string>Lato-Black</string>
|
||||
<string>Lato-Regular</string>
|
||||
<string>Lato-BlackItalic</string>
|
||||
<string>Lato-BoldItalic</string>
|
||||
<string>Lato-Light</string>
|
||||
<string>Lato-HairlineItalic</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
21
SampleFiles/Fonts/Generated/test2.plist
Normal file
21
SampleFiles/Fonts/Generated/test2.plist
Normal file
@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NewArr</key>
|
||||
<array/>
|
||||
<key>UIAppFonts</key>
|
||||
<array>
|
||||
<string>Lato-Italic</string>
|
||||
<string>Lato-LightItalic</string>
|
||||
<string>Lato-Hairline</string>
|
||||
<string>Lato-Bold</string>
|
||||
<string>Lato-Black</string>
|
||||
<string>Lato-Regular</string>
|
||||
<string>Lato-BlackItalic</string>
|
||||
<string>Lato-BoldItalic</string>
|
||||
<string>Lato-Light</string>
|
||||
<string>Lato-HairlineItalic</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
31
SampleFiles/Images/Generated/ImageYolo+GenAllScript.swift
Normal file
31
SampleFiles/Images/Generated/ImageYolo+GenAllScript.swift
Normal file
@ -0,0 +1,31 @@
|
||||
// Generated by ResgenSwift.Images 1.2
|
||||
// Images from sampleImages
|
||||
|
||||
import SwiftUI
|
||||
|
||||
extension ImageYolo {
|
||||
|
||||
var article_notification_pull_detail: Image {
|
||||
Image("article_notification_pull_detail")
|
||||
}
|
||||
|
||||
var article_notification_pull: Image {
|
||||
Image("article_notification_pull")
|
||||
}
|
||||
|
||||
var new_article: Image {
|
||||
Image("new_article")
|
||||
}
|
||||
|
||||
var welcome_background: Image {
|
||||
Image("welcome_background")
|
||||
}
|
||||
|
||||
var article_trash: Image {
|
||||
Image("article_trash")
|
||||
}
|
||||
|
||||
var ic_close_article: Image {
|
||||
Image("ic_close_article")
|
||||
}
|
||||
}
|
@ -1,32 +1,31 @@
|
||||
// Generated by ResgenSwift.Imagium 1.0.0
|
||||
// Generated by ResgenSwift.Images 1.2
|
||||
// Images from sampleImages
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UIImage {
|
||||
|
||||
static var article_notification_pull_detail: UIImage {
|
||||
var article_notification_pull_detail: UIImage {
|
||||
UIImage(named: "article_notification_pull_detail")!
|
||||
}
|
||||
|
||||
static var article_notification_pull: UIImage {
|
||||
|
||||
var article_notification_pull: UIImage {
|
||||
UIImage(named: "article_notification_pull")!
|
||||
}
|
||||
|
||||
static var new_article: UIImage {
|
||||
|
||||
var new_article: UIImage {
|
||||
UIImage(named: "new_article")!
|
||||
}
|
||||
|
||||
static var welcome_background: UIImage {
|
||||
|
||||
var welcome_background: UIImage {
|
||||
UIImage(named: "welcome_background")!
|
||||
}
|
||||
|
||||
static var article_trash: UIImage {
|
||||
|
||||
var article_trash: UIImage {
|
||||
UIImage(named: "article_trash")!
|
||||
}
|
||||
|
||||
static var ic_close_article: UIImage {
|
||||
|
||||
var ic_close_article: UIImage {
|
||||
UIImage(named: "ic_close_article")!
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -1,32 +0,0 @@
|
||||
// Generated from Imagium at 2022-02-14 09:30:23 +0000
|
||||
// Images from sampleImages
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UIImage {
|
||||
|
||||
static var article_notification_pull_detail: UIImage {
|
||||
UIImage(named: "article_notification_pull_detail")!
|
||||
}
|
||||
|
||||
static var article_notification_pull: UIImage {
|
||||
UIImage(named: "article_notification_pull")!
|
||||
}
|
||||
|
||||
static var new_article: UIImage {
|
||||
UIImage(named: "new_article")!
|
||||
}
|
||||
|
||||
static var welcome_background: UIImage {
|
||||
UIImage(named: "welcome_background")!
|
||||
}
|
||||
|
||||
static var article_trash: UIImage {
|
||||
UIImage(named: "article_trash")!
|
||||
}
|
||||
|
||||
static var ic_close_article: UIImage {
|
||||
UIImage(named: "ic_close_article")!
|
||||
}
|
||||
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
// Generated from StringToolCore at 2022-01-10 08:27:11 +0000
|
||||
|
||||
import UIKit
|
||||
|
||||
fileprivate let kStringsFileName = "sampleStrings"
|
||||
|
||||
extension MyString {
|
||||
|
||||
// MARK: - Webservice
|
||||
|
||||
/// Translation in en :
|
||||
/// en
|
||||
var param_lang: String {
|
||||
NSLocalizedString("param_lang", tableName: kStringsFileName, bundle: Bundle.main, value: "en", comment: "")
|
||||
}
|
||||
|
||||
// MARK: - Generic
|
||||
|
||||
/// Translation in en :
|
||||
/// Back
|
||||
var generic_back: String {
|
||||
NSLocalizedString("generic_back", tableName: kStringsFileName, bundle: Bundle.main, value: "Back", comment: "")
|
||||
}
|
||||
|
||||
/// Translation in en :
|
||||
/// Loading data...
|
||||
var generic_loading_data: String {
|
||||
NSLocalizedString("generic_loading_data", tableName: kStringsFileName, bundle: Bundle.main, value: "Loading data...", comment: "")
|
||||
}
|
||||
|
||||
/// Translation in en :
|
||||
/// Welcome %@ !
|
||||
var generic_welcome_firstname_format: String {
|
||||
NSLocalizedString("generic_welcome_firstname_format", tableName: kStringsFileName, bundle: Bundle.main, value: "Welcome %@ !", comment: "")
|
||||
}
|
||||
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
// Generated by ResgenSwift.Strings.Stringium 1.0.0
|
||||
// Generated by ResgenSwift.Strings.Stringium 1.2
|
||||
|
||||
import UIKit
|
||||
|
||||
@ -6,60 +6,79 @@ fileprivate let kStringsFileName = "sampleStrings"
|
||||
|
||||
extension String {
|
||||
|
||||
// MARK: - Webservice
|
||||
enum KeyGenAllScript: String {
|
||||
case param_lang = "param_lang"
|
||||
case generic_back = "generic_back"
|
||||
case generic_loading_data = "generic_loading_data"
|
||||
case generic_welcome_firstname_format = "generic_welcome_firstname_format"
|
||||
case test_equal_symbol = "test_equal_symbol"
|
||||
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
|
||||
|
||||
/// Translation in en :
|
||||
/// en
|
||||
static var param_lang: String {
|
||||
var param_lang: String {
|
||||
NSLocalizedString("param_lang", tableName: kStringsFileName, bundle: Bundle.main, value: "en", comment: "")
|
||||
}
|
||||
|
||||
// MARK: - Generic
|
||||
// MARK: - Generic
|
||||
|
||||
/// Translation in en :
|
||||
/// Back
|
||||
static var generic_back: String {
|
||||
var generic_back: String {
|
||||
NSLocalizedString("generic_back", tableName: kStringsFileName, bundle: Bundle.main, value: "Back", comment: "")
|
||||
}
|
||||
|
||||
/// Translation in en :
|
||||
/// Loading data...
|
||||
static var generic_loading_data: String {
|
||||
var generic_loading_data: String {
|
||||
NSLocalizedString("generic_loading_data", tableName: kStringsFileName, bundle: Bundle.main, value: "Loading data...", comment: "")
|
||||
}
|
||||
|
||||
/// Translation in en :
|
||||
/// Welcome \"%@\" !
|
||||
static var generic_welcome_firstname_format: String {
|
||||
var generic_welcome_firstname_format: String {
|
||||
NSLocalizedString("generic_welcome_firstname_format", tableName: kStringsFileName, bundle: Bundle.main, value: "Welcome \"%@\" !", comment: "")
|
||||
}
|
||||
|
||||
/// Translation in en :
|
||||
/// Welcome \"%@\" !
|
||||
static func generic_welcome_firstname_format(arg0: String) -> String {
|
||||
String(format: Self.generic_welcome_firstname_format, arg0)
|
||||
func generic_welcome_firstname_format(arg0: String) -> String {
|
||||
String(format: self.generic_welcome_firstname_format, arg0)
|
||||
}
|
||||
|
||||
// MARK: - EqualSymbol
|
||||
// MARK: - EqualSymbol
|
||||
|
||||
/// Translation in en :
|
||||
/// 1€ = 1 point !
|
||||
static var test_equal_symbol: String {
|
||||
var test_equal_symbol: String {
|
||||
NSLocalizedString("test_equal_symbol", tableName: kStringsFileName, bundle: Bundle.main, value: "1€ = 1 point !", comment: "")
|
||||
}
|
||||
|
||||
// MARK: - Placeholders
|
||||
// MARK: - Placeholders
|
||||
|
||||
/// Translation in en :
|
||||
/// You %%: %2$@ %1$@ Age: %3$d
|
||||
static var placeholders_test_one: String {
|
||||
var placeholders_test_one: String {
|
||||
NSLocalizedString("placeholders_test_one", tableName: kStringsFileName, bundle: Bundle.main, value: "You %%: %2$@ %1$@ Age: %3$d", comment: "")
|
||||
}
|
||||
|
||||
/// Translation in en :
|
||||
/// You %%: %2$@ %1$@ Age: %3$d
|
||||
static func placeholders_test_one(arg0: String, arg1: String, arg2: Int) -> String {
|
||||
String(format: Self.placeholders_test_one, arg0, arg1, arg2)
|
||||
func placeholders_test_one(arg0: String, arg1: String, arg2: Int) -> String {
|
||||
String(format: self.placeholders_test_one, arg0, arg1, arg2)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -1,57 +0,0 @@
|
||||
// Generated from Strings-Stringium at 2022-03-07 11:02:15 +0000
|
||||
|
||||
import UIKit
|
||||
|
||||
fileprivate let kStringsFileName = "sampleStrings"
|
||||
|
||||
extension String {
|
||||
|
||||
// MARK: - Webservice
|
||||
|
||||
/// Translation in en :
|
||||
/// en
|
||||
static var param_lang: String {
|
||||
NSLocalizedString("param_lang", tableName: kStringsFileName, bundle: Bundle.main, value: "en", comment: "")
|
||||
}
|
||||
|
||||
// MARK: - Generic
|
||||
|
||||
/// Translation in en :
|
||||
/// Back
|
||||
static var generic_back: String {
|
||||
NSLocalizedString("generic_back", tableName: kStringsFileName, bundle: Bundle.main, value: "Back", comment: "")
|
||||
}
|
||||
|
||||
/// Translation in en :
|
||||
/// Loading data...
|
||||
static var generic_loading_data: String {
|
||||
NSLocalizedString("generic_loading_data", tableName: kStringsFileName, bundle: Bundle.main, value: "Loading data...", comment: "")
|
||||
}
|
||||
|
||||
/// Translation in en :
|
||||
/// Welcome \"%@\" !
|
||||
static var generic_welcome_firstname_format: String {
|
||||
NSLocalizedString("generic_welcome_firstname_format", tableName: kStringsFileName, bundle: Bundle.main, value: "Welcome \"%@\" !", comment: "")
|
||||
}
|
||||
|
||||
/// Translation in en :
|
||||
/// Welcome \"%@\" !
|
||||
static func generic_welcome_firstname_format(arg0: String) -> String {
|
||||
String(format: Self.generic_welcome_firstname_format, arg0)
|
||||
}
|
||||
|
||||
// MARK: - Placeholders
|
||||
|
||||
/// Translation in en :
|
||||
/// You %%: %2$@ %1$@ Age: %3$d
|
||||
static var placeholders_test_one: String {
|
||||
NSLocalizedString("placeholders_test_one", tableName: kStringsFileName, bundle: Bundle.main, value: "You %%: %2$@ %1$@ Age: %3$d", comment: "")
|
||||
}
|
||||
|
||||
/// Translation in en :
|
||||
/// You %%: %2$@ %1$@ Age: %3$d
|
||||
static func placeholders_test_one(arg0: String, arg1: String, arg2: Int) -> String {
|
||||
String(format: Self.placeholders_test_one, arg0, arg1, arg2)
|
||||
}
|
||||
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
// Generated from StringToolCore at 2022-01-10 08:39:52 +0000
|
||||
|
||||
import UIKit
|
||||
|
||||
fileprivate let kStringsFileName = "sampleStrings"
|
||||
|
||||
extension String {
|
||||
|
||||
// MARK: - Webservice
|
||||
|
||||
/// Translation in en :
|
||||
/// en
|
||||
static var param_lang: String {
|
||||
NSLocalizedString("param_lang", tableName: kStringsFileName, bundle: Bundle.main, value: "en", comment: "")
|
||||
}
|
||||
|
||||
// MARK: - Generic
|
||||
|
||||
/// Translation in en :
|
||||
/// Back
|
||||
static var generic_back: String {
|
||||
NSLocalizedString("generic_back", tableName: kStringsFileName, bundle: Bundle.main, value: "Back", comment: "")
|
||||
}
|
||||
|
||||
/// Translation in en :
|
||||
/// Loading data...
|
||||
static var generic_loading_data: String {
|
||||
NSLocalizedString("generic_loading_data", tableName: kStringsFileName, bundle: Bundle.main, value: "Loading data...", comment: "")
|
||||
}
|
||||
|
||||
/// Translation in en :
|
||||
/// Welcome \"%@\" !
|
||||
static var generic_welcome_firstname_format: String {
|
||||
NSLocalizedString("generic_welcome_firstname_format", tableName: kStringsFileName, bundle: Bundle.main, value: "Welcome \"%@\" !", comment: "")
|
||||
}
|
||||
|
||||
}
|
@ -1,57 +0,0 @@
|
||||
// Generated from Strings-Stringium at 2022-03-07 11:00:52 +0000
|
||||
|
||||
import UIKit
|
||||
|
||||
fileprivate let kStringsFileName = "sampleStrings"
|
||||
|
||||
extension StringTest {
|
||||
|
||||
// MARK: - Webservice
|
||||
|
||||
/// Translation in en :
|
||||
/// en
|
||||
var param_lang: String {
|
||||
NSLocalizedString("param_lang", tableName: kStringsFileName, bundle: Bundle.main, value: "en", comment: "")
|
||||
}
|
||||
|
||||
// MARK: - Generic
|
||||
|
||||
/// Translation in en :
|
||||
/// Back
|
||||
var generic_back: String {
|
||||
NSLocalizedString("generic_back", tableName: kStringsFileName, bundle: Bundle.main, value: "Back", comment: "")
|
||||
}
|
||||
|
||||
/// Translation in en :
|
||||
/// Loading data...
|
||||
var generic_loading_data: String {
|
||||
NSLocalizedString("generic_loading_data", tableName: kStringsFileName, bundle: Bundle.main, value: "Loading data...", comment: "")
|
||||
}
|
||||
|
||||
/// Translation in en :
|
||||
/// Welcome \"%@\" !
|
||||
var generic_welcome_firstname_format: String {
|
||||
NSLocalizedString("generic_welcome_firstname_format", tableName: kStringsFileName, bundle: Bundle.main, value: "Welcome \"%@\" !", comment: "")
|
||||
}
|
||||
|
||||
/// Translation in en :
|
||||
/// Welcome \"%@\" !
|
||||
func generic_welcome_firstname_format(arg0: String) -> String {
|
||||
String(format: self.generic_welcome_firstname_format, arg0)
|
||||
}
|
||||
|
||||
// MARK: - Placeholders
|
||||
|
||||
/// Translation in en :
|
||||
/// You %%: %2$@ %1$@ Age: %3$d
|
||||
var placeholders_test_one: String {
|
||||
NSLocalizedString("placeholders_test_one", tableName: kStringsFileName, bundle: Bundle.main, value: "You %%: %2$@ %1$@ Age: %3$d", comment: "")
|
||||
}
|
||||
|
||||
/// Translation in en :
|
||||
/// You %%: %2$@ %1$@ Age: %3$d
|
||||
func placeholders_test_one(arg0: String, arg1: String, arg2: Int) -> String {
|
||||
String(format: self.placeholders_test_one, arg0, arg1, arg2)
|
||||
}
|
||||
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Apple Strings File
|
||||
* Generated by ResgenSwift 1.0.0
|
||||
* Generated by ResgenSwift 1.2
|
||||
* Language: en-us
|
||||
*/
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Apple Strings File
|
||||
* Generated by ResgenSwift 1.0.0
|
||||
* Generated by ResgenSwift 1.2
|
||||
* Language: en
|
||||
*/
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Apple Strings File
|
||||
* Generated by ResgenSwift 1.0.0
|
||||
* Generated by ResgenSwift 1.2
|
||||
* Language: fr
|
||||
*/
|
||||
|
||||
|
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/"
|
||||
)
|
||||
}
|
||||
}
|
@ -1,23 +1,20 @@
|
||||
// Generated by ResgenSwift.Strings.Tags 1.0.0
|
||||
|
||||
// typelias Tags = String
|
||||
// Generated by ResgenSwift.Strings.Tags 1.2
|
||||
|
||||
import UIKit
|
||||
|
||||
extension Tags {
|
||||
|
||||
// MARK: - ScreenTag
|
||||
// MARK: - ScreenTag
|
||||
|
||||
/// Translation in ium :
|
||||
/// Ecran un
|
||||
static var screen_one: String {
|
||||
var screen_one: String {
|
||||
"Ecran un"
|
||||
}
|
||||
|
||||
/// Translation in ium :
|
||||
/// Ecran deux
|
||||
static var screen_two: String {
|
||||
var screen_two: String {
|
||||
"Ecran deux"
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -1,23 +0,0 @@
|
||||
// Generated from Strings-Tags at 2022-02-14 09:30:20 +0000
|
||||
|
||||
// typelias Tags = String
|
||||
|
||||
import UIKit
|
||||
|
||||
extension Tags {
|
||||
|
||||
// MARK: - ScreenTag
|
||||
|
||||
/// Translation in ium :
|
||||
/// Ecran un
|
||||
static var screen_one: String {
|
||||
"Ecran un"
|
||||
}
|
||||
|
||||
/// Translation in ium :
|
||||
/// Ecran deux
|
||||
static var screen_two: String {
|
||||
"Ecran deux"
|
||||
}
|
||||
|
||||
}
|
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,56 +2,69 @@
|
||||
|
||||
FORCE_FLAG="$1"
|
||||
|
||||
# Font
|
||||
swift run -c release FontTool $FORCE_FLAG "./Fonts/sampleFontsAll.txt" \
|
||||
--extension-output-path "./Fonts/Generated" \
|
||||
--extension-name "UIFont" \
|
||||
--extension-suffix "GenAllScript"
|
||||
## Font
|
||||
#swift run -c release ResgenSwift fonts $FORCE_FLAG "./Fonts/sampleFontsAll.txt" \
|
||||
# --extension-output-path "./Fonts/Generated" \
|
||||
# --extension-name "FontYolo" \
|
||||
# --extension-name-ui-kit "UIFontYolo" \
|
||||
# --extension-suffix "GenAllScript" \
|
||||
# --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
|
||||
swift run -c release ColorTool $FORCE_FLAG "./Colors/sampleColors1.txt" \
|
||||
--style all \
|
||||
--xcassets-path "./Colors/colors.xcassets" \
|
||||
--extension-output-path "./Colors/Generated/" \
|
||||
--extension-name "UIColor" \
|
||||
--extension-suffix "GenAllScript"
|
||||
## Strings
|
||||
#swift run -c release ResgenSwift strings stringium $FORCE_FLAG "./Strings/sampleStrings.txt" \
|
||||
# --output-path "./Strings/Generated" \
|
||||
# --langs "fr en en-us" \
|
||||
# --default-lang "en" \
|
||||
# --extension-output-path "./Strings/Generated" \
|
||||
# --extension-name "String" \
|
||||
# --extension-suffix "GenAllScript"
|
||||
|
||||
echo "\n-------------------------\n"
|
||||
#echo "\n-------------------------\n"
|
||||
|
||||
# Twine
|
||||
swift run -c release Strings twine $FORCE_FLAG "./Twine/sampleStrings.txt" \
|
||||
--output-path "./Twine/Generated" \
|
||||
--langs "fr en en-us" \
|
||||
--default-lang "en" \
|
||||
--extension-output-path "./Twine/Generated"
|
||||
## Tags
|
||||
#swift run -c release ResgenSwift strings tags $FORCE_FLAG "./Tags/sampleTags.txt" \
|
||||
# --lang "ium" \
|
||||
# --extension-output-path "./Tags/Generated" \
|
||||
# --extension-name "Tags" \
|
||||
# --extension-suffix "GenAllScript"
|
||||
|
||||
echo "\n-------------------------\n"
|
||||
#echo "\n-------------------------\n"
|
||||
|
||||
# Strings
|
||||
swift run -c release Strings stringium $FORCE_FLAG "./Strings/sampleStrings.txt" \
|
||||
--output-path "./Strings/Generated" \
|
||||
--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 Strings tags $FORCE_FLAG "./Tags/sampleTags.txt" \
|
||||
--lang "ium" \
|
||||
# Analytics
|
||||
swift run -c release ResgenSwift analytics $FORCE_FLAG "./Tags/sampleTags.yml" \
|
||||
--target "matomo firebase" \
|
||||
--extension-output-path "./Tags/Generated" \
|
||||
--extension-name "Tags" \
|
||||
--extension-name "Analytics" \
|
||||
--extension-suffix "GenAllScript"
|
||||
|
||||
echo "\n-------------------------\n"
|
||||
|
||||
# Images
|
||||
swift run -c release Imagium $FORCE_FLAG "./Images/sampleImages.txt" \
|
||||
--xcassets-path "./Images/imagium.xcassets" \
|
||||
--extension-output-path "./Images/Generated" \
|
||||
--extension-name "UIImage" \
|
||||
--extension-suffix "GenAllScript"
|
||||
#echo "\n-------------------------\n"
|
||||
#
|
||||
## Images
|
||||
#swift run -c release ResgenSwift images $FORCE_FLAG "./Images/sampleImages.txt" \
|
||||
# --xcassets-path "./Images/imagium.xcassets" \
|
||||
# --extension-output-path "./Images/Generated" \
|
||||
# --extension-name "ImageYolo" \
|
||||
# --extension-name-ui-kit "UIImageYolo" \
|
||||
# --extension-suffix "GenAllScript"
|
||||
|
103
SampleFiles/resgenConfiguration.yml
Normal file
103
SampleFiles/resgenConfiguration.yml
Normal file
@ -0,0 +1,103 @@
|
||||
---
|
||||
#
|
||||
# Class architecture
|
||||
#
|
||||
architecture:
|
||||
property: R
|
||||
classname: R
|
||||
path: ./Tags
|
||||
children:
|
||||
- property: images
|
||||
classname: R2Image
|
||||
- property: strings
|
||||
classname: R2String
|
||||
- property: fonts
|
||||
classname: R2Font
|
||||
- property: images
|
||||
classname: R2Image
|
||||
- property: ui
|
||||
classname: R2UI
|
||||
children:
|
||||
- property: images
|
||||
classname: R2UIImage
|
||||
- property: fonts
|
||||
classname: R2UIFont
|
||||
- property: images
|
||||
classname: R2UIImage
|
||||
|
||||
#
|
||||
# Strings
|
||||
#
|
||||
strings:
|
||||
-
|
||||
inputFile: ./Strings/sampleStrings.txt
|
||||
outputPath: ./Strings/Generated
|
||||
langs: "fr en en-us"
|
||||
defaultLang: en
|
||||
extensionOutputPath: ./Strings/Generated
|
||||
extensionName: String
|
||||
extensionSuffix: GenAllScript
|
||||
|
||||
|
||||
#
|
||||
# Images
|
||||
#
|
||||
images:
|
||||
-
|
||||
inputFile: ./Images/sampleImages.txt
|
||||
xcassetsPath: ./Images/imagium.xcassets
|
||||
extensionOutputPath: ./Images/Generated
|
||||
extensionName: ImageYolo
|
||||
extensionNameUIKit: UIImageYolo
|
||||
extensionSuffix: GenAllScript
|
||||
|
||||
|
||||
#
|
||||
# Colors
|
||||
#
|
||||
colors:
|
||||
-
|
||||
inputFile: ./Colors/sampleColors1.txt
|
||||
style: all
|
||||
xcassetsPath: ./Colors/colors.xcassets
|
||||
extensionOutputPath: ./Colors/Generated/
|
||||
extensionName: ColorYolo
|
||||
extensionNameUIKit: UIColorYolo
|
||||
extensionSuffix: GenAllScript
|
||||
|
||||
|
||||
#
|
||||
# Tags
|
||||
#
|
||||
tags:
|
||||
-
|
||||
inputFile: ./Tags/sampleTags.txt
|
||||
lang: ium
|
||||
extensionOutputPath: ./Tags/Generated
|
||||
extensionName: Tags
|
||||
extensionSuffix: GenAllScript
|
||||
|
||||
|
||||
#
|
||||
# Analytics
|
||||
#
|
||||
analytics:
|
||||
-
|
||||
inputFile: ./Tags/sampleTags.yml
|
||||
target: "matomo firebase"
|
||||
extensionOutputPath: ./Tags/Generated
|
||||
extensionName: Analytics
|
||||
extensionSuffix: GenAllScript
|
||||
|
||||
|
||||
#
|
||||
# Fonts
|
||||
#
|
||||
fonts:
|
||||
-
|
||||
inputFile: ./Fonts/sampleFontsAll.txt
|
||||
extensionOutputPath: ./Fonts/Generated
|
||||
extensionName: FontYolo
|
||||
extensionNameUIKit: UIFontYolo
|
||||
extensionSuffix: GenAllScript
|
||||
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,7 +21,8 @@ struct Colors: ParsableCommand {
|
||||
// MARK: - Static
|
||||
|
||||
static let toolName = "Color"
|
||||
static let defaultExtensionName = "UIColor"
|
||||
static let defaultExtensionName = "Color"
|
||||
static let defaultExtensionNameUIKit = "UIColor"
|
||||
static let assetsColorsFolderName = "Colors"
|
||||
|
||||
// MARK: - Command options
|
||||
@ -43,7 +44,7 @@ struct Colors: ParsableCommand {
|
||||
|
||||
// Get colors to generate
|
||||
let parsedColors = ColorFileParser.parse(options.inputFile,
|
||||
colorStyle: options.colorStyle)
|
||||
colorStyle: options.style)
|
||||
// -> Time: 0.0020350217819213867 seconds
|
||||
|
||||
// Generate all colors in xcassets
|
||||
@ -55,9 +56,16 @@ struct Colors: ParsableCommand {
|
||||
ColorExtensionGenerator.writeExtensionFile(colors: parsedColors,
|
||||
staticVar: options.staticMembers,
|
||||
extensionName: options.extensionName,
|
||||
extensionFilePath: options.extensionFilePath)
|
||||
// -> Time: 0.0010340213775634766 seconds
|
||||
|
||||
extensionFilePath: options.extensionFilePath,
|
||||
isSwiftUI: true)
|
||||
|
||||
// Generate extension
|
||||
ColorExtensionGenerator.writeExtensionFile(colors: parsedColors,
|
||||
staticVar: options.staticMembers,
|
||||
extensionName: options.extensionNameUIKit,
|
||||
extensionFilePath: options.extensionFilePathUIKit,
|
||||
isSwiftUI: false)
|
||||
|
||||
print("[\(Self.toolName)] Colors generated")
|
||||
}
|
||||
|
||||
@ -69,14 +77,21 @@ struct Colors: ParsableCommand {
|
||||
// Check if input file exists
|
||||
guard fileManager.fileExists(atPath: options.inputFile) else {
|
||||
let error = ColorsToolError.fileNotExists(options.inputFile)
|
||||
print(error.localizedDescription)
|
||||
print(error.description)
|
||||
Colors.exit(withError: error)
|
||||
}
|
||||
|
||||
// Check if xcassets file exists
|
||||
guard fileManager.fileExists(atPath: options.xcassetsPath) else {
|
||||
let error = ColorsToolError.fileNotExists(options.xcassetsPath)
|
||||
print(error.localizedDescription)
|
||||
print(error.description)
|
||||
Colors.exit(withError: error)
|
||||
}
|
||||
|
||||
// Extension for UIKit and SwiftUI should have different name
|
||||
guard options.extensionName != options.extensionNameUIKit else {
|
||||
let error = ColorsToolError.extensionNamesCollision(options.extensionName)
|
||||
print(error.description)
|
||||
Colors.exit(withError: error)
|
||||
}
|
||||
|
||||
@ -102,7 +117,7 @@ struct Colors: ParsableCommand {
|
||||
try fileManager.removeItem(atPath: assetsColorPath)
|
||||
} catch {
|
||||
let error = ColorsToolError.deleteExistingColors("\(options.xcassetsPath)/Colors")
|
||||
print(error.localizedDescription)
|
||||
print(error.description)
|
||||
Colors.exit(withError: error)
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@
|
||||
import Foundation
|
||||
|
||||
enum ColorsToolError: Error {
|
||||
case extensionNamesCollision(String)
|
||||
case badFormat(String)
|
||||
case writeAsset(String)
|
||||
case createAssetFolder(String)
|
||||
@ -18,26 +19,29 @@ enum ColorsToolError: Error {
|
||||
|
||||
var description: String {
|
||||
switch self {
|
||||
case .extensionNamesCollision(let extensionName):
|
||||
return "error: [\(Fonts.toolName)] Error on extension names, extension name and SwiftUI extension name should be different (\(extensionName) is used on both)"
|
||||
|
||||
case .badFormat(let info):
|
||||
return "error:[\(Colors.toolName)] Bad line format: \(info). Accepted format are: colorName=\"#RGB/#ARGB\"; colorName \"#RGB/#ARGB\"; colorName \"#RGB/#ARGB\" \"#RGB/#ARGB\""
|
||||
return "error: [\(Colors.toolName)] Bad line format: \(info). Accepted format are: colorName=\"#RGB/#ARGB\"; colorName \"#RGB/#ARGB\"; colorName \"#RGB/#ARGB\" \"#RGB/#ARGB\""
|
||||
|
||||
case .writeAsset(let info):
|
||||
return "error:[\(Colors.toolName)] An error occured while writing color in Xcasset: \(info)"
|
||||
return "error: [\(Colors.toolName)] An error occured while writing color in Xcasset: \(info)"
|
||||
|
||||
case .createAssetFolder(let assetsFolder):
|
||||
return "error:[\(Colors.toolName)] An error occured while creating colors folder `\(assetsFolder)`"
|
||||
return "error: [\(Colors.toolName)] An error occured while creating colors folder `\(assetsFolder)`"
|
||||
|
||||
case .writeExtension(let filename, let info):
|
||||
return "error:[\(Colors.toolName)] An error occured while writing extension in \(filename): \(info)"
|
||||
return "error: [\(Colors.toolName)] An error occured while writing extension in \(filename): \(info)"
|
||||
|
||||
case .fileNotExists(let filename):
|
||||
return "error:[\(Colors.toolName)] File \(filename) does not exists"
|
||||
return "error: [\(Colors.toolName)] File \(filename) does not exists"
|
||||
|
||||
case .badColorDefinition(let lightColor, let darkColor):
|
||||
return "error:[\(Colors.toolName)] One of these two colors has invalid synthax: -\(lightColor)- or -\(darkColor)-"
|
||||
return "error: [\(Colors.toolName)] One of these two colors has invalid synthax: -\(lightColor)- or -\(darkColor)-"
|
||||
|
||||
case .deleteExistingColors(let assetsFolder):
|
||||
return "error:[\(Colors.toolName)] An error occured while deleting colors folder `\(assetsFolder)`"
|
||||
return "error: [\(Colors.toolName)] An error occured while deleting colors folder `\(assetsFolder)`"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ struct ColorsToolOptions: ParsableArguments {
|
||||
var inputFile: String
|
||||
|
||||
@Option(help: "Color style to generate: light for light colors only, or all for dark and light colors")
|
||||
fileprivate var style: String
|
||||
var style: ColorStyle
|
||||
|
||||
@Option(help: "Path of xcassets where to generate colors", transform: { $0.replaceTiltWithHomeDirectoryPath() })
|
||||
var xcassetsPath: String
|
||||
@ -27,9 +27,12 @@ struct ColorsToolOptions: ParsableArguments {
|
||||
@Option(help: "Tell if it will generate static properties or not")
|
||||
var staticMembers: Bool = false
|
||||
|
||||
@Option(help: "Extension name. If not specified, it will generate an UIColor extension. Using default extension name will generate static property.")
|
||||
@Option(help: "Extension name. If not specified, it will generate an Color extension.")
|
||||
var extensionName: String = Colors.defaultExtensionName
|
||||
|
||||
@Option(help: "SwiftUI Extension name. If not specified, it will generate an UIColor extension.")
|
||||
var extensionNameUIKit: String = Colors.defaultExtensionNameUIKit
|
||||
|
||||
@Option(help: "Extension suffix. Ex: MyApp, it will generate {extensionName}+ColorsMyApp.swift")
|
||||
var extensionSuffix: String?
|
||||
}
|
||||
@ -37,9 +40,8 @@ struct ColorsToolOptions: ParsableArguments {
|
||||
// MARK: - Computed var
|
||||
|
||||
extension ColorsToolOptions {
|
||||
var colorStyle: ColorStyle {
|
||||
ColorStyle(rawValue: style) ?? .all
|
||||
}
|
||||
|
||||
// MARK: - SwiftUI
|
||||
|
||||
var extensionFileName: String {
|
||||
if let extensionSuffix = extensionSuffix {
|
||||
@ -51,4 +53,17 @@ extension ColorsToolOptions {
|
||||
var extensionFilePath: String {
|
||||
"\(extensionOutputPath)/\(extensionFileName)"
|
||||
}
|
||||
|
||||
// MARK: - UIKit
|
||||
|
||||
var extensionFileNameUIKit: String {
|
||||
if let extensionSuffix = extensionSuffix {
|
||||
return "\(extensionNameUIKit)+\(extensionSuffix).swift"
|
||||
}
|
||||
return "\(extensionNameUIKit).swift"
|
||||
}
|
||||
|
||||
var extensionFilePathUIKit: String {
|
||||
"\(extensionOutputPath)/\(extensionFileNameUIKit)"
|
||||
}
|
||||
}
|
||||
|
@ -13,37 +13,47 @@ struct ColorExtensionGenerator {
|
||||
let colors: [ParsedColor]
|
||||
let extensionClassname: String
|
||||
|
||||
static func writeExtensionFile(colors: [ParsedColor], staticVar: Bool, extensionName: String, extensionFilePath: String) {
|
||||
// MARK: - UIKit
|
||||
|
||||
static func writeExtensionFile(colors: [ParsedColor],
|
||||
staticVar: Bool,
|
||||
extensionName: String,
|
||||
extensionFilePath: String,
|
||||
isSwiftUI: Bool) {
|
||||
// Create extension content
|
||||
let extensionContent = Self.getExtensionContent(colors: colors,
|
||||
staticVar: staticVar,
|
||||
extensionName: extensionName)
|
||||
extensionName: extensionName,
|
||||
isSwiftUI: isSwiftUI)
|
||||
|
||||
// Write content
|
||||
let extensionFilePathURL = URL(fileURLWithPath: extensionFilePath)
|
||||
do {
|
||||
try extensionContent.write(to: extensionFilePathURL, atomically: false, encoding: .utf8)
|
||||
} catch (let error) {
|
||||
} catch let error {
|
||||
let error = ColorsToolError.writeExtension(extensionFilePath, error.localizedDescription)
|
||||
print(error.localizedDescription)
|
||||
print(error.description)
|
||||
Colors.exit(withError: error)
|
||||
}
|
||||
}
|
||||
|
||||
static func getExtensionContent(colors: [ParsedColor], staticVar: Bool, extensionName: String) -> String {
|
||||
static func getExtensionContent(colors: [ParsedColor],
|
||||
staticVar: Bool,
|
||||
extensionName: String,
|
||||
isSwiftUI: Bool) -> String {
|
||||
[
|
||||
Self.getHeader(extensionClassname: extensionName),
|
||||
Self.getProperties(for: colors, withStaticVar: staticVar),
|
||||
Self.getHeader(extensionClassname: extensionName, isSwiftUI: isSwiftUI),
|
||||
Self.getProperties(for: colors, withStaticVar: staticVar, isSwiftUI: isSwiftUI),
|
||||
Self.getFooter()
|
||||
]
|
||||
.joined(separator: "\n")
|
||||
}
|
||||
|
||||
private static func getHeader(extensionClassname: String) -> String {
|
||||
private static func getHeader(extensionClassname: String, isSwiftUI: Bool) -> String {
|
||||
"""
|
||||
// Generated by ResgenSwift.\(Colors.toolName) \(ResgenSwiftVersion)
|
||||
|
||||
import UIKit
|
||||
import \(isSwiftUI ? "SwiftUI" : "UIKit")
|
||||
|
||||
extension \(extensionClassname) {\n
|
||||
"""
|
||||
@ -52,15 +62,15 @@ struct ColorExtensionGenerator {
|
||||
private static func getFooter() -> String {
|
||||
"""
|
||||
}
|
||||
|
||||
"""
|
||||
}
|
||||
|
||||
private static func getProperties(for colors: [ParsedColor], withStaticVar staticVar: Bool) -> String {
|
||||
private static func getProperties(for colors: [ParsedColor],
|
||||
withStaticVar staticVar: Bool,
|
||||
isSwiftUI: Bool) -> String {
|
||||
colors.map {
|
||||
if staticVar {
|
||||
return $0.getColorStaticProperty()
|
||||
}
|
||||
return $0.getColorProperty()
|
||||
$0.getColorProperty(isStatic: staticVar, isSwiftUI: isSwiftUI)
|
||||
}
|
||||
.joined(separator: "\n\n")
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ struct ColorXcassetHelper {
|
||||
withIntermediateDirectories: true)
|
||||
} catch {
|
||||
let error = ColorsToolError.createAssetFolder(colorSetPath)
|
||||
print(error.localizedDescription)
|
||||
print(error.description)
|
||||
Colors.exit(withError: error)
|
||||
}
|
||||
}
|
||||
@ -38,9 +38,9 @@ struct ColorXcassetHelper {
|
||||
let contentsJsonPathURL = URL(fileURLWithPath: contentsJsonPath)
|
||||
do {
|
||||
try color.contentsJSON().write(to: contentsJsonPathURL, atomically: false, encoding: .utf8)
|
||||
} catch (let error) {
|
||||
} catch let error {
|
||||
let error = ColorsToolError.writeAsset(error.localizedDescription)
|
||||
print(error.localizedDescription)
|
||||
print(error.description)
|
||||
Colors.exit(withError: error)
|
||||
}
|
||||
}
|
||||
|
@ -6,8 +6,16 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import ArgumentParser
|
||||
|
||||
enum ColorStyle: String, Decodable {
|
||||
enum ColorStyle: String, Decodable, ExpressibleByArgument {
|
||||
case light
|
||||
case all
|
||||
|
||||
static var allValueStrings: [String] {
|
||||
[
|
||||
Self.light.rawValue,
|
||||
Self.all.rawValue
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ struct ParsedColor {
|
||||
|
||||
guard allComponents.contains(true) == false else {
|
||||
let error = ColorsToolError.badColorDefinition(light, dark)
|
||||
print(error.localizedDescription)
|
||||
print(error.description)
|
||||
Colors.exit(withError: error)
|
||||
}
|
||||
|
||||
@ -72,19 +72,20 @@ struct ParsedColor {
|
||||
"""
|
||||
}
|
||||
|
||||
func getColorProperty() -> String {
|
||||
"""
|
||||
// MARK: - UIKit
|
||||
|
||||
func getColorProperty(isStatic: Bool, isSwiftUI: Bool) -> String {
|
||||
if isSwiftUI {
|
||||
return """
|
||||
/// Color \(name) is \(light) (light) or \(dark) (dark)"
|
||||
\(isStatic ? "static " : "")var \(name): Color {
|
||||
Color("\(name)")
|
||||
}
|
||||
"""
|
||||
}
|
||||
return """
|
||||
/// Color \(name) is \(light) (light) or \(dark) (dark)"
|
||||
@objc var \(name): UIColor {
|
||||
UIColor(named: "\(name)")!
|
||||
}
|
||||
"""
|
||||
}
|
||||
|
||||
func getColorStaticProperty() -> String {
|
||||
"""
|
||||
/// Color \(name) is \(light) (light) or \(dark) (dark)"
|
||||
static var \(name): UIColor {
|
||||
\(isStatic ? "static " : "@objc ")var \(name): UIColor {
|
||||
UIColor(named: "\(name)")!
|
||||
}
|
||||
"""
|
||||
|
@ -37,7 +37,7 @@ class ColorFileParser {
|
||||
|
||||
guard colorContent.count >= 2 else {
|
||||
let error = ColorsToolError.badFormat(colorLine)
|
||||
print(error.localizedDescription)
|
||||
print(error.description)
|
||||
Colors.exit(withError: error)
|
||||
}
|
||||
|
||||
|
@ -21,16 +21,25 @@ struct FontsOptions: ParsableArguments {
|
||||
@Option(help: "Tell if it will generate static properties or methods")
|
||||
var staticMembers: Bool = false
|
||||
|
||||
@Option(help: "Extension name. If not specified, it will generate an UIFont extension. Using default extension name will generate static property.")
|
||||
@Option(help: "Extension name. If not specified, it will generate an Font extension.")
|
||||
var extensionName: String = Fonts.defaultExtensionName
|
||||
|
||||
@Option(help: "Extension name. If not specified, it will generate an UIFont extension.")
|
||||
var extensionNameUIKit: String = Fonts.defaultExtensionNameUIKit
|
||||
|
||||
@Option(help: "Extension suffix. Ex: MyApp, it will generate {extensionName}+FontsMyApp.swift")
|
||||
var extensionSuffix: String = ""
|
||||
|
||||
@Option(name: .customLong("info-plist-paths"), help: "Info.plist paths (array). Will be used to update UIAppFonts content")
|
||||
fileprivate var infoPlistPathsRaw: String = ""
|
||||
}
|
||||
|
||||
// MARK: - Computed var
|
||||
|
||||
extension FontsOptions {
|
||||
|
||||
// MARK: - SwiftUI
|
||||
|
||||
var extensionFileName: String {
|
||||
if extensionSuffix.isEmpty == false {
|
||||
return "\(extensionName)+\(extensionSuffix).swift"
|
||||
@ -41,4 +50,25 @@ extension FontsOptions {
|
||||
var extensionFilePath: String {
|
||||
"\(extensionOutputPath)/\(extensionFileName)"
|
||||
}
|
||||
|
||||
// MARK: - UIKit
|
||||
|
||||
var extensionFileNameUIKit: String {
|
||||
if extensionSuffix.isEmpty == false {
|
||||
return "\(extensionNameUIKit)+\(extensionSuffix).swift"
|
||||
}
|
||||
return "\(extensionNameUIKit).swift"
|
||||
}
|
||||
|
||||
var extensionFilePathUIKit: String {
|
||||
"\(extensionOutputPath)/\(extensionFileNameUIKit)"
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
var infoPlistPaths: [String] {
|
||||
infoPlistPathsRaw
|
||||
.split(separator: " ")
|
||||
.map { String($0) }
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,8 @@ struct Fonts: ParsableCommand {
|
||||
// MARK: - Static
|
||||
|
||||
static let toolName = "Fonts"
|
||||
static let defaultExtensionName = "UIFont"
|
||||
static let defaultExtensionName = "Font"
|
||||
static let defaultExtensionNameUIKit = "UIFont"
|
||||
|
||||
// MARK: - Command Options
|
||||
|
||||
@ -50,10 +51,17 @@ struct Fonts: ParsableCommand {
|
||||
FontExtensionGenerator.writeExtensionFile(fontsNames: fontsNames,
|
||||
staticVar: options.staticMembers,
|
||||
extensionName: options.extensionName,
|
||||
extensionFilePath: options.extensionFilePath)
|
||||
extensionFilePath: options.extensionFilePath,
|
||||
isSwiftUI: true)
|
||||
|
||||
print("Info.plist information:")
|
||||
print("\(FontPlistGenerator.generatePlistUIAppsFontContent(for: fontsNames))")
|
||||
FontExtensionGenerator.writeExtensionFile(fontsNames: fontsNames,
|
||||
staticVar: options.staticMembers,
|
||||
extensionName: options.extensionNameUIKit,
|
||||
extensionFilePath: options.extensionFilePathUIKit,
|
||||
isSwiftUI: false)
|
||||
|
||||
print("Info.plist has been updated with:")
|
||||
print("\(FontPlistGenerator.generatePlistUIAppsFontContent(for: fontsNames, infoPlistPaths: options.infoPlistPaths))")
|
||||
|
||||
print("[\(Self.toolName)] Fonts generated")
|
||||
}
|
||||
@ -66,7 +74,14 @@ struct Fonts: ParsableCommand {
|
||||
// Check input file exists
|
||||
guard fileManager.fileExists(atPath: options.inputFile) else {
|
||||
let error = FontsToolError.fileNotExists(options.inputFile)
|
||||
print(error.localizedDescription)
|
||||
print(error.description)
|
||||
Fonts.exit(withError: error)
|
||||
}
|
||||
|
||||
// Extension for UIKit and SwiftUI should have different name
|
||||
guard options.extensionName != options.extensionNameUIKit else {
|
||||
let error = FontsToolError.extensionNamesCollision(options.extensionName)
|
||||
print(error.description)
|
||||
Fonts.exit(withError: error)
|
||||
}
|
||||
|
||||
|
@ -8,24 +8,28 @@
|
||||
import Foundation
|
||||
|
||||
enum FontsToolError: Error {
|
||||
case extensionNamesCollision(String)
|
||||
case fcScan(String, Int32, String?)
|
||||
case inputFolderNotFound(String)
|
||||
case fileNotExists(String)
|
||||
case writeExtension(String, String)
|
||||
|
||||
var localizedDescription: String {
|
||||
var description: String {
|
||||
switch self {
|
||||
case .extensionNamesCollision(let extensionName):
|
||||
return "error: [\(Fonts.toolName)] Error on extension names, extension name and SwiftUI extension name should be different (\(extensionName) is used on both)"
|
||||
|
||||
case .fcScan(let path, let code, let output):
|
||||
return "error:[\(Fonts.toolName)] Error while getting fontName (fc-scan --format %{postscriptname} \(path). fc-scan exit with \(code) and output is: \(output ?? "no output")"
|
||||
return "error: [\(Fonts.toolName)] Error while getting fontName (fc-scan --format %{postscriptname} \(path). fc-scan exit with \(code) and output is: \(output ?? "no output")"
|
||||
|
||||
case .inputFolderNotFound(let inputFolder):
|
||||
return " error:[\(Fonts.toolName)] Input folder not found: \(inputFolder)"
|
||||
return "error: [\(Fonts.toolName)] Input folder not found: \(inputFolder)"
|
||||
|
||||
case .fileNotExists(let filename):
|
||||
return " error:[\(Fonts.toolName)] File \(filename) does not exists"
|
||||
return "error: [\(Fonts.toolName)] File \(filename) does not exists"
|
||||
|
||||
case .writeExtension(let filename, let info):
|
||||
return "error:[\(Fonts.toolName)] An error occured while writing extension in \(filename): \(info)"
|
||||
return "error: [\(Fonts.toolName)] An error occured while writing extension in \(filename): \(info)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ class FontsToolHelper {
|
||||
let fileManager = FileManager()
|
||||
guard fileManager.fileExists(atPath: inputFolder) else {
|
||||
let error = FontsToolError.inputFolderNotFound(inputFolder)
|
||||
print(error.localizedDescription)
|
||||
print(error.description)
|
||||
Fonts.exit(withError: error)
|
||||
}
|
||||
|
||||
@ -64,7 +64,7 @@ class FontsToolHelper {
|
||||
|
||||
guard let fontName = task.output, task.terminationStatus == 0 else {
|
||||
let error = FontsToolError.fcScan(path, task.terminationStatus, task.output)
|
||||
print(error.localizedDescription)
|
||||
print(error.description)
|
||||
Fonts.exit(withError: error)
|
||||
}
|
||||
|
||||
|
@ -6,12 +6,34 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import ToolCore
|
||||
|
||||
class FontPlistGenerator {
|
||||
static func generatePlistUIAppsFontContent(for fonts: [FontName]) -> String {
|
||||
var plistData = "<key>UIAppFonts</key>\n\t<array>\n"
|
||||
fonts
|
||||
static func generatePlistUIAppsFontContent(for fonts: [FontName], infoPlistPaths: [String]) -> String {
|
||||
let fontsToAddToPlist = fonts
|
||||
.compactMap { $0 }
|
||||
|
||||
// Update each plist
|
||||
infoPlistPaths.forEach { infoPlist in
|
||||
// Remove UIAppFonts value
|
||||
Shell.shell(launchPath: "/usr/libexec/PlistBuddy",
|
||||
["-c", "delete :UIAppFonts", infoPlist])
|
||||
|
||||
// Add UIAppFonts empty array
|
||||
debugPrint("Will PlistBuddy -c add :UIAppFonts array \(infoPlist)")
|
||||
Shell.shell(launchPath: "/usr/libexec/PlistBuddy",
|
||||
["-c", "add :UIAppFonts array", infoPlist])
|
||||
|
||||
// Fill array with fonts
|
||||
fontsToAddToPlist
|
||||
.forEach {
|
||||
Shell.shell(launchPath: "/usr/libexec/PlistBuddy",
|
||||
["-c", "add :UIAppFonts: string \($0)", infoPlist])
|
||||
}
|
||||
}
|
||||
|
||||
var plistData = "<key>UIAppFonts</key>\n\t<array>\n"
|
||||
fontsToAddToPlist
|
||||
.forEach {
|
||||
plistData += "\t\t<string>\($0)</string>\n"
|
||||
}
|
||||
|
@ -9,44 +9,7 @@ import Foundation
|
||||
import ToolCore
|
||||
|
||||
class FontExtensionGenerator {
|
||||
|
||||
static func writeExtensionFile(fontsNames: [String], staticVar: Bool, extensionName: String, extensionFilePath: String) {
|
||||
// Create extension content
|
||||
let extensionContent = Self.getExtensionContent(fontsNames: fontsNames,
|
||||
staticVar: staticVar,
|
||||
extensionName: extensionName)
|
||||
|
||||
// Write content
|
||||
let extensionFilePathURL = URL(fileURLWithPath: extensionFilePath)
|
||||
do {
|
||||
try extensionContent.write(to: extensionFilePathURL, atomically: false, encoding: .utf8)
|
||||
} catch (let error) {
|
||||
let error = FontsToolError.writeExtension(extensionFilePath, error.localizedDescription)
|
||||
print(error.localizedDescription)
|
||||
Fonts.exit(withError: error)
|
||||
}
|
||||
}
|
||||
|
||||
static func getExtensionContent(fontsNames: [String], staticVar: Bool, extensionName: String) -> String {
|
||||
[
|
||||
Self.getHeader(extensionClassname: extensionName),
|
||||
Self.getFontNameEnum(fontsNames: fontsNames),
|
||||
Self.getFontMethods(fontsNames: fontsNames, staticVar: staticVar),
|
||||
Self.getFooter()
|
||||
]
|
||||
.joined(separator: "\n")
|
||||
}
|
||||
|
||||
private static func getHeader(extensionClassname: String) -> String {
|
||||
"""
|
||||
// Generated by ResgenSwift.\(Fonts.toolName) \(ResgenSwiftVersion)
|
||||
|
||||
import UIKit
|
||||
|
||||
extension \(extensionClassname) {\n
|
||||
"""
|
||||
}
|
||||
|
||||
|
||||
private static func getFontNameEnum(fontsNames: [String]) -> String {
|
||||
var enumDefinition = " enum FontName: String {\n"
|
||||
|
||||
@ -58,17 +21,58 @@ class FontExtensionGenerator {
|
||||
return enumDefinition
|
||||
}
|
||||
|
||||
private static func getFontMethods(fontsNames: [FontName], staticVar: Bool) -> String {
|
||||
static func writeExtensionFile(fontsNames: [String],
|
||||
staticVar: Bool,
|
||||
extensionName: String,
|
||||
extensionFilePath: String,
|
||||
isSwiftUI: Bool) {
|
||||
// Create extension content
|
||||
let extensionContent = Self.getExtensionContent(fontsNames: fontsNames,
|
||||
staticVar: staticVar,
|
||||
extensionName: extensionName,
|
||||
isSwiftUI: isSwiftUI)
|
||||
|
||||
// Write content
|
||||
let extensionFilePathURL = URL(fileURLWithPath: extensionFilePath)
|
||||
do {
|
||||
try extensionContent.write(to: extensionFilePathURL, atomically: false, encoding: .utf8)
|
||||
} catch let error {
|
||||
let error = FontsToolError.writeExtension(extensionFilePath, error.localizedDescription)
|
||||
print(error.description)
|
||||
Fonts.exit(withError: error)
|
||||
}
|
||||
}
|
||||
|
||||
static func getExtensionContent(fontsNames: [String],
|
||||
staticVar: Bool,
|
||||
extensionName: String,
|
||||
isSwiftUI: Bool) -> String {
|
||||
[
|
||||
Self.getHeader(extensionClassname: extensionName, isSwiftUI: isSwiftUI),
|
||||
Self.getFontNameEnum(fontsNames: fontsNames),
|
||||
Self.getFontMethods(fontsNames: fontsNames, staticVar: staticVar, isSwiftUI: isSwiftUI),
|
||||
Self.getFooter()
|
||||
]
|
||||
.joined(separator: "\n")
|
||||
}
|
||||
|
||||
private static func getHeader(extensionClassname: String, isSwiftUI: Bool) -> String {
|
||||
"""
|
||||
// Generated by ResgenSwift.\(Fonts.toolName) \(ResgenSwiftVersion)
|
||||
|
||||
import \(isSwiftUI ? "SwiftUI" : "UIKit")
|
||||
|
||||
extension \(extensionClassname) {\n
|
||||
"""
|
||||
}
|
||||
|
||||
private static func getFontMethods(fontsNames: [FontName], staticVar: Bool, isSwiftUI: Bool) -> String {
|
||||
let pragma = " // MARK: - Getter"
|
||||
|
||||
var propertiesOrMethods: [String] = fontsNames
|
||||
.unique()
|
||||
.map {
|
||||
if staticVar {
|
||||
return $0.staticProperty
|
||||
} else {
|
||||
return $0.method
|
||||
}
|
||||
$0.getProperty(isStatic: staticVar, isSwiftUI: isSwiftUI)
|
||||
}
|
||||
|
||||
propertiesOrMethods.insert(pragma, at: 0)
|
||||
@ -78,6 +82,7 @@ class FontExtensionGenerator {
|
||||
private static func getFooter() -> String {
|
||||
"""
|
||||
}
|
||||
|
||||
"""
|
||||
}
|
||||
}
|
||||
|
@ -14,19 +14,33 @@ extension FontName {
|
||||
self.removeCharacters(from: "[]+-_")
|
||||
}
|
||||
|
||||
var method: String {
|
||||
"""
|
||||
func getProperty(isStatic: Bool, isSwiftUI: Bool) -> String {
|
||||
if isSwiftUI {
|
||||
if isStatic {
|
||||
return """
|
||||
static let \(fontNameSanitize): ((_ size: CGFloat) -> Font) = { size in
|
||||
Font.custom(FontName.\(fontNameSanitize).rawValue, size: size)
|
||||
}
|
||||
"""
|
||||
}
|
||||
return """
|
||||
func \(fontNameSanitize)(withSize size: CGFloat) -> Font {
|
||||
Font.custom(FontName.\(fontNameSanitize).rawValue, size: size)
|
||||
}
|
||||
"""
|
||||
}
|
||||
// UIKit
|
||||
if isStatic {
|
||||
return """
|
||||
static let \(fontNameSanitize): ((_ size: CGFloat) -> UIFont) = { size in
|
||||
UIFont(name: FontName.\(fontNameSanitize).rawValue, size: size)!
|
||||
}
|
||||
"""
|
||||
}
|
||||
return """
|
||||
func \(fontNameSanitize)(withSize size: CGFloat) -> UIFont {
|
||||
UIFont(name: FontName.\(fontNameSanitize).rawValue, size: size)!
|
||||
}
|
||||
"""
|
||||
}
|
||||
|
||||
var staticProperty: String {
|
||||
"""
|
||||
static let \(fontNameSanitize): ((_ size: CGFloat) -> UIFont) = { size in
|
||||
UIFont(name: FontName.\(fontNameSanitize).rawValue, size: size)!
|
||||
}
|
||||
"""
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import Foundation
|
||||
extension String {
|
||||
|
||||
func prependIfRelativePath(_ prependPath: String) -> String {
|
||||
// If path starts with "/", it's an absolute path
|
||||
if self.hasPrefix("/") {
|
||||
return self
|
||||
}
|
||||
|
@ -34,14 +34,18 @@ struct Generate: ParsableCommand {
|
||||
// Parse
|
||||
let configuration = ConfigurationFileParser.parse(options.configurationFile)
|
||||
print("Found configurations :")
|
||||
print(" - \(configuration.colors.count) colors configuration")
|
||||
print(" - \(configuration.fonts.count) fonts configuration")
|
||||
print(" - \(configuration.images.count) images configuration")
|
||||
print(" - \(configuration.strings.count) strings configuration")
|
||||
print(" - \(configuration.tags.count) tags configuration")
|
||||
print(" - \(configuration.analytics.count) analytics configuration(s)")
|
||||
print(" - \(configuration.colors.count) colors configuration(s)")
|
||||
print(" - \(configuration.fonts.count) fonts configuration(s)")
|
||||
print(" - \(configuration.images.count) images configuration(s)")
|
||||
print(" - \(configuration.strings.count) strings configuration(s)")
|
||||
print(" - \(configuration.tags.count) tags configuration(s)")
|
||||
print()
|
||||
|
||||
print("Input file: \(configuration.colors.first?.inputFile ?? "no input file")")
|
||||
if let architecture = configuration.architecture {
|
||||
ArchitectureGenerator.writeArchitecture(architecture,
|
||||
projectDirectory: options.projectDirectory)
|
||||
}
|
||||
|
||||
// Execute commands
|
||||
configuration.runnableConfigurations
|
||||
|
@ -9,22 +9,26 @@ import Foundation
|
||||
|
||||
enum GenerateError: Error {
|
||||
case fileNotExists(String)
|
||||
case invalidConfigurationFile(String)
|
||||
case invalidConfigurationFile(String, String)
|
||||
case commandError([String], String)
|
||||
case writeFile(String, String)
|
||||
|
||||
var localizedDescription: String {
|
||||
var description: String {
|
||||
switch self {
|
||||
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):
|
||||
return " error:[\(Generate.toolName)] File \(filename) is not a valid configuration file"
|
||||
case .invalidConfigurationFile(let filename, let underneathErrorDescription):
|
||||
return "error: [\(Generate.toolName)] File \(filename) is not a valid configuration file. Underneath error: \(underneathErrorDescription)"
|
||||
|
||||
case .commandError(let command, let terminationStatus):
|
||||
let readableCommand = command
|
||||
.map { $0 }
|
||||
.joined(separator: " ")
|
||||
return "error:[\(Generate.toolName)] An error occured while running command '\(readableCommand)'. Command terminate with status code: \(terminationStatus)"
|
||||
return "error: [\(Generate.toolName)] An error occured while running command '\(readableCommand)'. Command terminate with status code: \(terminationStatus)"
|
||||
|
||||
case .writeFile(let filename, let info):
|
||||
return "error: [\(Generate.toolName)] An error occured while writing file in \(filename): \(info)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,8 +5,6 @@
|
||||
// Created by Thibaut Schmitt on 30/08/2022.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
import Foundation
|
||||
import ArgumentParser
|
||||
|
||||
|
@ -0,0 +1,39 @@
|
||||
//
|
||||
// ArchitectureGenerator.swift
|
||||
//
|
||||
//
|
||||
// Created by Thibaut Schmitt on 18/11/2022.
|
||||
//
|
||||
|
||||
import ToolCore
|
||||
import Foundation
|
||||
|
||||
struct ArchitectureGenerator {
|
||||
static func writeArchitecture(_ architecture: ConfigurationArchitecture, projectDirectory: String) {
|
||||
// Create extension content
|
||||
var architectureContent = [
|
||||
"// Generated by ResgenSwift.\(Generate.toolName) \(ResgenSwiftVersion)",
|
||||
architecture.getClass()
|
||||
]
|
||||
.joined(separator: "\n\n")
|
||||
|
||||
architectureContent += "\n"
|
||||
|
||||
let filename = "\(architecture.classname).swift"
|
||||
guard let filePath = architecture.path?.prependIfRelativePath(projectDirectory) else {
|
||||
let error = GenerateError.writeFile(filename, "Path of file is not defined.")
|
||||
print(error.description)
|
||||
Generate.exit(withError: error)
|
||||
}
|
||||
|
||||
// Write content
|
||||
let architectureFilePathURL = URL(fileURLWithPath: "\(filePath)/\(filename)")
|
||||
do {
|
||||
try architectureContent.write(to: architectureFilePathURL, atomically: false, encoding: .utf8)
|
||||
} catch let error {
|
||||
let error = GenerateError.writeFile(filename, error.localizedDescription)
|
||||
print(error.description)
|
||||
Generate.exit(withError: error)
|
||||
}
|
||||
}
|
||||
}
|
@ -8,6 +8,8 @@
|
||||
import Foundation
|
||||
|
||||
struct ConfigurationFile: Codable, CustomDebugStringConvertible {
|
||||
var architecture: ConfigurationArchitecture?
|
||||
var analytics: [AnalyticsConfiguration]
|
||||
var colors: [ColorsConfiguration]
|
||||
var fonts: [FontsConfiguration]
|
||||
var images: [ImagesConfiguration]
|
||||
@ -15,12 +17,15 @@ struct ConfigurationFile: Codable, CustomDebugStringConvertible {
|
||||
var tags: [TagsConfiguration]
|
||||
|
||||
var runnableConfigurations: [Runnable] {
|
||||
let runnables: [[Runnable]] = [colors, fonts, images, strings, tags]
|
||||
let runnables: [[Runnable]] = [analytics, colors, fonts, images, strings, tags]
|
||||
return Array(runnables.joined())
|
||||
}
|
||||
|
||||
var debugDescription: String {
|
||||
"""
|
||||
\(analytics)
|
||||
-----------
|
||||
-----------
|
||||
\(colors)
|
||||
-----------
|
||||
-----------
|
||||
@ -38,11 +43,46 @@ struct ConfigurationFile: Codable, CustomDebugStringConvertible {
|
||||
}
|
||||
}
|
||||
|
||||
struct ConfigurationArchitecture: Codable {
|
||||
let property: String
|
||||
let classname: String
|
||||
let path: String?
|
||||
let children: [ConfigurationArchitecture]?
|
||||
|
||||
func getProperty(isStatic: Bool) -> String {
|
||||
" \(isStatic ? "static " : "")let \(property) = \(classname)()"
|
||||
}
|
||||
|
||||
func getClass(generateStaticProperty: Bool = true) -> String {
|
||||
guard children?.isEmpty == false else {
|
||||
return "final class \(classname): Sendable {}"
|
||||
}
|
||||
|
||||
let classDefinition = [
|
||||
"class \(classname) {",
|
||||
children?.map { $0.getProperty(isStatic: generateStaticProperty) }.joined(separator: "\n"),
|
||||
"}"
|
||||
]
|
||||
.compactMap { $0 }
|
||||
.joined(separator: "\n")
|
||||
|
||||
return [classDefinition, "", getSubclass()]
|
||||
.compactMap { $0 }
|
||||
.joined(separator: "\n")
|
||||
}
|
||||
|
||||
func getSubclass() -> String? {
|
||||
guard let children else { return nil }
|
||||
return children.compactMap { arch in
|
||||
arch.getClass(generateStaticProperty: false)
|
||||
}
|
||||
.joined(separator: "\n\n")
|
||||
}
|
||||
}
|
||||
|
||||
struct ColorsConfiguration: Codable, CustomDebugStringConvertible {
|
||||
struct AnalyticsConfiguration: Codable, CustomDebugStringConvertible {
|
||||
let inputFile: String
|
||||
let style: String
|
||||
let xcassetsPath: String
|
||||
let target: String
|
||||
let extensionOutputPath: String
|
||||
let extensionName: String?
|
||||
let extensionSuffix: String?
|
||||
@ -55,6 +95,67 @@ struct ColorsConfiguration: Codable, CustomDebugStringConvertible {
|
||||
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 {
|
||||
let inputFile: String
|
||||
let style: String
|
||||
let xcassetsPath: String
|
||||
let extensionOutputPath: String
|
||||
let extensionName: String?
|
||||
let extensionNameUIKit: String?
|
||||
let extensionSuffix: String?
|
||||
private let staticMembers: Bool?
|
||||
|
||||
var staticMembersOptions: Bool {
|
||||
if let staticMembers = staticMembers {
|
||||
return staticMembers
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
internal init(inputFile: String,
|
||||
style: String,
|
||||
xcassetsPath: String,
|
||||
extensionOutputPath: String,
|
||||
extensionName: String?,
|
||||
extensionNameUIKit: String?,
|
||||
extensionSuffix: String?,
|
||||
staticMembers: Bool?) {
|
||||
self.inputFile = inputFile
|
||||
self.style = style
|
||||
self.xcassetsPath = xcassetsPath
|
||||
self.extensionOutputPath = extensionOutputPath
|
||||
self.extensionName = extensionName
|
||||
self.extensionNameUIKit = extensionNameUIKit
|
||||
self.extensionSuffix = extensionSuffix
|
||||
self.staticMembers = staticMembers
|
||||
}
|
||||
|
||||
var debugDescription: String {
|
||||
"""
|
||||
Colors configuration:
|
||||
@ -63,6 +164,7 @@ struct ColorsConfiguration: Codable, CustomDebugStringConvertible {
|
||||
- Xcassets path: \(xcassetsPath)
|
||||
- Extension output path: \(extensionOutputPath)
|
||||
- Extension name: \(extensionName ?? "-")
|
||||
- Extension name UIKit: \(extensionNameUIKit ?? "-")
|
||||
- Extension suffix: \(extensionSuffix ?? "-")
|
||||
"""
|
||||
}
|
||||
@ -72,7 +174,9 @@ struct FontsConfiguration: Codable, CustomDebugStringConvertible {
|
||||
let inputFile: String
|
||||
let extensionOutputPath: String
|
||||
let extensionName: String?
|
||||
let extensionNameUIKit: String?
|
||||
let extensionSuffix: String?
|
||||
let infoPlistPaths: String?
|
||||
private let staticMembers: Bool?
|
||||
|
||||
var staticMembersOptions: Bool {
|
||||
@ -82,13 +186,31 @@ struct FontsConfiguration: Codable, CustomDebugStringConvertible {
|
||||
return false
|
||||
}
|
||||
|
||||
internal init(inputFile: String,
|
||||
extensionOutputPath: String,
|
||||
extensionName: String?,
|
||||
extensionNameUIKit: String?,
|
||||
extensionSuffix: String?,
|
||||
infoPlistPaths: String?,
|
||||
staticMembers: Bool?) {
|
||||
self.inputFile = inputFile
|
||||
self.extensionOutputPath = extensionOutputPath
|
||||
self.extensionName = extensionName
|
||||
self.extensionNameUIKit = extensionNameUIKit
|
||||
self.extensionSuffix = extensionSuffix
|
||||
self.infoPlistPaths = infoPlistPaths
|
||||
self.staticMembers = staticMembers
|
||||
}
|
||||
|
||||
var debugDescription: String {
|
||||
"""
|
||||
Fonts configuration:
|
||||
- Input file: \(inputFile)
|
||||
- Extension output path: \(extensionOutputPath)
|
||||
- Extension name: \(extensionName ?? "-")
|
||||
- Extension name UIKit: \(extensionNameUIKit ?? "-")
|
||||
- Extension suffix: \(extensionSuffix ?? "-")
|
||||
- InfoPlistPaths: \(infoPlistPaths ?? "-")
|
||||
"""
|
||||
}
|
||||
}
|
||||
@ -98,6 +220,7 @@ struct ImagesConfiguration: Codable, CustomDebugStringConvertible {
|
||||
let xcassetsPath: String
|
||||
let extensionOutputPath: String
|
||||
let extensionName: String?
|
||||
let extensionNameUIKit: String?
|
||||
let extensionSuffix: String?
|
||||
private let staticMembers: Bool?
|
||||
|
||||
@ -108,6 +231,22 @@ struct ImagesConfiguration: Codable, CustomDebugStringConvertible {
|
||||
return false
|
||||
}
|
||||
|
||||
internal init(inputFile: String,
|
||||
xcassetsPath: String,
|
||||
extensionOutputPath: String,
|
||||
extensionName: String?,
|
||||
extensionNameUIKit: String?,
|
||||
extensionSuffix: String?,
|
||||
staticMembers: Bool?) {
|
||||
self.inputFile = inputFile
|
||||
self.xcassetsPath = xcassetsPath
|
||||
self.extensionOutputPath = extensionOutputPath
|
||||
self.extensionName = extensionName
|
||||
self.extensionNameUIKit = extensionNameUIKit
|
||||
self.extensionSuffix = extensionSuffix
|
||||
self.staticMembers = staticMembers
|
||||
}
|
||||
|
||||
var debugDescription: String {
|
||||
"""
|
||||
Images configuration:
|
||||
@ -115,6 +254,7 @@ struct ImagesConfiguration: Codable, CustomDebugStringConvertible {
|
||||
- Xcassets path: \(xcassetsPath)
|
||||
- Extension output path: \(extensionOutputPath)
|
||||
- Extension name: \(extensionName ?? "-")
|
||||
- Extension name UIKit: \(extensionNameUIKit ?? "-")
|
||||
- Extension suffix: \(extensionSuffix ?? "-")
|
||||
"""
|
||||
}
|
||||
@ -129,13 +269,41 @@ struct StringsConfiguration: Codable, CustomDebugStringConvertible {
|
||||
let extensionName: String?
|
||||
let extensionSuffix: String?
|
||||
private let staticMembers: Bool?
|
||||
|
||||
private let xcStrings: Bool?
|
||||
|
||||
var staticMembersOptions: Bool {
|
||||
if let staticMembers = staticMembers {
|
||||
return staticMembers
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var xcStringsOptions: Bool {
|
||||
if let xcStrings = xcStrings {
|
||||
return xcStrings
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
internal init(inputFile: String,
|
||||
outputPath: String,
|
||||
langs: String,
|
||||
defaultLang: String,
|
||||
extensionOutputPath: String,
|
||||
extensionName: String?,
|
||||
extensionSuffix: String?,
|
||||
staticMembers: Bool?,
|
||||
xcStrings: Bool?) {
|
||||
self.inputFile = inputFile
|
||||
self.outputPath = outputPath
|
||||
self.langs = langs
|
||||
self.defaultLang = defaultLang
|
||||
self.extensionOutputPath = extensionOutputPath
|
||||
self.extensionName = extensionName
|
||||
self.extensionSuffix = extensionSuffix
|
||||
self.staticMembers = staticMembers
|
||||
self.xcStrings = xcStrings
|
||||
}
|
||||
|
||||
var debugDescription: String {
|
||||
"""
|
||||
@ -166,6 +334,20 @@ struct TagsConfiguration: Codable, CustomDebugStringConvertible {
|
||||
return false
|
||||
}
|
||||
|
||||
internal init(inputFile: String,
|
||||
lang: String,
|
||||
extensionOutputPath: String,
|
||||
extensionName: String?,
|
||||
extensionSuffix: String?,
|
||||
staticMembers: Bool?) {
|
||||
self.inputFile = inputFile
|
||||
self.lang = lang
|
||||
self.extensionOutputPath = extensionOutputPath
|
||||
self.extensionName = extensionName
|
||||
self.extensionSuffix = extensionSuffix
|
||||
self.staticMembers = staticMembers
|
||||
}
|
||||
|
||||
var debugDescription: String {
|
||||
"""
|
||||
Tags configuration:
|
||||
@ -177,4 +359,3 @@ struct TagsConfiguration: Codable, CustomDebugStringConvertible {
|
||||
"""
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -12,16 +12,19 @@ class ConfigurationFileParser {
|
||||
static func parse(_ configurationFile: String) -> ConfigurationFile {
|
||||
guard let data = FileManager().contents(atPath: configurationFile) else {
|
||||
let error = GenerateError.fileNotExists(configurationFile)
|
||||
print(error.localizedDescription)
|
||||
print(error.description)
|
||||
Generate.exit(withError: error)
|
||||
}
|
||||
|
||||
guard let configuration = try? YAMLDecoder().decode(ConfigurationFile.self, from: data) else {
|
||||
let error = GenerateError.invalidConfigurationFile(configurationFile)
|
||||
print(error.localizedDescription)
|
||||
do {
|
||||
return try YAMLDecoder().decode(ConfigurationFile.self, from: data)
|
||||
} catch {
|
||||
let error = GenerateError.invalidConfigurationFile(
|
||||
configurationFile,
|
||||
error.localizedDescription.description
|
||||
)
|
||||
print(error.description)
|
||||
Generate.exit(withError: error)
|
||||
}
|
||||
|
||||
return configuration
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,13 @@
|
||||
//
|
||||
// FontsConfiguration+Runnable.swift
|
||||
// AnalyticsConfiguration+Runnable.swift
|
||||
//
|
||||
//
|
||||
// Created by Thibaut Schmitt on 30/08/2022.
|
||||
// Created by Loris Perret on 08/12/2023.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension FontsConfiguration: Runnable {
|
||||
extension AnalyticsConfiguration: Runnable {
|
||||
func run(projectDirectory: String, force: Bool) {
|
||||
var args = [String]()
|
||||
|
||||
@ -17,6 +17,8 @@ extension FontsConfiguration: Runnable {
|
||||
|
||||
args += [
|
||||
inputFile.prependIfRelativePath(projectDirectory),
|
||||
"--target",
|
||||
target,
|
||||
"--extension-output-path",
|
||||
extensionOutputPath.prependIfRelativePath(projectDirectory),
|
||||
"--static-members",
|
||||
@ -29,7 +31,6 @@ extension FontsConfiguration: Runnable {
|
||||
extensionName
|
||||
]
|
||||
}
|
||||
|
||||
if let extensionSuffix = extensionSuffix {
|
||||
args += [
|
||||
"--extension-suffix",
|
||||
@ -37,6 +38,6 @@ extension FontsConfiguration: Runnable {
|
||||
]
|
||||
}
|
||||
|
||||
Fonts.main(args)
|
||||
Analytics.main(args)
|
||||
}
|
||||
}
|
@ -9,6 +9,11 @@ import Foundation
|
||||
|
||||
extension ColorsConfiguration: Runnable {
|
||||
func run(projectDirectory: String, force: Bool) {
|
||||
let args = getArguments(projectDirectory: projectDirectory, force: force)
|
||||
Colors.main(args)
|
||||
}
|
||||
|
||||
func getArguments(projectDirectory: String, force: Bool) -> [String] {
|
||||
var args = [String]()
|
||||
|
||||
if force {
|
||||
@ -33,6 +38,12 @@ extension ColorsConfiguration: Runnable {
|
||||
extensionName
|
||||
]
|
||||
}
|
||||
if let extensionNameUIKit = extensionNameUIKit {
|
||||
args += [
|
||||
"--extension-name-ui-kit",
|
||||
extensionNameUIKit
|
||||
]
|
||||
}
|
||||
if let extensionSuffix = extensionSuffix {
|
||||
args += [
|
||||
"--extension-suffix",
|
||||
@ -40,6 +51,6 @@ extension ColorsConfiguration: Runnable {
|
||||
]
|
||||
}
|
||||
|
||||
Colors.main(args)
|
||||
return args
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
//
|
||||
// FontsConfiguration+Runnable.swift
|
||||
//
|
||||
//
|
||||
// Created by Thibaut Schmitt on 30/08/2022.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension FontsConfiguration: Runnable {
|
||||
func run(projectDirectory: String, force: Bool) {
|
||||
let args = getArguments(projectDirectory: projectDirectory, force: force)
|
||||
Fonts.main(args)
|
||||
}
|
||||
|
||||
func getArguments(projectDirectory: String, force: Bool) -> [String] {
|
||||
var args = [String]()
|
||||
|
||||
if force {
|
||||
args += ["-f"]
|
||||
}
|
||||
|
||||
args += [
|
||||
inputFile.prependIfRelativePath(projectDirectory),
|
||||
"--extension-output-path",
|
||||
extensionOutputPath.prependIfRelativePath(projectDirectory),
|
||||
"--static-members",
|
||||
"\(staticMembersOptions)"
|
||||
]
|
||||
|
||||
if let extensionName = extensionName {
|
||||
args += [
|
||||
"--extension-name",
|
||||
extensionName
|
||||
]
|
||||
}
|
||||
if let extensionNameUIKit = extensionNameUIKit {
|
||||
args += [
|
||||
"--extension-name-ui-kit",
|
||||
extensionNameUIKit
|
||||
]
|
||||
}
|
||||
|
||||
if let extensionSuffix = extensionSuffix {
|
||||
args += [
|
||||
"--extension-suffix",
|
||||
extensionSuffix
|
||||
]
|
||||
}
|
||||
|
||||
if let infoPlistPaths = infoPlistPaths {
|
||||
let adjustedPlistPaths = infoPlistPaths
|
||||
.split(separator: " ")
|
||||
.map { String($0).prependIfRelativePath(projectDirectory) }
|
||||
.joined(separator: " ")
|
||||
|
||||
args += [
|
||||
"--info-plist-paths",
|
||||
adjustedPlistPaths
|
||||
]
|
||||
}
|
||||
|
||||
return args
|
||||
}
|
||||
}
|
@ -9,6 +9,11 @@ import Foundation
|
||||
|
||||
extension ImagesConfiguration: Runnable {
|
||||
func run(projectDirectory: String, force: Bool) {
|
||||
let args = getArguments(projectDirectory: projectDirectory, force: force)
|
||||
Images.main(args)
|
||||
}
|
||||
|
||||
func getArguments(projectDirectory: String, force: Bool) -> [String] {
|
||||
var args = [String]()
|
||||
|
||||
if force {
|
||||
@ -31,6 +36,12 @@ extension ImagesConfiguration: Runnable {
|
||||
extensionName
|
||||
]
|
||||
}
|
||||
if let extensionNameUIKit = extensionNameUIKit {
|
||||
args += [
|
||||
"--extension-name-ui-kit",
|
||||
extensionNameUIKit
|
||||
]
|
||||
}
|
||||
if let extensionSuffix = extensionSuffix {
|
||||
args += [
|
||||
"--extension-suffix",
|
||||
@ -38,6 +49,6 @@ extension ImagesConfiguration: Runnable {
|
||||
]
|
||||
}
|
||||
|
||||
Images.main(args)
|
||||
return args
|
||||
}
|
||||
}
|
@ -10,4 +10,3 @@ import Foundation
|
||||
protocol Runnable {
|
||||
func run(projectDirectory: String, force: Bool)
|
||||
}
|
||||
|
||||
|
@ -14,7 +14,7 @@ extension StringsConfiguration: Runnable {
|
||||
if force {
|
||||
args += ["-f"]
|
||||
}
|
||||
|
||||
|
||||
args += [
|
||||
inputFile.prependIfRelativePath(projectDirectory),
|
||||
"--output-path",
|
||||
@ -26,7 +26,9 @@ extension StringsConfiguration: Runnable {
|
||||
"--extension-output-path",
|
||||
extensionOutputPath.prependIfRelativePath(projectDirectory),
|
||||
"--static-members",
|
||||
"\(staticMembersOptions)"
|
||||
"\(staticMembersOptions)",
|
||||
"--xc-strings",
|
||||
"\(xcStringsOptions)"
|
||||
]
|
||||
|
||||
if let extensionName = extensionName {
|
@ -12,19 +12,19 @@ extension FileManager {
|
||||
var files = [String]()
|
||||
guard let enumerator = self.enumerator(at: URL(string: directory)!, includingPropertiesForKeys: [.isRegularFileKey], options: [.skipsHiddenFiles, .skipsPackageDescendants]) else {
|
||||
let error = ImagesError.unknown("Cannot enumerate file in \(directory)")
|
||||
print(error.localizedDescription)
|
||||
print(error.description)
|
||||
Images.exit(withError: error)
|
||||
}
|
||||
|
||||
for case let fileURL as URL in enumerator {
|
||||
do {
|
||||
let fileAttributes = try fileURL.resourceValues(forKeys:[.isRegularFileKey])
|
||||
let fileAttributes = try fileURL.resourceValues(forKeys: [.isRegularFileKey])
|
||||
if fileAttributes.isRegularFile! {
|
||||
files.append(fileURL.relativePath)
|
||||
}
|
||||
} catch {
|
||||
let error = ImagesError.getFileAttributed(fileURL.relativePath, error.localizedDescription)
|
||||
print(error.localizedDescription)
|
||||
print(error.description)
|
||||
Images.exit(withError: error)
|
||||
}
|
||||
}
|
||||
@ -35,19 +35,19 @@ extension FileManager {
|
||||
var files = [String]()
|
||||
guard let enumerator = self.enumerator(at: URL(string: directory)!, includingPropertiesForKeys: [.isDirectoryKey], options: [.skipsHiddenFiles, .skipsPackageDescendants]) else {
|
||||
let error = ImagesError.unknown("Cannot enumerate imageset directory in \(directory)")
|
||||
print(error.localizedDescription)
|
||||
print(error.description)
|
||||
Images.exit(withError: error)
|
||||
}
|
||||
|
||||
for case let fileURL as URL in enumerator {
|
||||
do {
|
||||
let fileAttributes = try fileURL.resourceValues(forKeys:[.isDirectoryKey])
|
||||
let fileAttributes = try fileURL.resourceValues(forKeys: [.isDirectoryKey])
|
||||
if fileAttributes.isDirectory! && fileURL.lastPathComponent.hasSuffix(".imageset") {
|
||||
files.append(fileURL.lastPathComponent)
|
||||
}
|
||||
} catch {
|
||||
let error = ImagesError.getFileAttributed(fileURL.relativePath, error.localizedDescription)
|
||||
print(error.localizedDescription)
|
||||
print(error.description)
|
||||
Images.exit(withError: error)
|
||||
}
|
||||
}
|
||||
|
@ -10,68 +10,68 @@ import Foundation
|
||||
|
||||
class ImageExtensionGenerator {
|
||||
|
||||
// MARK: - pragm
|
||||
// MARK: - UIKit
|
||||
|
||||
static func generateExtensionFile(images: [ParsedImage],
|
||||
staticVar: Bool,
|
||||
inputFilename: String,
|
||||
extensionName: String,
|
||||
extensionFilePath: String) {
|
||||
extensionFilePath: String,
|
||||
isSwiftUI: Bool) {
|
||||
// Create extension conten1t
|
||||
let extensionContent = Self.getExtensionContent(images: images,
|
||||
staticVar: staticVar,
|
||||
extensionName: extensionName,
|
||||
inputFilename: inputFilename)
|
||||
inputFilename: inputFilename,
|
||||
isSwiftUI: isSwiftUI)
|
||||
|
||||
// Write content
|
||||
let extensionFilePathURL = URL(fileURLWithPath: extensionFilePath)
|
||||
do {
|
||||
try extensionContent.write(to: extensionFilePathURL, atomically: false, encoding: .utf8)
|
||||
} catch (let error) {
|
||||
} catch let error {
|
||||
let error = ImagesError.writeFile(extensionFilePath, error.localizedDescription)
|
||||
print(error.localizedDescription)
|
||||
print(error.description)
|
||||
Images.exit(withError: error)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Extension content
|
||||
|
||||
static func getExtensionContent(images: [ParsedImage], staticVar: Bool, extensionName: String, inputFilename: String) -> String {
|
||||
static func getExtensionContent(images: [ParsedImage],
|
||||
staticVar: Bool,
|
||||
extensionName: String,
|
||||
inputFilename: String,
|
||||
isSwiftUI: Bool) -> String {
|
||||
[
|
||||
Self.getHeader(inputFilename: inputFilename, extensionClassname: extensionName),
|
||||
Self.getProperties(images: images, staticVar: staticVar),
|
||||
Self.getHeader(inputFilename: inputFilename, extensionClassname: extensionName, isSwiftUI: isSwiftUI),
|
||||
Self.getProperties(images: images, staticVar: staticVar, isSwiftUI: isSwiftUI),
|
||||
Self.getFooter()
|
||||
]
|
||||
.joined(separator: "\n")
|
||||
}
|
||||
|
||||
// MARK: - Extension part
|
||||
|
||||
private static func getHeader(inputFilename: String, extensionClassname: String) -> String {
|
||||
private static func getHeader(inputFilename: String,
|
||||
extensionClassname: String,
|
||||
isSwiftUI: Bool) -> String {
|
||||
"""
|
||||
// Generated by ResgenSwift.\(Images.toolName) \(ResgenSwiftVersion)
|
||||
// Images from \(inputFilename)
|
||||
|
||||
import UIKit
|
||||
import \(isSwiftUI ? "SwiftUI" : "UIKit")
|
||||
|
||||
extension \(extensionClassname) {
|
||||
"""
|
||||
}
|
||||
|
||||
private static func getProperties(images: [ParsedImage], staticVar: Bool) -> String {
|
||||
if staticVar {
|
||||
return images
|
||||
.map { "\n\($0.getStaticImageProperty())" }
|
||||
.joined(separator: "\n")
|
||||
}
|
||||
return images
|
||||
.map { "\n\($0.getImageProperty())" }
|
||||
private static func getProperties(images: [ParsedImage], staticVar: Bool, isSwiftUI: Bool) -> String {
|
||||
images
|
||||
.map { "\n\($0.getImageProperty(isStatic: staticVar, isSwiftUI: isSwiftUI))" }
|
||||
.joined(separator: "\n")
|
||||
}
|
||||
|
||||
private static func getFooter() -> String {
|
||||
"""
|
||||
}
|
||||
|
||||
"""
|
||||
}
|
||||
}
|
||||
|
@ -8,10 +8,13 @@
|
||||
import Foundation
|
||||
import ToolCore
|
||||
|
||||
enum OutputImageExtension: String {
|
||||
case png
|
||||
case svg
|
||||
}
|
||||
|
||||
class XcassetsGenerator {
|
||||
|
||||
static let outputImageExtension = "png"
|
||||
|
||||
|
||||
let forceGeneration: Bool
|
||||
|
||||
// MARK: - Init
|
||||
@ -48,7 +51,7 @@ class XcassetsGenerator {
|
||||
}
|
||||
}
|
||||
let error = ImagesError.unknownImageExtension(parsedImage.name)
|
||||
print(error.localizedDescription)
|
||||
print(error.description)
|
||||
Images.exit(withError: error)
|
||||
}()
|
||||
|
||||
@ -60,13 +63,20 @@ class XcassetsGenerator {
|
||||
generatedAssetsPaths.append(imagesetName)
|
||||
|
||||
// Generate output images path
|
||||
let output1x = "\(imagesetPath)/\(parsedImage.name).\(XcassetsGenerator.outputImageExtension)"
|
||||
let output2x = "\(imagesetPath)/\(parsedImage.name)@2x.\(XcassetsGenerator.outputImageExtension)"
|
||||
let output3x = "\(imagesetPath)/\(parsedImage.name)@3x.\(XcassetsGenerator.outputImageExtension)"
|
||||
|
||||
let output1x = "\(imagesetPath)/\(parsedImage.name).\(OutputImageExtension.png.rawValue)"
|
||||
let output2x = "\(imagesetPath)/\(parsedImage.name)@2x.\(OutputImageExtension.png.rawValue)"
|
||||
let output3x = "\(imagesetPath)/\(parsedImage.name)@3x.\(OutputImageExtension.png.rawValue)"
|
||||
|
||||
// Check if we need to convert image
|
||||
guard self.shouldGenerate(inputImagePath: imageData.path, xcassetImagePath: output1x) else {
|
||||
//print("\(parsedImage.name) -> Not regenerating")
|
||||
|
||||
var needToGenerateForSvg = false
|
||||
|
||||
if imageData.ext == "svg" && !parsedImage.imageExtensions.contains(.png) {
|
||||
needToGenerateForSvg = true
|
||||
}
|
||||
|
||||
guard self.shouldGenerate(inputImagePath: imageData.path, xcassetImagePath: output1x, needToGenerateForSvg: needToGenerateForSvg) else {
|
||||
print("\(parsedImage.name) -> Not regenerating")
|
||||
return
|
||||
}
|
||||
|
||||
@ -77,32 +87,58 @@ class XcassetsGenerator {
|
||||
withIntermediateDirectories: true)
|
||||
} catch {
|
||||
let error = ImagesError.createAssetFolder(imagesetPath)
|
||||
print(error.localizedDescription)
|
||||
print(error.description)
|
||||
Images.exit(withError: error)
|
||||
}
|
||||
} else {
|
||||
do {
|
||||
let documentsDirectory = try fileManager.contentsOfDirectory(atPath: imagesetPath)
|
||||
for filePath in documentsDirectory {
|
||||
try fileManager.removeItem(atPath: "\(imagesetPath)/\(filePath)")
|
||||
}
|
||||
} catch {
|
||||
print("Error deleting previous assets")
|
||||
}
|
||||
}
|
||||
|
||||
// Convert image
|
||||
|
||||
let convertArguments = parsedImage.convertArguments
|
||||
|
||||
if imageData.ext == "svg" {
|
||||
// /usr/local/bin/rsvg-convert path/to/image.png -w 200 -h 300 -o path/to/output.png
|
||||
// /usr/local/bin/rsvg-convert path/to/image.png -w 200 -o path/to/output.png
|
||||
// /usr/local/bin/rsvg-convert path/to/image.png -h 300 -o path/to/output.png
|
||||
var command1x = ["\(svgConverter)", "\(imageData.path)"]
|
||||
var command2x = ["\(svgConverter)", "\(imageData.path)"]
|
||||
var command3x = ["\(svgConverter)", "\(imageData.path)"]
|
||||
|
||||
self.addConvertArgument(command: &command1x, convertArgument: convertArguments.x1)
|
||||
self.addConvertArgument(command: &command2x, convertArgument: convertArguments.x2)
|
||||
self.addConvertArgument(command: &command3x, convertArgument: convertArguments.x3)
|
||||
|
||||
command1x.append(contentsOf: ["-o", output1x])
|
||||
command2x.append(contentsOf: ["-o", output2x])
|
||||
command3x.append(contentsOf: ["-o", output3x])
|
||||
|
||||
Shell.shell(command1x)
|
||||
Shell.shell(command2x)
|
||||
Shell.shell(command3x)
|
||||
if parsedImage.imageExtensions.contains(.png) {
|
||||
|
||||
// /usr/local/bin/rsvg-convert path/to/image.png -w 200 -h 300 -o path/to/output.png
|
||||
// /usr/local/bin/rsvg-convert path/to/image.png -w 200 -o path/to/output.png
|
||||
// /usr/local/bin/rsvg-convert path/to/image.png -h 300 -o path/to/output.png
|
||||
var command1x = ["\(svgConverter)", "\(imageData.path)"]
|
||||
var command2x = ["\(svgConverter)", "\(imageData.path)"]
|
||||
var command3x = ["\(svgConverter)", "\(imageData.path)"]
|
||||
|
||||
self.addConvertArgument(command: &command1x, convertArgument: convertArguments.x1)
|
||||
self.addConvertArgument(command: &command2x, convertArgument: convertArguments.x2)
|
||||
self.addConvertArgument(command: &command3x, convertArgument: convertArguments.x3)
|
||||
|
||||
command1x.append(contentsOf: ["-o", output1x])
|
||||
command2x.append(contentsOf: ["-o", output2x])
|
||||
command3x.append(contentsOf: ["-o", output3x])
|
||||
|
||||
Shell.shell(command1x)
|
||||
Shell.shell(command2x)
|
||||
Shell.shell(command3x)
|
||||
|
||||
} else {
|
||||
|
||||
let output = "\(imagesetPath)/\(parsedImage.name).\(OutputImageExtension.svg.rawValue)"
|
||||
let tempURL = URL(fileURLWithPath: output)
|
||||
|
||||
do {
|
||||
if FileManager.default.fileExists(atPath: tempURL.path) {
|
||||
try FileManager.default.removeItem(atPath: tempURL.path)
|
||||
}
|
||||
try FileManager.default.copyItem(atPath: imageData.path, toPath: tempURL.path)
|
||||
} catch {
|
||||
print(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// convert path/to/image.png -resize 200x300 path/to/output.png
|
||||
// convert path/to/image.png -resize 200x path/to/output.png
|
||||
@ -119,7 +155,7 @@ class XcassetsGenerator {
|
||||
}
|
||||
|
||||
// Write Content.json
|
||||
let imagesetContentJson = parsedImage.contentJson
|
||||
guard let imagesetContentJson = parsedImage.generateContentJson(isVector: imageData.ext == "svg") else { return }
|
||||
let contentJsonFilePath = "\(imagesetPath)/Contents.json"
|
||||
|
||||
let contentJsonFilePathURL = URL(fileURLWithPath: contentJsonFilePath)
|
||||
@ -161,8 +197,8 @@ class XcassetsGenerator {
|
||||
|
||||
// MARK: - Helpers: bypass generation
|
||||
|
||||
private func shouldGenerate(inputImagePath: String, xcassetImagePath: String) -> Bool {
|
||||
if forceGeneration {
|
||||
private func shouldGenerate(inputImagePath: String, xcassetImagePath: String, needToGenerateForSvg: Bool) -> Bool {
|
||||
if forceGeneration || needToGenerateForSvg {
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -21,7 +21,8 @@ struct Images: ParsableCommand {
|
||||
// MARK: - Static
|
||||
|
||||
static let toolName = "Images"
|
||||
static let defaultExtensionName = "UIImage"
|
||||
static let defaultExtensionName = "Image"
|
||||
static let defaultExtensionNameUIKit = "UIImage"
|
||||
|
||||
// MARK: - Command Options
|
||||
|
||||
@ -56,8 +57,15 @@ struct Images: ParsableCommand {
|
||||
staticVar: options.staticMembers,
|
||||
inputFilename: options.inputFilenameWithoutExt,
|
||||
extensionName: options.extensionName,
|
||||
extensionFilePath: options.extensionFilePath)
|
||||
extensionFilePath: options.extensionFilePath,
|
||||
isSwiftUI: true)
|
||||
|
||||
ImageExtensionGenerator.generateExtensionFile(images: imagesToGenerate,
|
||||
staticVar: options.staticMembers,
|
||||
inputFilename: options.inputFilenameWithoutExt,
|
||||
extensionName: options.extensionNameUIKit,
|
||||
extensionFilePath: options.extensionFilePathUIKit,
|
||||
isSwiftUI: false)
|
||||
|
||||
print("[\(Self.toolName)] Images generated")
|
||||
}
|
||||
@ -74,13 +82,20 @@ struct Images: ParsableCommand {
|
||||
// Input file
|
||||
guard fileManager.fileExists(atPath: options.inputFile) else {
|
||||
let error = ImagesError.fileNotExists(options.inputFile)
|
||||
print(error.localizedDescription)
|
||||
print(error.description)
|
||||
Images.exit(withError: error)
|
||||
}
|
||||
|
||||
// RSVG-Converter
|
||||
_ = Images.getSvgConverterPath()
|
||||
|
||||
// Extension for UIKit and SwiftUI should have different name
|
||||
guard options.extensionName != options.extensionNameUIKit else {
|
||||
let error = ImagesError.extensionNamesCollision(options.extensionName)
|
||||
print(error.description)
|
||||
Images.exit(withError: error)
|
||||
}
|
||||
|
||||
// Check if needed to regenerate
|
||||
guard GeneratorChecker.shouldGenerate(force: options.forceExecution,
|
||||
inputFilePath: options.inputFile,
|
||||
@ -102,7 +117,7 @@ struct Images: ParsableCommand {
|
||||
}
|
||||
|
||||
let error = ImagesError.rsvgConvertNotFound
|
||||
print(error.localizedDescription)
|
||||
print(error.description)
|
||||
Images.exit(withError: error)
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@
|
||||
import Foundation
|
||||
|
||||
enum ImagesError: Error {
|
||||
case extensionNamesCollision(String)
|
||||
case inputFolderNotFound(String)
|
||||
case fileNotExists(String)
|
||||
case unknownImageExtension(String)
|
||||
@ -17,31 +18,34 @@ enum ImagesError: Error {
|
||||
case createAssetFolder(String)
|
||||
case unknown(String)
|
||||
|
||||
var localizedDescription: String {
|
||||
var description: String {
|
||||
switch self {
|
||||
case .extensionNamesCollision(let extensionName):
|
||||
return "error: [\(Fonts.toolName)] Error on extension names, extension name and SwiftUI extension name should be different (\(extensionName) is used on both)"
|
||||
|
||||
case .inputFolderNotFound(let inputFolder):
|
||||
return " error:[\(Images.toolName)] Input folder not found: \(inputFolder)"
|
||||
return "error: [\(Images.toolName)] Input folder not found: \(inputFolder)"
|
||||
|
||||
case .fileNotExists(let filename):
|
||||
return " error:[\(Images.toolName)] File \(filename) does not exists"
|
||||
return "error: [\(Images.toolName)] File \(filename) does not exists"
|
||||
|
||||
case .unknownImageExtension(let filename):
|
||||
return " error:[\(Images.toolName)] File \(filename) have an unhandled file extension. Cannot generate image."
|
||||
return "error: [\(Images.toolName)] File \(filename) have an unhandled file extension. Cannot generate image."
|
||||
|
||||
case .getFileAttributed(let filename, let errorDescription):
|
||||
return " error:[\(Images.toolName)] Getting file attributes of \(filename) failed with error: \(errorDescription)"
|
||||
return "error: [\(Images.toolName)] Getting file attributes of \(filename) failed with error: \(errorDescription)"
|
||||
|
||||
case .rsvgConvertNotFound:
|
||||
return " error:[\(Images.toolName)] Can't find rsvg-convert (can be installed with 'brew remove imagemagick && brew install imagemagick --with-librsvg')"
|
||||
return "error: [\(Images.toolName)] Can't find rsvg-convert (can be installed with 'brew remove imagemagick && brew install librsvg')"
|
||||
|
||||
case .writeFile(let subErrorDescription, let filename):
|
||||
return " error:[\(Images.toolName)] An error occured while writing content to \(filename): \(subErrorDescription)"
|
||||
return "error: [\(Images.toolName)] An error occured while writing content to \(filename): \(subErrorDescription)"
|
||||
|
||||
case .createAssetFolder(let folder):
|
||||
return "error:[\(Colors.toolName)] An error occured while creating folder `\(folder)`"
|
||||
return "error: [\(Colors.toolName)] An error occured while creating folder `\(folder)`"
|
||||
|
||||
case .unknown(let errorDescription):
|
||||
return " error:[\(Images.toolName)] Unknown error: \(errorDescription)"
|
||||
return "error: [\(Images.toolName)] Unknown error: \(errorDescription)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -27,9 +27,12 @@ struct ImagesOptions: ParsableArguments {
|
||||
@Option(help: "Tell if it will generate static properties or not")
|
||||
var staticMembers: Bool = false
|
||||
|
||||
@Option(help: "Extension name. If not specified, it will generate an UIImage extension. Using default extension name will generate static property.")
|
||||
@Option(help: "Extension name. If not specified, it will generate an Image extension.")
|
||||
var extensionName: String = Images.defaultExtensionName
|
||||
|
||||
@Option(help: "Extension name. If not specified, it will generate an UIImage extension.")
|
||||
var extensionNameUIKit: String = Images.defaultExtensionNameUIKit
|
||||
|
||||
@Option(help: "Extension suffix. Ex: MyApp, it will generate {extensionName}+Image{extensionSuffix}.swift")
|
||||
var extensionSuffix: String?
|
||||
}
|
||||
@ -37,6 +40,9 @@ struct ImagesOptions: ParsableArguments {
|
||||
// MARK: - Computed var
|
||||
|
||||
extension ImagesOptions {
|
||||
|
||||
// MARK: - SwiftUI
|
||||
|
||||
var extensionFileName: String {
|
||||
if let extensionSuffix = extensionSuffix {
|
||||
return "\(extensionName)+\(extensionSuffix).swift"
|
||||
@ -48,6 +54,21 @@ extension ImagesOptions {
|
||||
"\(extensionOutputPath)/\(extensionFileName)"
|
||||
}
|
||||
|
||||
// MARK: - UIKit
|
||||
|
||||
var extensionFileNameUIKit: String {
|
||||
if let extensionSuffix = extensionSuffix {
|
||||
return "\(extensionNameUIKit)+\(extensionSuffix).swift"
|
||||
}
|
||||
return "\(extensionNameUIKit).swift"
|
||||
}
|
||||
|
||||
var extensionFilePathUIKit: String {
|
||||
"\(extensionOutputPath)/\(extensionFileNameUIKit)"
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
var inputFilenameWithoutExt: String {
|
||||
URL(fileURLWithPath: inputFile)
|
||||
.deletingPathExtension()
|
||||
|
76
Sources/ResgenSwift/Images/Model/ImageContent.swift
Normal file
76
Sources/ResgenSwift/Images/Model/ImageContent.swift
Normal file
@ -0,0 +1,76 @@
|
||||
//
|
||||
// ImageContent.swift
|
||||
//
|
||||
//
|
||||
// Created by Quentin Bandera on 19/04/2024.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum TemplateRenderingIntent: String, Codable {
|
||||
case template
|
||||
case original
|
||||
}
|
||||
|
||||
struct AssetContent: Codable, Equatable {
|
||||
let images: [AssetImageDescription]
|
||||
let info: AssetInfo
|
||||
let properties: AssetProperties?
|
||||
|
||||
init(
|
||||
images: [AssetImageDescription],
|
||||
info: AssetInfo,
|
||||
properties: AssetProperties? = nil
|
||||
) {
|
||||
self.images = images
|
||||
self.info = info
|
||||
self.properties = properties
|
||||
}
|
||||
|
||||
static func == (lhs: AssetContent, rhs: AssetContent) -> Bool {
|
||||
guard lhs.images.count == rhs.images.count else { return false }
|
||||
let lhsImages = lhs.images.sorted(by: { $0.filename < $1.filename })
|
||||
let rhsImages = rhs.images.sorted(by: { $0.filename < $1.filename })
|
||||
|
||||
return lhsImages == rhsImages
|
||||
}
|
||||
}
|
||||
|
||||
struct AssetImageDescription: Codable, Equatable {
|
||||
let idiom: String
|
||||
let scale: String?
|
||||
let filename: String
|
||||
|
||||
init(
|
||||
idiom: String,
|
||||
scale: String? = nil,
|
||||
filename: String
|
||||
) {
|
||||
self.idiom = idiom
|
||||
self.scale = scale
|
||||
self.filename = filename
|
||||
}
|
||||
}
|
||||
|
||||
struct AssetInfo: Codable, Equatable {
|
||||
let version: Int
|
||||
let author: String
|
||||
}
|
||||
|
||||
struct AssetProperties: Codable, Equatable {
|
||||
let preservesVectorRepresentation: Bool
|
||||
let templateRenderingIntent: TemplateRenderingIntent?
|
||||
|
||||
init(
|
||||
preservesVectorRepresentation: Bool,
|
||||
templateRenderingIntent: TemplateRenderingIntent? = nil
|
||||
) {
|
||||
self.preservesVectorRepresentation = preservesVectorRepresentation
|
||||
self.templateRenderingIntent = templateRenderingIntent
|
||||
}
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case preservesVectorRepresentation = "preserves-vector-representation"
|
||||
case templateRenderingIntent = "template-rendering-intent"
|
||||
}
|
||||
}
|
@ -7,12 +7,31 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
enum ImageExtension: String {
|
||||
case png
|
||||
}
|
||||
|
||||
struct ParsedImage {
|
||||
let name: String
|
||||
let tags: String
|
||||
let width: Int
|
||||
let height: Int
|
||||
|
||||
let imageExtensions: [ImageExtension]
|
||||
|
||||
init(
|
||||
name: String,
|
||||
tags: String,
|
||||
width: Int,
|
||||
height: Int,
|
||||
imageExtensions: [ImageExtension] = []
|
||||
) {
|
||||
self.name = name
|
||||
self.tags = tags
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.imageExtensions = imageExtensions
|
||||
}
|
||||
|
||||
// MARK: - Convert
|
||||
|
||||
var convertArguments: (x1: ConvertArgument, x2: ConvertArgument, x3: ConvertArgument) {
|
||||
@ -42,47 +61,81 @@ struct ParsedImage {
|
||||
|
||||
// MARK: - Assets
|
||||
|
||||
var contentJson: String {
|
||||
"""
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x",
|
||||
"filename" : "\(name).\(XcassetsGenerator.outputImageExtension)"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x",
|
||||
"filename" : "\(name)@2x.\(XcassetsGenerator.outputImageExtension)"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x",
|
||||
"filename" : "\(name)@3x.\(XcassetsGenerator.outputImageExtension)"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "ResgenSwift-Imagium"
|
||||
}
|
||||
func generateContentJson(isVector: Bool) -> String? {
|
||||
let encoder = JSONEncoder()
|
||||
encoder.outputFormatting = .prettyPrinted
|
||||
|
||||
let imageContent = generateImageContent(isVector: isVector)
|
||||
|
||||
guard let data = try? encoder.encode(imageContent) else {
|
||||
let error = ImagesError.writeFile("Contents.json", "Error encoding json file")
|
||||
print(error.description)
|
||||
Images.exit(withError: error)
|
||||
}
|
||||
"""
|
||||
|
||||
return String(decoding: data, as: UTF8.self)
|
||||
}
|
||||
|
||||
|
||||
func generateImageContent(isVector: Bool) -> AssetContent {
|
||||
|
||||
if !imageExtensions.contains(.png) && isVector {
|
||||
//Generate svg
|
||||
return AssetContent(
|
||||
images: [
|
||||
AssetImageDescription(
|
||||
idiom: "universal",
|
||||
filename: "\(name).\(OutputImageExtension.svg.rawValue)"
|
||||
)
|
||||
],
|
||||
info: AssetInfo(
|
||||
version: 1,
|
||||
author: "ResgenSwift-Imagium"
|
||||
),
|
||||
properties: AssetProperties(
|
||||
preservesVectorRepresentation: true,
|
||||
templateRenderingIntent: .original
|
||||
)
|
||||
)
|
||||
} else {
|
||||
//Generate png
|
||||
return AssetContent(
|
||||
images: [
|
||||
AssetImageDescription(
|
||||
idiom: "universal",
|
||||
scale: "1x",
|
||||
filename: "\(name).\(OutputImageExtension.png.rawValue)"
|
||||
),
|
||||
AssetImageDescription(
|
||||
idiom: "universal",
|
||||
scale: "2x",
|
||||
filename: "\(name)@2x.\(OutputImageExtension.png.rawValue)"
|
||||
),
|
||||
AssetImageDescription(
|
||||
idiom: "universal",
|
||||
scale: "3x",
|
||||
filename: "\(name)@3x.\(OutputImageExtension.png.rawValue)"
|
||||
)
|
||||
],
|
||||
info: AssetInfo(
|
||||
version: 1,
|
||||
author: "ResgenSwift-Imagium"
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Extension property
|
||||
|
||||
func getImageProperty() -> String {
|
||||
"""
|
||||
var \(name): UIImage {
|
||||
UIImage(named: "\(name)")!
|
||||
}
|
||||
"""
|
||||
}
|
||||
|
||||
func getStaticImageProperty() -> String {
|
||||
"""
|
||||
static var \(name): UIImage {
|
||||
func getImageProperty(isStatic: Bool, isSwiftUI: Bool) -> String {
|
||||
if isSwiftUI {
|
||||
return """
|
||||
\(isStatic ? "static ": "")var \(name): Image {
|
||||
Image("\(name)")
|
||||
}
|
||||
"""
|
||||
}
|
||||
return """
|
||||
\(isStatic ? "static ": "")var \(name): UIImage {
|
||||
UIImage(named: "\(name)")!
|
||||
}
|
||||
"""
|
||||
|
@ -38,11 +38,21 @@ class ImageFileParser {
|
||||
}
|
||||
return Int(splittedLine[3])!
|
||||
}()
|
||||
|
||||
let image = ParsedImage(name: String(splittedLine[1]), tags: String(splittedLine[0]), width: width, height: height)
|
||||
|
||||
var imageExtensions: [ImageExtension] = []
|
||||
|
||||
splittedLine.forEach { stringExtension in
|
||||
if let imageExtension = ImageExtension(rawValue: String(stringExtension)) {
|
||||
imageExtensions.append(imageExtension)
|
||||
}
|
||||
}
|
||||
|
||||
let image = ParsedImage(name: String(splittedLine[1]), tags: String(splittedLine[0]), width: width, height: height, imageExtensions: imageExtensions)
|
||||
imagesToGenerate.append(image)
|
||||
}
|
||||
|
||||
|
||||
print(imagesToGenerate)
|
||||
|
||||
return imagesToGenerate.filter {
|
||||
$0.tags.contains(platform.rawValue)
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
//
|
||||
// StringsFileGenerator.swift
|
||||
//
|
||||
//
|
||||
//
|
||||
// Created by Thibaut Schmitt on 04/01/2022.
|
||||
//
|
||||
@ -9,10 +9,16 @@ import Foundation
|
||||
import ToolCore
|
||||
|
||||
class StringsFileGenerator {
|
||||
|
||||
|
||||
// MARK: - Strings Files
|
||||
|
||||
static func writeStringsFiles(sections: [Section], langs: [String], defaultLang: String, tags: [String], outputPath: String, inputFilenameWithoutExt: String) {
|
||||
|
||||
static func writeStringsFiles(sections: [Section],
|
||||
langs: [String],
|
||||
defaultLang: String,
|
||||
tags: [String],
|
||||
outputPath: String,
|
||||
inputFilenameWithoutExt: String) {
|
||||
|
||||
var stringsFilesContent = [String: String]()
|
||||
for lang in langs {
|
||||
stringsFilesContent[lang] = Self.generateStringsFileContent(lang: lang,
|
||||
@ -20,24 +26,52 @@ class StringsFileGenerator {
|
||||
tags: tags,
|
||||
sections: sections)
|
||||
}
|
||||
|
||||
|
||||
// Write strings file content
|
||||
langs.forEach { lang in
|
||||
guard let fileContent = stringsFilesContent[lang] else { return }
|
||||
|
||||
|
||||
let stringsFilePath = "\(outputPath)/\(lang).lproj/\(inputFilenameWithoutExt).strings"
|
||||
let stringsFilePathURL = URL(fileURLWithPath: stringsFilePath)
|
||||
do {
|
||||
try fileContent.write(to: stringsFilePathURL, atomically: false, encoding: .utf8)
|
||||
} catch (let error) {
|
||||
} catch let error {
|
||||
let error = StringiumError.writeFile(error.localizedDescription, stringsFilePath)
|
||||
print(error.localizedDescription)
|
||||
print(error.description)
|
||||
Stringium.exit(withError: error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func generateStringsFileContent(lang: String, defaultLang: String, tags inputTags: [String], sections: [Section]) -> String {
|
||||
|
||||
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,
|
||||
defaultLang: String,
|
||||
tags inputTags: [String],
|
||||
sections: [Section]) -> String {
|
||||
var stringsFileContent = """
|
||||
/**
|
||||
* Apple Strings File
|
||||
@ -45,13 +79,13 @@ class StringsFileGenerator {
|
||||
* Language: \(lang)
|
||||
*/\n
|
||||
"""
|
||||
|
||||
|
||||
sections.forEach { section in
|
||||
// Check that at least one string will be generated
|
||||
guard section.hasOneOrMoreMatchingTags(tags: inputTags) else {
|
||||
return // Go to next section
|
||||
}
|
||||
|
||||
|
||||
stringsFileContent += "\n/********** \(section.name) **********/\n\n"
|
||||
section.definitions.forEach { definition in
|
||||
var skipDefinition = false // Set to true if not matching tag
|
||||
@ -61,89 +95,257 @@ class StringsFileGenerator {
|
||||
skipDefinition = true
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
// If tags contains `noTranslationTag` => get default lang
|
||||
if definition.tags.contains(Stringium.noTranslationTag) {
|
||||
return definition.translations[defaultLang]
|
||||
}
|
||||
|
||||
|
||||
// Else: get specific lang
|
||||
return definition.translations[lang]
|
||||
}()
|
||||
|
||||
|
||||
if let translation = translationOpt {
|
||||
stringsFileContent += "\"\(definition.name)\" = \"\(translation)\";\n\n"
|
||||
} else if skipDefinition == false {
|
||||
let error = StringiumError.langNotDefined(lang, definition.name, definition.reference != nil)
|
||||
print(error.localizedDescription)
|
||||
print(error.description)
|
||||
Stringium.exit(withError: error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return stringsFileContent
|
||||
}
|
||||
|
||||
|
||||
// MARK: - XcStrings Generation
|
||||
|
||||
static func generateXcStringsFileContent(langs: [String],
|
||||
defaultLang: String,
|
||||
tags inputTags: [String],
|
||||
sections: [Section]) -> String {
|
||||
let rootObject = generateRootObject(langs: langs, defaultLang: defaultLang, tags: inputTags, sections: sections)
|
||||
let file = generateXcStringsFileContentFromRootObject(rootObject: rootObject)
|
||||
|
||||
return file
|
||||
}
|
||||
|
||||
static func generateXcStringsFileContentFromRootObject(rootObject: Root) -> String {
|
||||
do {
|
||||
let encoder = JSONEncoder()
|
||||
encoder.outputFormatting = [.prettyPrinted]
|
||||
|
||||
let json = try encoder.encode(rootObject)
|
||||
|
||||
return String(decoding: json, as: UTF8.self)
|
||||
|
||||
} catch {
|
||||
debugPrint("Failed to encode: \(error)")
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
static func generateRootObject(langs: [String],
|
||||
defaultLang: String,
|
||||
tags inputTags: [String],
|
||||
sections: [Section]) -> Root {
|
||||
|
||||
var xcStringDefinitionTab: [XCStringDefinition] = []
|
||||
|
||||
sections.forEach { section in
|
||||
// Check that at least one string will be generated
|
||||
guard section.hasOneOrMoreMatchingTags(tags: inputTags) else {
|
||||
return // Go to next section
|
||||
}
|
||||
|
||||
section.definitions.forEach { definition in
|
||||
var skipDefinition = false
|
||||
var isNoTranslation = false
|
||||
|
||||
var localizationTab: [XCStringLocalization] = []
|
||||
|
||||
if definition.hasOneOrMoreMatchingTags(inputTags: inputTags) == false {
|
||||
skipDefinition = true
|
||||
}
|
||||
|
||||
if definition.tags.contains(Stringium.noTranslationTag) {
|
||||
isNoTranslation = true
|
||||
}
|
||||
|
||||
if !skipDefinition {
|
||||
if isNoTranslation {
|
||||
// Search for langs in yaml
|
||||
for lang in langs {
|
||||
if let value = definition.translations[defaultLang], !value.isEmpty {
|
||||
let localization = XCStringLocalization(
|
||||
lang: lang,
|
||||
content: XCStringLocalizationLangContent(
|
||||
stringUnit: DefaultStringUnit(state: "translated", value: value)
|
||||
)
|
||||
)
|
||||
localizationTab.append(localization)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Search for langs in twine
|
||||
for (lang, value) in definition.translations where !value.isEmpty {
|
||||
|
||||
let localization = XCStringLocalization(
|
||||
lang: lang,
|
||||
content: XCStringLocalizationLangContent(
|
||||
stringUnit: DefaultStringUnit(state: "translated", value: value)
|
||||
)
|
||||
)
|
||||
|
||||
localizationTab.append(localization)
|
||||
}
|
||||
}
|
||||
|
||||
let xcStringDefinition = XCStringDefinition(
|
||||
title: definition.name,
|
||||
content: XCStringDefinitionContent(
|
||||
comment: definition.comment,
|
||||
extractionState: "manual",
|
||||
localizations: XCStringLocalizationContainer(
|
||||
localizations: localizationTab
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
xcStringDefinitionTab.append(xcStringDefinition)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let xcStringContainer = XCStringDefinitionContainer(strings: xcStringDefinitionTab)
|
||||
|
||||
return Root(
|
||||
sourceLanguage: defaultLang,
|
||||
strings: xcStringContainer,
|
||||
version: "1.0"
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: - Extension file
|
||||
|
||||
static func writeExtensionFiles(sections: [Section], defaultLang lang: String, tags: [String], staticVar: Bool, inputFilename: String, extensionName: String, extensionFilePath: String) {
|
||||
|
||||
static func writeExtensionFiles(sections: [Section],
|
||||
defaultLang lang: String,
|
||||
tags: [String],
|
||||
staticVar: Bool,
|
||||
inputFilename: String,
|
||||
extensionName: String,
|
||||
extensionFilePath: String,
|
||||
extensionSuffix: String) {
|
||||
// Get extension content
|
||||
let extensionFileContent = Self.getExtensionContent(sections: sections,
|
||||
defaultLang: lang,
|
||||
tags: tags,
|
||||
staticVar: staticVar,
|
||||
inputFilename: inputFilename,
|
||||
extensionName: extensionName)
|
||||
|
||||
extensionName: extensionName,
|
||||
extensionSuffix: extensionSuffix)
|
||||
|
||||
// Write content
|
||||
let extensionFilePathURL = URL(fileURLWithPath: extensionFilePath)
|
||||
do {
|
||||
try extensionFileContent.write(to: extensionFilePathURL, atomically: false, encoding: .utf8)
|
||||
} catch (let error) {
|
||||
} catch let error {
|
||||
let error = StringiumError.writeFile(extensionFilePath, error.localizedDescription)
|
||||
print(error.localizedDescription)
|
||||
print(error.description)
|
||||
Stringium.exit(withError: error)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Extension content
|
||||
|
||||
static func getExtensionContent(sections: [Section], defaultLang lang: String, tags: [String], staticVar: Bool, inputFilename: String, extensionName: String) -> String {
|
||||
|
||||
static func getExtensionContent(sections: [Section],
|
||||
defaultLang lang: String,
|
||||
tags: [String],
|
||||
staticVar: Bool,
|
||||
inputFilename: String,
|
||||
extensionName: String,
|
||||
extensionSuffix: String) -> String {
|
||||
[
|
||||
Self.getHeader(stringsFilename: inputFilename, extensionClassname: extensionName),
|
||||
Self.getEnumKey(sections: sections, tags: tags, extensionClassname: extensionName, extensionSuffix: extensionSuffix),
|
||||
Self.getProperties(sections: sections, defaultLang: lang, tags: tags, staticVar: staticVar),
|
||||
Self.getFooter()
|
||||
]
|
||||
.joined(separator: "\n")
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Extension part
|
||||
|
||||
|
||||
private static func getHeader(stringsFilename: String, extensionClassname: String) -> String {
|
||||
"""
|
||||
// Generated by ResgenSwift.Strings.\(Stringium.toolName) \(ResgenSwiftVersion)
|
||||
|
||||
|
||||
import UIKit
|
||||
|
||||
|
||||
fileprivate let kStringsFileName = "\(stringsFilename)"
|
||||
|
||||
|
||||
extension \(extensionClassname) {
|
||||
"""
|
||||
}
|
||||
|
||||
|
||||
private static func getEnumKey(sections: [Section], tags: [String], extensionClassname: String, extensionSuffix: String) -> String {
|
||||
var enumDefinition = "\n enum Key\(extensionSuffix.uppercasedFirst()): String {\n"
|
||||
|
||||
// Enum
|
||||
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) = \"\(definition.name)\"\n"
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
private static func getProperties(sections: [Section], defaultLang lang: String, 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.name)\n"
|
||||
res += section.definitions.compactMap { definition in
|
||||
guard definition.hasOneOrMoreMatchingTags(inputTags: tags) == true else {
|
||||
return nil // Go to next definition
|
||||
}
|
||||
|
||||
|
||||
if staticVar {
|
||||
return "\n\(definition.getNSLocalizedStringStaticProperty(forLang: lang))"
|
||||
}
|
||||
@ -154,10 +356,11 @@ class StringsFileGenerator {
|
||||
}
|
||||
.joined(separator: "\n")
|
||||
}
|
||||
|
||||
|
||||
private static func getFooter() -> String {
|
||||
"""
|
||||
}
|
||||
|
||||
"""
|
||||
}
|
||||
}
|
||||
|
@ -22,9 +22,9 @@ class TagsGenerator {
|
||||
let extensionFilePathURL = URL(fileURLWithPath: extensionFilePath)
|
||||
do {
|
||||
try extensionFileContent.write(to: extensionFilePathURL, atomically: false, encoding: .utf8)
|
||||
} catch (let error) {
|
||||
} catch let error {
|
||||
let error = StringiumError.writeFile(extensionFilePath, error.localizedDescription)
|
||||
print(error.localizedDescription)
|
||||
print(error.description)
|
||||
Stringium.exit(withError: error)
|
||||
}
|
||||
}
|
||||
@ -80,6 +80,7 @@ class TagsGenerator {
|
||||
private static func getFooter() -> String {
|
||||
"""
|
||||
}
|
||||
|
||||
"""
|
||||
}
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ class Definition {
|
||||
}
|
||||
|
||||
func hasOneOrMoreMatchingTags(inputTags: [String]) -> Bool {
|
||||
if Set(inputTags).intersection(Set(self.tags)).isEmpty {
|
||||
if Set(inputTags).isDisjoint(with: tags) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
@ -84,22 +84,29 @@ class Definition {
|
||||
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)
|
||||
///
|
||||
/// Comment :
|
||||
/// \(comment?.isEmpty == false ? comment! : "No comment")
|
||||
\(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)
|
||||
///
|
||||
/// Comment :
|
||||
/// \(comment?.isEmpty == false ? comment! : "No comment")
|
||||
\(isStatic ? "static ": "")func \(name)(\(inputParameters.joined(separator: ", "))) -> String {
|
||||
String(format: \(isStatic ? "Self" : "self").\(name), \(translationArguments.joined(separator: ", ")))
|
||||
}
|
||||
@ -109,13 +116,18 @@ class Definition {
|
||||
func getNSLocalizedStringProperty(forLang lang: String) -> String {
|
||||
guard let translation = translations[lang] else {
|
||||
let error = StringiumError.langNotDefined(lang, name, reference != nil)
|
||||
print(error.localizedDescription)
|
||||
print(error.description)
|
||||
Stringium.exit(withError: error)
|
||||
}
|
||||
|
||||
// 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
|
||||
var method = ""
|
||||
if let parameters = self.getStringParameters(input: translation) {
|
||||
@ -123,7 +135,8 @@ class Definition {
|
||||
translation: translation,
|
||||
isStatic: false,
|
||||
inputParameters: parameters.inputParameters,
|
||||
translationArguments: parameters.translationArguments)
|
||||
translationArguments: parameters.translationArguments,
|
||||
comment: self.comment)
|
||||
}
|
||||
|
||||
return property + method
|
||||
@ -132,12 +145,17 @@ class Definition {
|
||||
func getNSLocalizedStringStaticProperty(forLang lang: String) -> String {
|
||||
guard let translation = translations[lang] else {
|
||||
let error = StringiumError.langNotDefined(lang, name, reference != nil)
|
||||
print(error.localizedDescription)
|
||||
print(error.description)
|
||||
Stringium.exit(withError: error)
|
||||
}
|
||||
|
||||
// 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
|
||||
var method = ""
|
||||
@ -146,7 +164,8 @@ class Definition {
|
||||
translation: translation,
|
||||
isStatic: true,
|
||||
inputParameters: parameters.inputParameters,
|
||||
translationArguments: parameters.translationArguments)
|
||||
translationArguments: parameters.translationArguments,
|
||||
comment: self.comment)
|
||||
}
|
||||
|
||||
return property + method
|
||||
@ -157,13 +176,17 @@ class Definition {
|
||||
func getProperty(forLang lang: String) -> String {
|
||||
guard let translation = translations[lang] else {
|
||||
let error = StringiumError.langNotDefined(lang, name, reference != nil)
|
||||
print(error.localizedDescription)
|
||||
print(error.description)
|
||||
Stringium.exit(withError: error)
|
||||
}
|
||||
|
||||
|
||||
return """
|
||||
/// Translation in \(lang) :
|
||||
/// \(translation)
|
||||
///
|
||||
/// Comment :
|
||||
/// \(comment?.isEmpty == false ? comment! : "No comment")
|
||||
|
||||
var \(name): String {
|
||||
"\(translation)"
|
||||
}
|
||||
@ -173,13 +196,16 @@ class Definition {
|
||||
func getStaticProperty(forLang lang: String) -> String {
|
||||
guard let translation = translations[lang] else {
|
||||
let error = StringiumError.langNotDefined(lang, name, reference != nil)
|
||||
print(error.localizedDescription)
|
||||
print(error.description)
|
||||
Stringium.exit(withError: error)
|
||||
}
|
||||
|
||||
return """
|
||||
/// Translation in \(lang) :
|
||||
/// \(translation)
|
||||
///
|
||||
/// Comment :
|
||||
/// \(comment?.isEmpty == false ? comment! : "No comment")
|
||||
static var \(name): String {
|
||||
"\(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
|
||||
}
|
@ -84,7 +84,7 @@ class TwineFileParser {
|
||||
}
|
||||
}
|
||||
if invalidDefinitionNames.count > 0 {
|
||||
print(" warning:[\(Stringium.toolName)] Found \(invalidDefinitionNames.count) definition (\(invalidDefinitionNames.joined(separator: ", "))")
|
||||
print("warning: [\(Stringium.toolName)] Found \(invalidDefinitionNames.count) definition (\(invalidDefinitionNames.joined(separator: ", "))")
|
||||
}
|
||||
|
||||
return sections
|
||||
|
@ -43,13 +43,26 @@ struct Stringium: ParsableCommand {
|
||||
let sections = TwineFileParser.parse(options.inputFile)
|
||||
|
||||
// Generate strings files
|
||||
StringsFileGenerator.writeStringsFiles(sections: sections,
|
||||
langs: options.langs,
|
||||
defaultLang: options.defaultLang,
|
||||
tags: options.tags,
|
||||
outputPath: options.stringsFileOutputPath,
|
||||
inputFilenameWithoutExt: options.inputFilenameWithoutExt)
|
||||
|
||||
print(options.xcStrings)
|
||||
if !options.xcStrings {
|
||||
print("[\(Self.toolName)] Will generate strings")
|
||||
|
||||
StringsFileGenerator.writeStringsFiles(sections: sections,
|
||||
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
|
||||
StringsFileGenerator.writeExtensionFiles(sections: sections,
|
||||
defaultLang: options.defaultLang,
|
||||
@ -57,8 +70,9 @@ struct Stringium: ParsableCommand {
|
||||
staticVar: options.staticMembers,
|
||||
inputFilename: options.inputFilenameWithoutExt,
|
||||
extensionName: options.extensionName,
|
||||
extensionFilePath: options.extensionFilePath)
|
||||
|
||||
extensionFilePath: options.extensionFilePath,
|
||||
extensionSuffix: options.extensionSuffix)
|
||||
|
||||
print("[\(Self.toolName)] Strings generated")
|
||||
}
|
||||
|
||||
@ -70,20 +84,20 @@ struct Stringium: ParsableCommand {
|
||||
// Input file
|
||||
guard fileManager.fileExists(atPath: options.inputFile) else {
|
||||
let error = StringiumError.fileNotExists(options.inputFile)
|
||||
print(error.localizedDescription)
|
||||
print(error.description)
|
||||
Stringium.exit(withError: error)
|
||||
}
|
||||
|
||||
// Langs
|
||||
guard options.langs.isEmpty == false else {
|
||||
let error = StringiumError.langsListEmpty
|
||||
print(error.localizedDescription)
|
||||
print(error.description)
|
||||
Stringium.exit(withError: error)
|
||||
}
|
||||
|
||||
guard options.langs.contains(options.defaultLang) else {
|
||||
let error = StringiumError.defaultLangsNotInLangs
|
||||
print(error.localizedDescription)
|
||||
print(error.description)
|
||||
Stringium.exit(withError: error)
|
||||
}
|
||||
|
||||
|
@ -14,25 +14,25 @@ enum StringiumError: Error {
|
||||
case writeFile(String, String)
|
||||
case langNotDefined(String, String, Bool)
|
||||
|
||||
var localizedDescription: String {
|
||||
var description: String {
|
||||
switch self {
|
||||
case .fileNotExists(let filename):
|
||||
return " error:[\(Stringium.toolName)] File \(filename) does not exists "
|
||||
return "error: [\(Stringium.toolName)] File \(filename) does not exists "
|
||||
|
||||
case .langsListEmpty:
|
||||
return " error:[\(Stringium.toolName)] Langs list is empty"
|
||||
return "error: [\(Stringium.toolName)] Langs list is empty"
|
||||
|
||||
case .defaultLangsNotInLangs:
|
||||
return " error:[\(Stringium.toolName)] Langs list does not contains the default lang"
|
||||
return "error: [\(Stringium.toolName)] Langs list does not contains the default lang"
|
||||
|
||||
case .writeFile(let subErrorDescription, let filename):
|
||||
return " error:[\(Stringium.toolName)] An error occured while writing content to \(filename): \(subErrorDescription)"
|
||||
return "error: [\(Stringium.toolName)] An error occured while writing content to \(filename): \(subErrorDescription)"
|
||||
|
||||
case .langNotDefined(let lang, let definitionName, let isReference):
|
||||
if isReference {
|
||||
return " error:[\(Stringium.toolName)] Reference are handled only by TwineTool. Please use it or remove reference from you strings file."
|
||||
return "error: [\(Stringium.toolName)] Reference are handled only by Twine. Please use it or remove reference from you strings file."
|
||||
}
|
||||
return " error:[\(Stringium.toolName)] Lang \"\(lang)\" not found for \"\(definitionName)\""
|
||||
return "error: [\(Stringium.toolName)] Lang \"\(lang)\" not found for \"\(definitionName)\""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,8 +11,8 @@ import ArgumentParser
|
||||
struct StringiumOptions: ParsableArguments {
|
||||
@Flag(name: [.customShort("f"), .customShort("F")], help: "Should force generation")
|
||||
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
|
||||
|
||||
@Option(name: .customLong("output-path"), help: "Path where to strings file.", transform: { $0.replaceTiltWithHomeDirectoryPath() })
|
||||
@ -32,12 +32,15 @@ struct StringiumOptions: ParsableArguments {
|
||||
|
||||
@Option(help: "Tell if it will generate static properties or not")
|
||||
var staticMembers: Bool = false
|
||||
|
||||
@Option(help: "Extension name. If not specified, it will generate an String extension. Using default extension name will generate static property.")
|
||||
|
||||
@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.")
|
||||
var extensionName: String = Stringium.defaultExtensionName
|
||||
|
||||
@Option(help: "Extension suffix. Ex: MyApp, it will generate {extensionName}+{extensionSuffix}.swift")
|
||||
var extensionSuffix: String?
|
||||
@Option(help: "Extension suffix: {extensionName}+{extensionSuffix}.swift")
|
||||
var extensionSuffix: String
|
||||
}
|
||||
|
||||
// MARK: - Private var getter
|
||||
@ -68,10 +71,7 @@ extension StringiumOptions {
|
||||
|
||||
extension StringiumOptions {
|
||||
var extensionFileName: String {
|
||||
if let extensionSuffix = extensionSuffix {
|
||||
return "\(extensionName)+\(extensionSuffix).swift"
|
||||
}
|
||||
return "\(extensionName).swift"
|
||||
"\(extensionName)+\(extensionSuffix).swift"
|
||||
}
|
||||
|
||||
var extensionFilePath: String {
|
||||
|
@ -27,4 +27,3 @@ struct Strings: ParsableCommand {
|
||||
}
|
||||
|
||||
//Strings.main()
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user