Publish v1.0
Some checks failed
gitea-openium/resgen.swift/pipeline/head There was a failure building this commit

Reviewed-on: #1
This commit is contained in:
Thibaut Schmitt 2022-10-17 11:24:27 +02:00
parent a99466f258
commit 6203700b0c
87 changed files with 3112 additions and 1223 deletions

View File

@ -1,78 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1310"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "ColorToolCore"
BuildableName = "ColorToolCore"
BlueprintName = "ColorToolCore"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "ColorToolCore"
BuildableName = "ColorToolCore"
BlueprintName = "ColorToolCore"
ReferencedContainer = "container:">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "ColorToolCore"
BuildableName = "ColorToolCore"
BlueprintName = "ColorToolCore"
ReferencedContainer = "container:">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -1,78 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1310"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "FontToolCore"
BuildableName = "FontToolCore"
BlueprintName = "FontToolCore"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "FontToolCore"
BuildableName = "FontToolCore"
BlueprintName = "FontToolCore"
ReferencedContainer = "container:">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "FontToolCore"
BuildableName = "FontToolCore"
BlueprintName = "FontToolCore"
ReferencedContainer = "container:">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -1,78 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1310"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "Imagium"
BuildableName = "Imagium"
BlueprintName = "Imagium"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "Imagium"
BuildableName = "Imagium"
BlueprintName = "Imagium"
ReferencedContainer = "container:">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "Imagium"
BuildableName = "Imagium"
BlueprintName = "Imagium"
ReferencedContainer = "container:">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -1,237 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1310"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "ColorToolCore"
BuildableName = "ColorToolCore"
BlueprintName = "ColorToolCore"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "FontToolCore"
BuildableName = "FontToolCore"
BlueprintName = "FontToolCore"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "ResgenSwift"
BuildableName = "ResgenSwift"
BlueprintName = "ResgenSwift"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "NO"
buildForArchiving = "NO"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "ResgenSwiftTests"
BuildableName = "ResgenSwiftTests"
BlueprintName = "ResgenSwiftTests"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "CLIToolCore"
BuildableName = "CLIToolCore"
BlueprintName = "CLIToolCore"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "StringToolCore"
BuildableName = "StringToolCore"
BlueprintName = "StringToolCore"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "TwineToolCore"
BuildableName = "TwineToolCore"
BlueprintName = "TwineToolCore"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "Strings"
BuildableName = "Strings"
BlueprintName = "Strings"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "FontTool"
BuildableName = "FontTool"
BlueprintName = "FontTool"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "Imagium"
BuildableName = "Imagium"
BlueprintName = "Imagium"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "ToolCore"
BuildableName = "ToolCore"
BlueprintName = "ToolCore"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "ResgenSwiftTests"
BuildableName = "ResgenSwiftTests"
BlueprintName = "ResgenSwiftTests"
ReferencedContainer = "container:">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "Imagium"
BuildableName = "Imagium"
BlueprintName = "Imagium"
ReferencedContainer = "container:">
</BuildableReference>
</BuildableProductRunnable>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "ColorToolCore"
BuildableName = "ColorToolCore"
BlueprintName = "ColorToolCore"
ReferencedContainer = "container:">
</BuildableReference>
</MacroExpansion>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "Imagium"
BuildableName = "Imagium"
BlueprintName = "Imagium"
ReferencedContainer = "container:">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -34,6 +34,34 @@
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "ToolCore"
BuildableName = "ToolCore"
BlueprintName = "ToolCore"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "ResgenSwift_ResgenSwift"
BuildableName = "ResgenSwift_ResgenSwift"
BlueprintName = "ResgenSwift_ResgenSwift"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction

4
Jenkinsfile vendored
View File

@ -1,6 +1,8 @@
library "openiumpipeline"
//env.DEVELOPER_DIR="/Applications/Xcode_12.4.app/Contents/Developer"
env.DEVELOPER_DIR="/Applications/Xcode_13.3.0.app/Contents/Developer"
//env.SIMULATOR_DEVICE_TYPES="iPad--7th-generation-"
env.IS_PACKAGE_SWIFT=1
env.TARGETS_MACOS=1
iOSpipeline()

View File

@ -9,6 +9,15 @@
"revision": "e1465042f195f374b94f915ba8ca49de24300a0d",
"version": "1.0.2"
}
},
{
"package": "Yams",
"repositoryURL": "https://github.com/jpsim/Yams.git",
"state": {
"branch": null,
"revision": "01835dc202670b5bb90d07f3eae41867e9ed29f6",
"version": "5.0.1"
}
}
]
},

View File

@ -1,53 +1,31 @@
// swift-tools-version:5.3
// swift-tools-version:5.6
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "ResgenSwift",
platforms: [.macOS(.v10_12)],
platforms: [.macOS(.v12)],
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/apple/swift-argument-parser", from: "1.0.0"),
.package(url: "https://github.com/jpsim/Yams.git", from: "5.0.1")
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
.executableTarget(
name: "ResgenSwift",
dependencies: ["FontTool", "ColorTool", "Strings", "Imagium"]
),
.target(
name: "FontTool",
dependencies: [
"ToolCore",
.product(name: "ArgumentParser", package: "swift-argument-parser")
]
),
.target(
name: "ColorTool",
dependencies: [
"ToolCore",
.product(name: "ArgumentParser", package: "swift-argument-parser")
]
),
.target(
name: "Strings",
dependencies: [
"ToolCore",
.product(name: "ArgumentParser", package: "swift-argument-parser")
],
sources: ["."] // Force include all subdirectories
),
.target(
name: "Imagium",
dependencies: [
"ToolCore",
.product(name: "ArgumentParser", package: "swift-argument-parser")
.product(name: "ArgumentParser", package: "swift-argument-parser"),
"Yams"
]
),
// Helper targets
.target(name: "ToolCore"),
// Test targets
.testTarget(
name: "ResgenSwiftTests",

183
README.md
View File

@ -4,18 +4,19 @@ ResgenSwift is a package, fully written in Swift, to help you automatize ressour
> 🧐 For all commands, see samples files in `SampleFiles`
# Fonts
## 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`.
**Example**
```
swift run -c release FontTool $FORCE_FLAG "./Fonts/fonts.txt" \
```sh
swift run -c release ResgenSwift fonts $FORCE_FLAG "./Fonts/fonts.txt" \
--extension-output-path "./Fonts/Generated" \
--extension-name "AppFont" \
--extension-suffix "GreatApp"
--extension-suffix "GreatApp" \
--static-members true
```
**Parameters**
@ -25,21 +26,21 @@ swift run -c release FontTool $FORCE_FLAG "./Fonts/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`)
> ⚠️ If extension name is not set or is `UIFont`, it will generate static property on `UIFont` instead of method in your custom class.
6. `--static-members` *(optional)*: generate static properties or not
# Colors
## Colors
Colors generator generates an extension of `UIColor` (or a custom class) along with colorsets in specified xcassets.
```
swift run -c release ColorTool $FORCE_FLAG "./Colors/colors.txt" \
```sh
swift run -c release ResgenSwift colors $FORCE_FLAG "./Colors/colors.txt" \
--style all \
--xcassets-path "./Colors/colors.xcassets" \
--extension-output-path "./Colors/Generated/" \
--extension-name "AppColor" \
--extension-suffix "GreatApp"
--extension-suffix "GreatApp" \
--static-members true
```
**Parameters**
@ -50,23 +51,22 @@ swift run -c release ColorTool $FORCE_FLAG "./Colors/colors.txt" \
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
> ⚠️ If extension name is not set or is `UIColor`, it will generate static property on `UIColor`.
# Strings
## Strings
Strings command allows to generate `strings` files along with extensions to access those strings easily. It can do it 2 ways: Twine and Stringium. It is not recommended to use Twine except on legacy projects or while migrating to ResgenSwift, because it use https://github.com/openium/twine. Using Stringium is recommended because it does not required external dependency and allow more customisation.
## Twine (not recommended)
### Twine (not recommended)
```
swift run -c release Strings twine $FORCE_FLAG "./Twine/strings.txt" \
```sh
swift run -c release ResgenSwift strings twine $FORCE_FLAG "./Twine/strings.txt" \
--output-path "./Twine/Generated" \
--langs "fr en en-us" \
--default-lang "en" \
--extension-output-path "./Twine/Generated"
```
```
**Parameters**
@ -76,16 +76,17 @@ swift run -c release Strings twine $FORCE_FLAG "./Twine/strings.txt" \
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
## Stringium (recommended)
### Stringium (recommended)
```
swift run -c release Strings stringium $FORCE_FLAG "./Strings/strings.txt" \
```sh
swift run -c release ResgenSwift strings stringium $FORCE_FLAG "./Strings/strings.txt" \
--output-path "./Strings/Generated" \
--langs "fr en en-us" \
--default-lang "en" \
--extension-output-path "./Strings/Generated" \
--extension-name "AppString" \
--extension-suffix "GreatApp"
--extension-suffix "GreatApp" \
--static-members true
```
**Parameters**
@ -97,19 +98,20 @@ swift run -c release Strings stringium $FORCE_FLAG "./Strings/strings.txt" \
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`)
7. `--static-members` *(optional)*: generate static properties or not
> ⚠️ If extension name is not set or is `String`, it will generate static property on `String`.
# Tags
## Tags
Tags is also a subcommand of `Strings`. Input files are formatted the same way. Tags will generate properties which return exactly what is specified in the input file. It was designed to be used for analytics purpose and to be shared with any other platform to have the same analytics keys.
```
swift run -c release Strings tags $FORCE_FLAG "./Tags/tags.txt" \
```sh
swift run -c release ResgenSwift strings tags $FORCE_FLAG "./Tags/tags.txt" \
--lang "ium" \
--extension-output-path "./Tags/Generated" \
--extension-name "AppTags" \
--extension-suffix "GreatApp"
--extension-suffix "GreatApp" \
--static-members true
```
**Parameters**
@ -120,19 +122,21 @@ swift run -c release Strings tags $FORCE_FLAG "./Tags/tags.txt" \
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: `AppTags+GreatApp.swift`)
7. `--static-members` *(optional)*: generate static properties or not
> ⚠️ If extension name is not set or is `Tags`, it will generate static property on `Tags`. This class may not exists in your project, just create an empty class named `Tags` is necessary.
> ⚠️ If extension name is not set or is `Tags`, it will generate the following typaloas `typealias Tags = String`.
# Images
## Images
Images generator will generate images assets along with extensions to access those images easily.
```
swift run -c release Imagium $FORCE_FLAG "./Images/images.txt" \
```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-suffix "GreatApp"
--extension-suffix "GreatApp" \
--static-members true
```
**Parameters**
@ -143,9 +147,120 @@ swift run -c release Imagium $FORCE_FLAG "./Images/images.txt" \
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: `AppImage+GreatApp.swift`)
7. `--static-members` *(optional)*: generate static properties or not
> ⚠️ If extension name is not set or is `UIImage`, it will generate static property on `UIImage`.
# TODO
## All at once
[ ] Allow static variable generation on custom extension
Another command exists to generate all ressources at the same time: `generate`. It use the following commands: `Fonts`, `Colors`, `Strings/Stringium`, `Strings/Tags`, `Images`.
All parameters can be specified in a configuration file in `Yaml`:
> Order of configuration types does not matter. Order them to fit your needs.
```yaml
---
colors:
-
inputFile: String
style: [light/all]
xcassetsPath: String
extensionOutputPath: String
extensionName: String?
extensionSuffix: String?
staticMembers: Bool?
fonts:
-
inputFile: String
extensionOutputPath: String
extensionName: String?
extensionSuffix: String?
staticMembers: Bool?
images:
-
inputFile: String
xcassetsPath: String
extensionOutputPath: String
extensionName: String?
extensionSuffix: String?
staticMembers: Bool?
strings:
-
inputFile: String
outputPath: String
langs: String
defaultLang: String
extensionOutputPath: String
extensionName: String?
extensionSuffix: String?
staticMembers: Bool?
tags:
-
inputFile: String
lang: String
extensionOutputPath: String
extensionName: String?
extensionSuffix: String?
staticMembers: Bool?
```
### Multiple configurations
In some case, you may need to have 2 colors files in your projects. You will need 2 colors configurations. Every configuration type is an array and can contains as many configurations as you need.
Sample for 2 colors configurations:
```yaml
...
colors:
-
inputFile: String
style: [light/all]
xcassetsPath: String
extensionOutputPath: String
extensionName: String?
extensionSuffix: String?
staticMembers: Bool?
-
inputFile: String
style: [light/all]
xcassetsPath: String
extensionOutputPath: String
extensionName: String?
extensionSuffix: String?
staticMembers: Bool?
...
```
### No configuration
In some case, you may not need to generate tags for example. You must specified `tags` as an empty array :
```yaml
...
tags: []
...
```
### Usage
```sh
swift run -c release ResgenSwift generate path/to/configuration.yml --project-directory ${PROJECT_DIR}
```
> ⚠️ Every path in `configuration.yml` will be prepended by content of `--project-directory` if they are relative path (not starting with `/`)
## Installation
Simple run `./install.sh`. Binary will be install in `/usr/local/bin`. Then, use ResgenSwift like any other command:
Example:
```sh
ResgenSwift generate path/to/configuration.yml --project-directory ${PROJECT_DIR}
```

View File

@ -1,35 +0,0 @@
//
// ColorToolError.swift
//
//
// Created by Thibaut Schmitt on 20/12/2021.
//
import Foundation
enum ColorToolError: Error {
case badFormat(String)
case writeAsset(String)
case writeExtension(String, String)
case fileNotExists(String)
case badColorDefinition(String, String)
var description: String {
switch self {
case .badFormat(let info):
return "error:[ColorTool] Bad line format: \(info). Accepted format are: colorName=\"#RGB/#ARGB\"; colorName \"#RGB/#ARGB\"; colorName \"#RGB/#ARGB\" \"#RGB/#ARGB\""
case .writeAsset(let info):
return "error:[ColorTool] An error occured while writing color in Xcasset: \(info)"
case .writeExtension(let filename, let info):
return "error:[ColorTool] An error occured while writing extension in \(filename): \(info)"
case .fileNotExists(let filename):
return "error:[ColorTool] File \(filename) does not exists"
case .badColorDefinition(let lightColor, let darkColor):
return "error:[ColorTool]One of these two colors has invalid synthax: -\(lightColor)- or -\(darkColor)-"
}
}
}

View File

@ -1,49 +0,0 @@
//
// File.swift
//
//
// Created by Thibaut Schmitt on 29/08/2022.
//
import Foundation
class ColorFileParser {
static func parse(_ inputFile: String, colorStyle: ColorStyle) -> [ParsedColor] {
// Get content of input file
let inputFileContent = try! String(contentsOfFile: inputFile, encoding: .utf8)
let colorsByLines = inputFileContent.components(separatedBy: CharacterSet.newlines)
// Iterate on each line of input file
return colorsByLines.enumerated().compactMap { lineNumber, colorLine in
// Required format:
// colorName="#RGB/#ARGB", colorName "#RGB/#ARGB", colorName "#RGB/#ARGB" "#RGB/#ARGB"
let colorLineCleanedUp = colorLine
.removeTrailingWhitespace()
.replacingOccurrences(of: "=", with: "") // Keep compat with current file format
guard colorLineCleanedUp.hasPrefix("#") == false, colorLineCleanedUp.isEmpty == false else {
print("[\(ColorTool.toolName)] ⚠️ BadFormat or empty line (line number: \(lineNumber + 1)). Skip this line")
return nil
}
let colorContent = colorLineCleanedUp.split(separator: " ")
guard colorContent.count >= 2 else {
let error = ColorToolError.badFormat(colorLine)
print(error.localizedDescription)
ColorTool.exit(withError: error)
}
switch colorStyle {
case .light:
return ParsedColor(name: String(colorContent[0]), light: String(colorContent[1]), dark: String(colorContent[1]))
case .all:
if colorContent.count == 3 {
return ParsedColor(name: String(colorContent[0]), light: String(colorContent[1]), dark: String(colorContent[2]))
}
return ParsedColor(name: String(colorContent[0]), light: String(colorContent[1]), dark: String(colorContent[1]))
}
}
}
}

View File

@ -1,87 +0,0 @@
//
// ImageExtensionGenerator.swift
//
//
// Created by Thibaut Schmitt on 14/02/2022.
//
import ToolCore
import Foundation
class ImageExtensionGenerator {
// MARK: - Extension files
static func writeStringsFiles(images: [ParsedImage], staticVar: Bool, inputFilename: String, extensionName: String, extensionFilePath: String) {
// Get header/footer
let extensionHeader = Self.getHeader(inputFilename: inputFilename, extensionClassname: extensionName)
let extensionFooter = Self.getFooter()
// Create content
let extensionContent: String = {
var content = ""
images.forEach { img in
if staticVar {
content += "\n\(img.getStaticImageProperty())"
} else {
content += "\n\(img.getImageProperty())"
}
content += "\n "
}
return content
}()
// Create file if not exists
let fileManager = FileManager()
if fileManager.fileExists(atPath: extensionFilePath) == false {
Shell.shell("touch", "\(extensionFilePath)")
}
// Generate extension
Self.generateExtensionFile(extensionFilePath: extensionFilePath, extensionHeader, extensionContent, extensionFooter)
}
// MARK: - pragm
private static func generateExtensionFile(extensionFilePath: String, _ args: String...) {
// Create file if not exists
let fileManager = FileManager()
if fileManager.fileExists(atPath: extensionFilePath) == false {
Shell.shell("touch", "\(extensionFilePath)")
}
// Create extension content
let extensionContent = args.joined(separator: "\n")
// Write content
let extensionFilePathURL = URL(fileURLWithPath: extensionFilePath)
do {
try extensionContent.write(to: extensionFilePathURL, atomically: true, encoding: .utf8)
} catch (let error) {
let error = ImagiumError.writeFile(extensionFilePath, error.localizedDescription)
print(error.localizedDescription)
Imagium.exit(withError: error)
}
}
private static func getHeader(inputFilename: String, extensionClassname: String) -> String {
"""
// Generated by ResgenSwift.Imagium \(ResgenSwiftVersion)
// Images from \(inputFilename)
import UIKit
extension \(extensionClassname) {
"""
}
private static func getFooter() -> String {
"""
}
"""
}
}
//@objc var onboarding_foreground3: UIImage {
// return UIImage(named: "onboarding_foreground3")!
// }

View File

@ -1,43 +0,0 @@
//
// ImagiumError.swift
//
//
// Created by Thibaut Schmitt on 24/01/2022.
//
import Foundation
enum ImagiumError: Error {
case inputFolderNotFound(String)
case fileNotExists(String)
case unknownImageExtension(String)
case getFileAttributed(String, String)
case rsvgConvertNotFound
case writeFile(String, String)
case unknown(String)
var localizedDescription: String {
switch self {
case .inputFolderNotFound(let inputFolder):
return " error:[\(Imagium.toolName)] Input folder not found: \(inputFolder)"
case .fileNotExists(let filename):
return " error:[\(Imagium.toolName)] File \(filename) does not exists"
case .unknownImageExtension(let filename):
return " error:[\(Imagium.toolName)] File \(filename) have an unhandled file extension. Cannot generate image."
case .getFileAttributed(let filename, let errorDescription):
return " error:[\(Imagium.toolName)] Getting file attributes of \(filename) failed with error: \(errorDescription)"
case .rsvgConvertNotFound:
return " error:[\(Imagium.toolName)] Can't find rsvg-convert (can be installed with 'brew remove imagemagick && brew install imagemagick --with-librsvg')"
case .writeFile(let subErrorDescription, let filename):
return " error:[\(Imagium.toolName)] An error occured while writing content to \(filename): \(subErrorDescription)"
case .unknown(let errorDescription):
return " error:[\(Imagium.toolName)] Unknown error: \(errorDescription)"
}
}
}

View File

@ -9,7 +9,7 @@ import ToolCore
import Foundation
import ArgumentParser
struct ColorTool: ParsableCommand {
struct Colors: ParsableCommand {
// MARK: - CommandConfiguration
@ -20,33 +20,19 @@ struct ColorTool: ParsableCommand {
// MARK: - Static
static let toolName = "ColorTool"
static let toolName = "Color"
static let defaultExtensionName = "UIColor"
static let assetsColorsFolderName = "Colors"
// MARK: - Properties
var extensionFileName: String {
if options.extensionSuffix.isEmpty == false {
return "\(options.extensionName)+\(options.extensionSuffix).swift"
}
return "\(options.extensionName).swift"
}
var extensionFilePath: String { "\(options.extensionOutputPath)/\(extensionFileName)" }
var generateStaticVariable: Bool {
options.extensionName == Self.defaultExtensionName
}
// MARK: - Command options
@OptionGroup var options: ColorToolOptions
@OptionGroup var options: ColorsToolOptions
// MARK: - Run
public func run() throws {
print("[\(Self.toolName)] Starting colors generation")
print("[\(Self.toolName)] Will use inputFile \(options.inputFile) to generate \(options.colorStyle) colors in xcassets \(options.xcassetsPath)")
// Check requirements
guard checkRequirements() else { return }
@ -54,21 +40,24 @@ struct ColorTool: ParsableCommand {
// Delete current colors
deleteCurrentColors()
// Get colors to generate
let parsedColors = ColorFileParser.parse(options.inputFile,
colorStyle: options.colorStyle)
// -> Time: 0.0020350217819213867 seconds
// Generate all colors in xcassets
ColorXcassetHelper.generateXcassetColors(colors: parsedColors,
to: options.xcassetsPath)
// -> Time: 3.4505380392074585 seconds
// Generate extension
ColorExtensionGenerator.writeExtensionFile(colors: parsedColors,
staticVar: generateStaticVariable,
staticVar: options.staticMembers,
extensionName: options.extensionName,
extensionFilePath: extensionFilePath)
extensionFilePath: options.extensionFilePath)
// -> Time: 0.0010340213775634766 seconds
print("[\(Self.toolName)] Colors generated")
}
@ -79,20 +68,22 @@ struct ColorTool: ParsableCommand {
// Check if input file exists
guard fileManager.fileExists(atPath: options.inputFile) else {
let error = ColorToolError.fileNotExists(options.inputFile)
let error = ColorsToolError.fileNotExists(options.inputFile)
print(error.localizedDescription)
ColorTool.exit(withError: error)
Colors.exit(withError: error)
}
// Check if xcassets file exists
guard fileManager.fileExists(atPath: options.xcassetsPath) else {
let error = ColorToolError.fileNotExists(options.xcassetsPath)
let error = ColorsToolError.fileNotExists(options.xcassetsPath)
print(error.localizedDescription)
ColorTool.exit(withError: error)
Colors.exit(withError: error)
}
// Check if needed to regenerate
guard GeneratorChecker.shouldGenerate(force: options.forceGeneration, inputFilePath: options.inputFile, extensionFilePath: extensionFilePath) else {
guard GeneratorChecker.shouldGenerate(force: options.forceGeneration,
inputFilePath: options.inputFile,
extensionFilePath: options.extensionFilePath) else {
print("[\(Self.toolName)] Colors are already up to date :) ")
return false
}
@ -103,21 +94,17 @@ struct ColorTool: ParsableCommand {
// MARK: - Helpers
private func deleteCurrentColors() {
Shell.shell("rm", "-rf", "\(options.xcassetsPath)/Colors/*")
let fileManager = FileManager()
let assetsColorPath = "\(options.xcassetsPath)/Colors"
if fileManager.fileExists(atPath: assetsColorPath) {
do {
try fileManager.removeItem(atPath: assetsColorPath)
} catch {
let error = ColorsToolError.deleteExistingColors("\(options.xcassetsPath)/Colors")
print(error.localizedDescription)
Colors.exit(withError: error)
}
}
}
}
ColorTool.main()
/*
Command samples:
1. UIColor extension without suffix
swift run -c release ColorToolCore -f ./SampleFiles/Colors/sampleColors1.txt --style all --xcassets-path "./SampleFiles/Colors/colors.xcassets" --extension-output-path "./SampleFiles/Colors/Generated/" --extension-name "UIColor"
2. UIColor extension with custom suffix
swift run -c release ColorToolCore -f ./SampleFiles/Colors/sampleColors1.txt --style all --xcassets-path "./SampleFiles/Colors/colors.xcassets" --extension-output-path "./SampleFiles/Colors/Generated/" --extension-name "UIColor" --extension-suffix "SampleApp"
3. Custom extension with only light theme colors (R2Color)
swift run -c release ColorToolCore -f ./SampleFiles/Colors/sampleColors1.txt --style light --xcassets-path "./SampleFiles/Colors/colors.xcassets" --extension-output-path "./SampleFiles/Colors/Generated/" --extension-name "R2Color"
*/

View File

@ -0,0 +1,43 @@
//
// ColorsToolError.swift
//
//
// Created by Thibaut Schmitt on 20/12/2021.
//
import Foundation
enum ColorsToolError: Error {
case badFormat(String)
case writeAsset(String)
case createAssetFolder(String)
case writeExtension(String, String)
case fileNotExists(String)
case badColorDefinition(String, String)
case deleteExistingColors(String)
var description: String {
switch self {
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\""
case .writeAsset(let 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)`"
case .writeExtension(let filename, let 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"
case .badColorDefinition(let lightColor, let 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)`"
}
}
}

View File

@ -1,5 +1,5 @@
//
// ColorToolOptions.swift
// ColorsToolOptions.swift
//
//
// Created by Thibaut Schmitt on 17/01/2022.
@ -8,7 +8,7 @@
import Foundation
import ArgumentParser
struct ColorToolOptions: ParsableArguments {
struct ColorsToolOptions: ParsableArguments {
@Flag(name: [.customShort("f"), .customShort("F")], help: "Should force generation")
var forceGeneration = false
@ -24,15 +24,31 @@ struct ColorToolOptions: ParsableArguments {
@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 an UIColor extension. Using default extension name will generate static property.")
var extensionName: String = ColorTool.defaultExtensionName
var extensionName: String = Colors.defaultExtensionName
@Option(help: "Extension suffix. Ex: MyApp, it will generate {extensionName}+ColorsMyApp.swift")
var extensionSuffix: String = ""
var extensionSuffix: String?
}
extension ColorToolOptions {
// MARK: - Computed var
extension ColorsToolOptions {
var colorStyle: ColorStyle {
ColorStyle(rawValue: style) ?? .all
}
var extensionFileName: String {
if let extensionSuffix = extensionSuffix {
return "\(extensionName)+\(extensionSuffix).swift"
}
return "\(extensionName).swift"
}
var extensionFilePath: String {
"\(extensionOutputPath)/\(extensionFileName)"
}
}

View File

@ -14,34 +14,34 @@ struct ColorExtensionGenerator {
let extensionClassname: String
static func writeExtensionFile(colors: [ParsedColor], staticVar: Bool, extensionName: String, extensionFilePath: String) {
// Create file if not exists
let fileManager = FileManager()
if fileManager.fileExists(atPath: extensionFilePath) == false {
Shell.shell("touch", "\(extensionFilePath)")
}
// Create extension content
let extensionContent = [
let extensionContent = Self.getExtensionContent(colors: colors,
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 = ColorsToolError.writeExtension(extensionFilePath, error.localizedDescription)
print(error.localizedDescription)
Colors.exit(withError: error)
}
}
static func getExtensionContent(colors: [ParsedColor], staticVar: Bool, extensionName: String) -> String {
[
Self.getHeader(extensionClassname: extensionName),
Self.getProperties(for: colors, withStaticVar: staticVar),
Self.getFooter()
]
.joined(separator: "\n")
// Write content
let extensionFilePathURL = URL(fileURLWithPath: extensionFilePath)
do {
try extensionContent.write(to: extensionFilePathURL, atomically: true, encoding: .utf8)
} catch (let error) {
let error = ColorToolError.writeExtension(extensionFilePath, error.localizedDescription)
print(error.localizedDescription)
ColorTool.exit(withError: error)
}
}
private static func getHeader(extensionClassname: String) -> String {
"""
// Generated by ResgenSwift.\(ColorTool.toolName) \(ResgenSwiftVersion)
// Generated by ResgenSwift.\(Colors.toolName) \(ResgenSwiftVersion)
import UIKit

View File

@ -20,20 +20,28 @@ struct ColorXcassetHelper {
private static func generateColorSetAssets(from color: ParsedColor, to xcassetsPath: String) {
// Create ColorSet
let colorSetPath = "\(xcassetsPath)/Colors/\(color.name).colorset"
Shell.shell("mkdir", "-p", "\(colorSetPath)")
// Create Contents.json in ColorSet
let contentsJsonPath = "\(colorSetPath)/Contents.json"
Shell.shell("touch", "\(contentsJsonPath)")
let fileManager = FileManager()
if fileManager.fileExists(atPath: colorSetPath) == false {
do {
try fileManager.createDirectory(atPath: colorSetPath,
withIntermediateDirectories: true)
} catch {
let error = ColorsToolError.createAssetFolder(colorSetPath)
print(error.localizedDescription)
Colors.exit(withError: error)
}
}
// Write content in Contents.json
let contentsJsonPathURL = URL(fileURLWithPath: contentsJsonPath)
do {
try color.contentsJSON().write(to: contentsJsonPathURL, atomically: true, encoding: .utf8)
try color.contentsJSON().write(to: contentsJsonPathURL, atomically: false, encoding: .utf8)
} catch (let error) {
let error = ColorToolError.writeAsset(error.localizedDescription)
let error = ColorsToolError.writeAsset(error.localizedDescription)
print(error.localizedDescription)
ColorTool.exit(withError: error)
Colors.exit(withError: error)
}
}
}

View File

@ -1,5 +1,5 @@
//
// File.swift
// ColorStyle.swift
//
//
// Created by Thibaut Schmitt on 29/08/2022.

View File

@ -25,9 +25,9 @@ struct ParsedColor {
}
guard allComponents.contains(true) == false else {
let error = ColorToolError.badColorDefinition(light, dark)
let error = ColorsToolError.badColorDefinition(light, dark)
print(error.localizedDescription)
ColorTool.exit(withError: error)
Colors.exit(withError: error)
}
return """

View File

@ -0,0 +1,56 @@
//
// ColorFileParser.swift
//
//
// Created by Thibaut Schmitt on 29/08/2022.
//
import Foundation
class ColorFileParser {
static func parse(_ inputFile: String, colorStyle: ColorStyle) -> [ParsedColor] {
// Get content of input file
let inputFileContent = try! String(contentsOfFile: inputFile, encoding: .utf8)
let colorsByLines = inputFileContent.components(separatedBy: CharacterSet.newlines)
// Iterate on each line of input file
return parseLines(lines: colorsByLines, colorStyle: colorStyle)
}
static func parseLines(lines: [String], colorStyle: ColorStyle) -> [ParsedColor] {
lines
.enumerated()
.compactMap { lineNumber, colorLine in
// Required format:
// colorName = "#RGB/#ARGB", colorName "#RGB/#ARGB", colorName "#RGB/#ARGB" "#RGB/#ARGB"
let colorLineCleanedUp = colorLine
.removeLeadingWhitespace()
.removeTrailingWhitespace()
.replacingOccurrences(of: "=", with: "") // Keep compat with current file format
guard colorLineCleanedUp.hasPrefix("#") == false, colorLineCleanedUp.isEmpty == false else {
// debugPrint("[\(Colors.toolName)] BadFormat or empty line (line number: \(lineNumber + 1)). Skip this line")
return nil
}
let colorContent = colorLineCleanedUp.split(separator: " ")
guard colorContent.count >= 2 else {
let error = ColorsToolError.badFormat(colorLine)
print(error.localizedDescription)
Colors.exit(withError: error)
}
switch colorStyle {
case .light:
return ParsedColor(name: String(colorContent[0]), light: String(colorContent[1]), dark: String(colorContent[1]))
case .all:
if colorContent.count == 3 {
return ParsedColor(name: String(colorContent[0]), light: String(colorContent[1]), dark: String(colorContent[2]))
}
return ParsedColor(name: String(colorContent[0]), light: String(colorContent[1]), dark: String(colorContent[1]))
}
}
}
}

View File

@ -1,5 +1,5 @@
//
// FontOptions.swift
// FontsOptions.swift
//
//
// Created by Thibaut Schmitt on 17/01/2022.
@ -8,7 +8,7 @@
import Foundation
import ArgumentParser
struct FontOptions: ParsableArguments {
struct FontsOptions: ParsableArguments {
@Flag(name: [.customShort("f"), .customShort("F")], help: "Should force generation")
var forceGeneration = false
@ -18,9 +18,27 @@ struct FontOptions: ParsableArguments {
@Option(help: "Path where to generate the extension.", transform: { $0.replaceTiltWithHomeDirectoryPath() })
var extensionOutputPath: String
@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.")
var extensionName: String = FontTool.defaultExtensionName
var extensionName: String = Fonts.defaultExtensionName
@Option(help: "Extension suffix. Ex: MyApp, it will generate {extensionName}+FontsMyApp.swift")
var extensionSuffix: String = ""
}
// MARK: - Computed var
extension FontsOptions {
var extensionFileName: String {
if extensionSuffix.isEmpty == false {
return "\(extensionName)+\(extensionSuffix).swift"
}
return "\(extensionName).swift"
}
var extensionFilePath: String {
"\(extensionOutputPath)/\(extensionFileName)"
}
}

View File

@ -1,5 +1,5 @@
//
// FontTool.swift
// Fonts.swift
//
//
// Created by Thibaut Schmitt on 13/12/2021.
@ -9,7 +9,7 @@ import ToolCore
import Foundation
import ArgumentParser
struct FontTool: ParsableCommand {
struct Fonts: ParsableCommand {
// MARK: - CommandConfiguration
@ -20,30 +20,18 @@ struct FontTool: ParsableCommand {
// MARK: - Static
static let toolName = "FontTool"
static let toolName = "Fonts"
static let defaultExtensionName = "UIFont"
// MARK: - Properties
var extensionFileName: String {
if options.extensionSuffix.isEmpty == false {
return "\(options.extensionName)+\(options.extensionSuffix).swift"
}
return "\(options.extensionName).swift"
}
var extensionFilePath: String { "\(options.extensionOutputPath)/\(extensionFileName)" }
var generateStaticVariable: Bool {
options.extensionName == Self.defaultExtensionName
}
// MARK: - Command Options
@OptionGroup var options: FontOptions
@OptionGroup var options: FontsOptions
// MARK: - Run
public func run() throws {
print("[\(Self.toolName)] Starting fonts generation")
print("[\(Self.toolName)] Will use inputFile \(options.inputFile) to generate fonts")
// Check requirements
guard checkRequirements() else { return }
@ -55,14 +43,14 @@ struct FontTool: ParsableCommand {
// Get real font names
let inputFolder = URL(fileURLWithPath: options.inputFile).deletingLastPathComponent().relativePath
let fontsNames = FontToolHelper.getFontPostScriptName(for: fontsToGenerate,
let fontsNames = FontsToolHelper.getFontPostScriptName(for: fontsToGenerate,
inputFolder: inputFolder)
// Generate extension
FontExtensionGenerator.writeExtensionFile(fontsNames: fontsNames,
staticVar: generateStaticVariable,
staticVar: options.staticMembers,
extensionName: options.extensionName,
extensionFilePath: extensionFilePath)
extensionFilePath: options.extensionFilePath)
print("Info.plist information:")
print("\(FontPlistGenerator.generatePlistUIAppsFontContent(for: fontsNames))")
@ -77,13 +65,15 @@ struct FontTool: ParsableCommand {
// Check input file exists
guard fileManager.fileExists(atPath: options.inputFile) else {
let error = FontToolError.fileNotExists(options.inputFile)
let error = FontsToolError.fileNotExists(options.inputFile)
print(error.localizedDescription)
FontTool.exit(withError: error)
Fonts.exit(withError: error)
}
// Check if needed to regenerate
guard GeneratorChecker.shouldGenerate(force: options.forceGeneration, inputFilePath: options.inputFile, extensionFilePath: extensionFilePath) else {
guard GeneratorChecker.shouldGenerate(force: options.forceGeneration,
inputFilePath: options.inputFile,
extensionFilePath: options.extensionFilePath) else {
print("[\(Self.toolName)] Fonts are already up to date :) ")
return false
}
@ -91,5 +81,3 @@ struct FontTool: ParsableCommand {
return true
}
}
FontTool.main()

View File

@ -1,5 +1,5 @@
//
// FontToolError.swift
// FontsToolError.swift
//
//
// Created by Thibaut Schmitt on 13/12/2021.
@ -7,7 +7,7 @@
import Foundation
enum FontToolError: Error {
enum FontsToolError: Error {
case fcScan(String, Int32, String?)
case inputFolderNotFound(String)
case fileNotExists(String)
@ -16,16 +16,16 @@ enum FontToolError: Error {
var localizedDescription: String {
switch self {
case .fcScan(let path, let code, let output):
return "error:[\(FontTool.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:[\(FontTool.toolName)] Input folder not found: \(inputFolder)"
return " error:[\(Fonts.toolName)] Input folder not found: \(inputFolder)"
case .fileNotExists(let filename):
return " error:[\(FontTool.toolName)] File \(filename) does not exists"
return " error:[\(Fonts.toolName)] File \(filename) does not exists"
case .writeExtension(let filename, let info):
return "error:[\(FontTool.toolName)] An error occured while writing extension in \(filename): \(info)"
return "error:[\(Fonts.toolName)] An error occured while writing extension in \(filename): \(info)"
}
}
}

View File

@ -1,5 +1,5 @@
//
// FontToolHelper.swift
// FontsToolHelper.swift
//
//
// Created by Thibaut Schmitt on 13/12/2021.
@ -8,7 +8,7 @@
import Foundation
import ToolCore
class FontToolHelper {
class FontsToolHelper {
static func getFontPostScriptName(for fonts: [String], inputFolder: String) -> [FontName] {
let fontsFilenames = Self.getFontsFilenames(fromInputFolder: inputFolder)
@ -38,9 +38,9 @@ class FontToolHelper {
// Get a enumerator for all files
let fileManager = FileManager()
guard fileManager.fileExists(atPath: inputFolder) else {
let error = FontToolError.inputFolderNotFound(inputFolder)
let error = FontsToolError.inputFolderNotFound(inputFolder)
print(error.localizedDescription)
FontTool.exit(withError: error)
Fonts.exit(withError: error)
}
let enumerator: FileManager.DirectoryEnumerator = fileManager.enumerator(atPath: inputFolder)!
@ -60,12 +60,12 @@ class FontToolHelper {
private static func getFontName(atPath path: String) -> String {
//print("fc-scan --format %{postscriptname} \(path)")
// Get real font name
let task = Shell.shell("fc-scan", "--format", "%{postscriptname}", path)
let task = Shell.shell(["fc-scan", "--format", "%{postscriptname}", path])
guard let fontName = task.output, task.terminationStatus == 0 else {
let error = FontToolError.fcScan(path, task.terminationStatus, task.output)
let error = FontsToolError.fcScan(path, task.terminationStatus, task.output)
print(error.localizedDescription)
FontTool.exit(withError: error)
Fonts.exit(withError: error)
}
return fontName

View File

@ -1,5 +1,5 @@
//
// File.swift
// FontPlistGenerator.swift
//
//
// Created by Thibaut Schmitt on 29/08/2022.
@ -15,7 +15,7 @@ class FontPlistGenerator {
.forEach {
plistData += "\t\t<string>\($0)</string>\n"
}
plistData += "\t</array>\n*/"
plistData += "\t</array>"
return plistData
}

View File

@ -11,35 +11,35 @@ import ToolCore
class FontExtensionGenerator {
static func writeExtensionFile(fontsNames: [String], staticVar: Bool, extensionName: String, extensionFilePath: String) {
// Check file if not exists
let fileManager = FileManager()
if fileManager.fileExists(atPath: extensionFilePath) == false {
Shell.shell("touch", "\(extensionFilePath)")
}
// Create extension content
let extensionContent = [
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")
// Write content
let extensionFilePathURL = URL(fileURLWithPath: extensionFilePath)
do {
try extensionContent.write(to: extensionFilePathURL, atomically: true, encoding: .utf8)
} catch (let error) {
let error = FontToolError.writeExtension(extensionFilePath, error.localizedDescription)
print(error.localizedDescription)
FontTool.exit(withError: error)
}
}
private static func getHeader(extensionClassname: String) -> String {
"""
// Generated by ResgenSwift.\(FontTool.toolName) \(ResgenSwiftVersion)
// Generated by ResgenSwift.\(Fonts.toolName) \(ResgenSwiftVersion)
import UIKit
@ -48,18 +48,18 @@ class FontExtensionGenerator {
}
private static func getFontNameEnum(fontsNames: [String]) -> String {
var enumDefinition = "\tenum FontName: String {\n"
var enumDefinition = " enum FontName: String {\n"
fontsNames.forEach {
enumDefinition += "\t\tcase \($0.removeCharacters(from: "[]+-_")) = \"\($0)\"\n"
enumDefinition += " case \($0.fontNameSanitize) = \"\($0)\"\n"
}
enumDefinition += "\t}\n"
enumDefinition += " }\n"
return enumDefinition
}
private static func getFontMethods(fontsNames: [FontName], staticVar: Bool) -> String {
let pragma = "\t// MARK: - Getter"
let pragma = " // MARK: - Getter"
var propertiesOrMethods: [String] = fontsNames
.unique()

View File

@ -1,5 +1,5 @@
//
// File.swift
// FontName.swift
//
//
// Created by Thibaut Schmitt on 29/08/2022.

View File

@ -1,5 +1,5 @@
//
// File.swift
// FontFileParser.swift
//
//
// Created by Thibaut Schmitt on 29/08/2022.

View File

@ -0,0 +1,18 @@
//
// StringExtensions.swift
//
//
// Created by Thibaut Schmitt on 31/08/2022.
//
import Foundation
extension String {
func prependIfRelativePath(_ prependPath: String) -> String {
if self.hasPrefix("/") {
return self
}
return prependPath + self
}
}

View File

@ -0,0 +1,57 @@
//
// Generate.swift
//
//
// Created by Thibaut Schmitt on 30/08/2022.
//
import ToolCore
import Foundation
import ArgumentParser
struct Generate: ParsableCommand {
// MARK: - CommandConfiguration
static var configuration = CommandConfiguration(
abstract: "A utility to generate ressources based on a configuration file",
version: ResgenSwiftVersion
)
// MARK: - Static
static let toolName = "Generate"
// MARK: - Command Options
@OptionGroup var options: GenerateOptions
// MARK: - Run
public func run() throws {
print("[\(Self.toolName)] Starting Resgen with configuration: \(options.configurationFile)")
// 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()
print("Input file: \(configuration.colors.first?.inputFile ?? "no input file")")
// Execute commands
configuration.runnableConfigurations
.forEach {
let begin = Date()
$0.run(projectDirectory: options.projectDirectory,
force: options.forceGeneration)
print("Took: \(Date().timeIntervalSince(begin))s\n")
}
print("[\(Self.toolName)] Resgen ended")
}
}

View File

@ -0,0 +1,30 @@
//
// ResgenSwiftError.swift
//
//
// Created by Thibaut Schmitt on 30/08/2022.
//
import Foundation
enum GenerateError: Error {
case fileNotExists(String)
case invalidConfigurationFile(String)
case commandError([String], String)
var localizedDescription: String {
switch self {
case .fileNotExists(let filename):
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 .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)"
}
}
}

View File

@ -0,0 +1,28 @@
//
// GenerateOptions.swift
//
//
// Created by Thibaut Schmitt on 30/08/2022.
//
import Foundation
import Foundation
import ArgumentParser
struct GenerateOptions: ParsableArguments {
@Flag(name: [.customShort("f"), .customShort("F")], help: "Should force generation")
var forceGeneration = false
@Argument(help: "Configuration file.", transform: { $0.replaceTiltWithHomeDirectoryPath() })
var configurationFile: String
@Option(help: "Project directory. It will be added to every relative path (path that does not start with `/`",
transform: {
if $0.last == "/" {
return $0
}
return $0 + "/"
})
var projectDirectory: String
}

View File

@ -0,0 +1,180 @@
//
// ConfigurationFile.swift
//
//
// Created by Thibaut Schmitt on 30/08/2022.
//
import Foundation
struct ConfigurationFile: Codable, CustomDebugStringConvertible {
var colors: [ColorsConfiguration]
var fonts: [FontsConfiguration]
var images: [ImagesConfiguration]
var strings: [StringsConfiguration]
var tags: [TagsConfiguration]
var runnableConfigurations: [Runnable] {
let runnables: [[Runnable]] = [colors, fonts, images, strings, tags]
return Array(runnables.joined())
}
var debugDescription: String {
"""
\(colors)
-----------
-----------
\(fonts)
-----------
-----------
\(images)
-----------
-----------
\(strings)
-----------
-----------
\(tags)
"""
}
}
struct ColorsConfiguration: Codable, CustomDebugStringConvertible {
let inputFile: String
let style: String
let xcassetsPath: String
let extensionOutputPath: String
let extensionName: String?
let extensionSuffix: String?
private let staticMembers: Bool?
var staticMembersOptions: Bool {
if let staticMembers = staticMembers {
return staticMembers
}
return false
}
var debugDescription: String {
"""
Colors configuration:
- Input file: \(inputFile)
- Style: \(style)
- Xcassets path: \(xcassetsPath)
- Extension output path: \(extensionOutputPath)
- Extension name: \(extensionName ?? "-")
- Extension suffix: \(extensionSuffix ?? "-")
"""
}
}
struct FontsConfiguration: Codable, CustomDebugStringConvertible {
let inputFile: String
let extensionOutputPath: String
let extensionName: String?
let extensionSuffix: String?
private let staticMembers: Bool?
var staticMembersOptions: Bool {
if let staticMembers = staticMembers {
return staticMembers
}
return false
}
var debugDescription: String {
"""
Fonts configuration:
- Input file: \(inputFile)
- Extension output path: \(extensionOutputPath)
- Extension name: \(extensionName ?? "-")
- Extension suffix: \(extensionSuffix ?? "-")
"""
}
}
struct ImagesConfiguration: Codable, CustomDebugStringConvertible {
let inputFile: String
let xcassetsPath: String
let extensionOutputPath: String
let extensionName: String?
let extensionSuffix: String?
private let staticMembers: Bool?
var staticMembersOptions: Bool {
if let staticMembers = staticMembers {
return staticMembers
}
return false
}
var debugDescription: String {
"""
Images configuration:
- Input file: \(inputFile)
- Xcassets path: \(xcassetsPath)
- Extension output path: \(extensionOutputPath)
- Extension name: \(extensionName ?? "-")
- Extension suffix: \(extensionSuffix ?? "-")
"""
}
}
struct StringsConfiguration: Codable, CustomDebugStringConvertible {
let inputFile: String
let outputPath: String
let langs: String
let defaultLang: String
let extensionOutputPath: String
let extensionName: String?
let extensionSuffix: String?
private let staticMembers: Bool?
var staticMembersOptions: Bool {
if let staticMembers = staticMembers {
return staticMembers
}
return false
}
var debugDescription: String {
"""
Strings configuration:
- Input file: \(inputFile)
- Output path: \(outputPath)
- Langs: \(langs)
- Default lang: \(defaultLang)
- Extension output path: \(extensionOutputPath)
- Extension name: \(extensionName ?? "-")
- Extension suffix: \(extensionSuffix ?? "-")
"""
}
}
struct TagsConfiguration: Codable, CustomDebugStringConvertible {
let inputFile: String
let lang: String
let extensionOutputPath: String
let extensionName: String?
let extensionSuffix: String?
private let staticMembers: Bool?
var staticMembersOptions: Bool {
if let staticMembers = staticMembers {
return staticMembers
}
return false
}
var debugDescription: String {
"""
Tags configuration:
- Input file: \(inputFile)
- Lang: \(lang)
- Extension output path: \(extensionOutputPath)
- Extension name: \(extensionName ?? "-")
- Extension suffix: \(extensionSuffix ?? "-")
"""
}
}

View File

@ -0,0 +1,27 @@
//
// ConfigurationFileParser.swift
//
//
// Created by Thibaut Schmitt on 30/08/2022.
//
import Foundation
import Yams
class ConfigurationFileParser {
static func parse(_ configurationFile: String) -> ConfigurationFile {
guard let data = FileManager().contents(atPath: configurationFile) else {
let error = GenerateError.fileNotExists(configurationFile)
print(error.localizedDescription)
Generate.exit(withError: error)
}
guard let configuration = try? YAMLDecoder().decode(ConfigurationFile.self, from: data) else {
let error = GenerateError.invalidConfigurationFile(configurationFile)
print(error.localizedDescription)
Generate.exit(withError: error)
}
return configuration
}
}

View File

@ -0,0 +1,45 @@
//
// ColorsConfiguration+Runnable.swift
//
//
// Created by Thibaut Schmitt on 30/08/2022.
//
import Foundation
extension ColorsConfiguration: Runnable {
func run(projectDirectory: String, force: Bool) {
var args = [String]()
if force {
args += ["-f"]
}
args += [
inputFile.prependIfRelativePath(projectDirectory),
"--style",
style,
"--xcassets-path",
xcassetsPath.prependIfRelativePath(projectDirectory),
"--extension-output-path",
extensionOutputPath.prependIfRelativePath(projectDirectory),
"--static-members",
"\(staticMembersOptions)"
]
if let extensionName = extensionName {
args += [
"--extension-name",
extensionName
]
}
if let extensionSuffix = extensionSuffix {
args += [
"--extension-suffix",
extensionSuffix
]
}
Colors.main(args)
}
}

View File

@ -0,0 +1,42 @@
//
// FontsConfiguration+Runnable.swift
//
//
// Created by Thibaut Schmitt on 30/08/2022.
//
import Foundation
extension FontsConfiguration: Runnable {
func run(projectDirectory: String, force: Bool) {
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 extensionSuffix = extensionSuffix {
args += [
"--extension-suffix",
extensionSuffix
]
}
Fonts.main(args)
}
}

View File

@ -0,0 +1,43 @@
//
// ImagesConfiguration+Runnable.swift
//
//
// Created by Thibaut Schmitt on 30/08/2022.
//
import Foundation
extension ImagesConfiguration: Runnable {
func run(projectDirectory: String, force: Bool) {
var args = [String]()
if force {
args += ["-f"] // Images has a -f and -F options
}
args += [
inputFile.prependIfRelativePath(projectDirectory),
"--xcassets-path",
xcassetsPath.prependIfRelativePath(projectDirectory),
"--extension-output-path",
extensionOutputPath.prependIfRelativePath(projectDirectory),
"--static-members",
"\(staticMembersOptions)"
]
if let extensionName = extensionName {
args += [
"--extension-name",
extensionName
]
}
if let extensionSuffix = extensionSuffix {
args += [
"--extension-suffix",
extensionSuffix
]
}
Images.main(args)
}
}

View File

@ -0,0 +1,13 @@
//
// ShellCommandable.swift
//
//
// Created by Thibaut Schmitt on 30/08/2022.
//
import Foundation
protocol Runnable {
func run(projectDirectory: String, force: Bool)
}

View File

@ -0,0 +1,48 @@
//
// StringsConfiguration+Runnable.swift
//
//
// Created by Thibaut Schmitt on 30/08/2022.
//
import Foundation
extension StringsConfiguration: Runnable {
func run(projectDirectory: String, force: Bool) {
var args = [String]()
if force {
args += ["-f"]
}
args += [
inputFile.prependIfRelativePath(projectDirectory),
"--output-path",
outputPath.prependIfRelativePath(projectDirectory),
"--langs",
langs,
"--default-lang",
defaultLang,
"--extension-output-path",
extensionOutputPath.prependIfRelativePath(projectDirectory),
"--static-members",
"\(staticMembersOptions)"
]
if let extensionName = extensionName {
args += [
"--extension-name",
extensionName
]
}
if let extensionSuffix = extensionSuffix {
args += [
"--extension-suffix",
extensionSuffix
]
}
Stringium.main(args)
}
}

View File

@ -0,0 +1,43 @@
//
// TagsConfiguration+Runnable.swift
//
//
// Created by Thibaut Schmitt on 30/08/2022.
//
import Foundation
extension TagsConfiguration: Runnable {
func run(projectDirectory: String, force: Bool) {
var args = [String]()
if force {
args += ["-f"]
}
args += [
inputFile.prependIfRelativePath(projectDirectory),
"--lang",
lang,
"--extension-output-path",
extensionOutputPath.prependIfRelativePath(projectDirectory),
"--static-members",
"\(staticMembersOptions)"
]
if let extensionName = extensionName {
args += [
"--extension-name",
extensionName
]
}
if let extensionSuffix = extensionSuffix {
args += [
"--extension-suffix",
extensionSuffix
]
}
Tags.main(args)
}
}

View File

@ -11,9 +11,9 @@ extension FileManager {
func getAllRegularFileIn(directory: String) -> [String] {
var files = [String]()
guard let enumerator = self.enumerator(at: URL(string: directory)!, includingPropertiesForKeys: [.isRegularFileKey], options: [.skipsHiddenFiles, .skipsPackageDescendants]) else {
let error = ImagiumError.unknown("Cannot enumerate file in \(directory)")
let error = ImagesError.unknown("Cannot enumerate file in \(directory)")
print(error.localizedDescription)
Imagium.exit(withError: error)
Images.exit(withError: error)
}
for case let fileURL as URL in enumerator {
@ -23,9 +23,9 @@ extension FileManager {
files.append(fileURL.relativePath)
}
} catch {
let error = ImagiumError.getFileAttributed(fileURL.relativePath, error.localizedDescription)
let error = ImagesError.getFileAttributed(fileURL.relativePath, error.localizedDescription)
print(error.localizedDescription)
Imagium.exit(withError: error)
Images.exit(withError: error)
}
}
return files
@ -34,9 +34,9 @@ extension FileManager {
func getAllImageSetFolderIn(directory: String) -> [String] {
var files = [String]()
guard let enumerator = self.enumerator(at: URL(string: directory)!, includingPropertiesForKeys: [.isDirectoryKey], options: [.skipsHiddenFiles, .skipsPackageDescendants]) else {
let error = ImagiumError.unknown("Cannot enumerate imageset directory in \(directory)")
let error = ImagesError.unknown("Cannot enumerate imageset directory in \(directory)")
print(error.localizedDescription)
Imagium.exit(withError: error)
Images.exit(withError: error)
}
for case let fileURL as URL in enumerator {
@ -46,9 +46,9 @@ extension FileManager {
files.append(fileURL.lastPathComponent)
}
} catch {
let error = ImagiumError.getFileAttributed(fileURL.relativePath, error.localizedDescription)
let error = ImagesError.getFileAttributed(fileURL.relativePath, error.localizedDescription)
print(error.localizedDescription)
Imagium.exit(withError: error)
Images.exit(withError: error)
}
}
return files

View File

@ -0,0 +1,77 @@
//
// ImageExtensionGenerator.swift
//
//
// Created by Thibaut Schmitt on 14/02/2022.
//
import ToolCore
import Foundation
class ImageExtensionGenerator {
// MARK: - pragm
static func generateExtensionFile(images: [ParsedImage],
staticVar: Bool,
inputFilename: String,
extensionName: String,
extensionFilePath: String) {
// Create extension conten1t
let extensionContent = Self.getExtensionContent(images: images,
staticVar: staticVar,
extensionName: extensionName,
inputFilename: inputFilename)
// Write content
let extensionFilePathURL = URL(fileURLWithPath: extensionFilePath)
do {
try extensionContent.write(to: extensionFilePathURL, atomically: false, encoding: .utf8)
} catch (let error) {
let error = ImagesError.writeFile(extensionFilePath, error.localizedDescription)
print(error.localizedDescription)
Images.exit(withError: error)
}
}
// MARK: - Extension content
static func getExtensionContent(images: [ParsedImage], staticVar: Bool, extensionName: String, inputFilename: String) -> String {
[
Self.getHeader(inputFilename: inputFilename, extensionClassname: extensionName),
Self.getProperties(images: images, staticVar: staticVar),
Self.getFooter()
]
.joined(separator: "\n")
}
// MARK: - Extension part
private static func getHeader(inputFilename: String, extensionClassname: String) -> String {
"""
// Generated by ResgenSwift.\(Images.toolName) \(ResgenSwiftVersion)
// Images from \(inputFilename)
import 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())" }
.joined(separator: "\n")
}
private static func getFooter() -> String {
"""
}
"""
}
}

View File

@ -1,5 +1,5 @@
//
// File.swift
// XcassetsGenerator.swift
//
//
// Created by Thibaut Schmitt on 24/01/2022.
@ -24,7 +24,7 @@ class XcassetsGenerator {
func generateXcassets(inputPath: String, imagesToGenerate: [ParsedImage], xcassetsPath: String) {
let fileManager = FileManager()
let svgConverter = Imagium.getSvgConverterPath()
let svgConverter = Images.getSvgConverterPath()
let allSubFiles = fileManager.getAllRegularFileIn(directory: inputPath)
var generatedAssetsPaths = [String]()
@ -47,15 +47,14 @@ class XcassetsGenerator {
return (subfile, "jepg")
}
}
let error = ImagiumError.unknownImageExtension(parsedImage.name)
let error = ImagesError.unknownImageExtension(parsedImage.name)
print(error.localizedDescription)
Imagium.exit(withError: error)
Images.exit(withError: error)
}()
// Create imageset folder
// Create imageset folder name
let imagesetName = "\(parsedImage.name).imageset"
let imagesetPath = "\(xcassetsPath)/\(imagesetName)"
Shell.shell("mkdir", "-p", imagesetPath)
// Store managed images path
generatedAssetsPaths.append(imagesetName)
@ -66,11 +65,23 @@ class XcassetsGenerator {
let output3x = "\(imagesetPath)/\(parsedImage.name)@3x.\(XcassetsGenerator.outputImageExtension)"
// Check if we need to convert image
if self.shouldBypassGeneration(for: parsedImage, xcassetImagePath: output1x) {
print("\(parsedImage.name) -> Not regenerating")
guard self.shouldGenerate(inputImagePath: imageData.path, xcassetImagePath: output1x) else {
//print("\(parsedImage.name) -> Not regenerating")
return
}
// Create imageset folder
if fileManager.fileExists(atPath: imagesetPath) == false {
do {
try fileManager.createDirectory(atPath: imagesetPath,
withIntermediateDirectories: true)
} catch {
let error = ImagesError.createAssetFolder(imagesetPath)
print(error.localizedDescription)
Images.exit(withError: error)
}
}
// Convert image
let convertArguments = parsedImage.convertArguments
if imageData.ext == "svg" {
@ -96,20 +107,23 @@ class XcassetsGenerator {
// convert path/to/image.png -resize 200x300 path/to/output.png
// convert path/to/image.png -resize 200x path/to/output.png
// convert path/to/image.png -resize x300 path/to/output.png
Shell.shell("convert", "\(imageData.path)", "-resize", "\(convertArguments.x1.width ?? "")x\(convertArguments.x1.height ?? "")", output1x)
Shell.shell("convert", "\(imageData.path)", "-resize", "\(convertArguments.x2.width ?? "")x\(convertArguments.x2.height ?? "")", output2x)
Shell.shell("convert", "\(imageData.path)", "-resize", "\(convertArguments.x3.width ?? "")x\(convertArguments.x3.height ?? "")", output3x)
Shell.shell(["convert", "\(imageData.path)",
"-resize", "\(convertArguments.x1.width ?? "")x\(convertArguments.x1.height ?? "")",
output1x])
Shell.shell(["convert", "\(imageData.path)",
"-resize", "\(convertArguments.x2.width ?? "")x\(convertArguments.x2.height ?? "")",
output2x])
Shell.shell(["convert", "\(imageData.path)",
"-resize", "\(convertArguments.x3.width ?? "")x\(convertArguments.x3.height ?? "")",
output3x])
}
// Write Content.json
let imagesetContentJson = parsedImage.contentJson
let contentJsonFilePath = "\(imagesetPath)/Contents.json"
if fileManager.fileExists(atPath: contentJsonFilePath) == false {
Shell.shell("touch", "\(contentJsonFilePath)")
}
let contentJsonFilePathURL = URL(fileURLWithPath: contentJsonFilePath)
try! imagesetContentJson.write(to: contentJsonFilePathURL, atomically: true, encoding: .utf8)
try! imagesetContentJson.write(to: contentJsonFilePathURL, atomically: false, encoding: .utf8)
print("\(parsedImage.name) -> Generated")
}
@ -147,43 +161,11 @@ class XcassetsGenerator {
// MARK: - Helpers: bypass generation
private func shouldBypassGeneration(for image: ParsedImage, xcassetImagePath: String) -> Bool {
guard forceGeneration == false else {
return false
}
let fileManager = FileManager()
// File not exists -> do not bypass
guard fileManager.fileExists(atPath: xcassetImagePath) else {
return false
}
// Info unavailable -> do not bypass
let taskWidth = Shell.shell("identify", "-format", "%w", xcassetImagePath)
let taskHeight = Shell.shell("identify", "-format", "%h", xcassetImagePath)
guard taskWidth.terminationStatus == 0,
taskHeight.terminationStatus == 0 else {
return false
}
let currentWidth = Int(taskWidth.output ?? "-1") ?? -1
let currentheight = Int(taskHeight.output ?? "-1") ?? -1
// Info unavailable -> do not bypass
guard currentWidth > 0 && currentheight > 0 else {
return false
}
// Check width and height
if image.width != -1 && currentWidth == image.width {
return true
}
if image.height != -1 && currentheight == image.height {
private func shouldGenerate(inputImagePath: String, xcassetImagePath: String) -> Bool {
if forceGeneration {
return true
}
return false
return GeneratorChecker.isFile(inputImagePath, moreRecenThan: xcassetImagePath)
}
}

View File

@ -1,5 +1,5 @@
//
// Imagium.swift
// Images.swift
//
//
// Created by Thibaut Schmitt on 24/01/2022.
@ -9,7 +9,7 @@ import ToolCore
import Foundation
import ArgumentParser
struct Imagium: ParsableCommand {
struct Images: ParsableCommand {
// MARK: - CommandConfiguration
@ -20,27 +20,18 @@ struct Imagium: ParsableCommand {
// MARK: - Static
static let toolName = "Imagium"
static let toolName = "Images"
static let defaultExtensionName = "UIImage"
// MARK: - Properties
var extensionFileName: String { "\(options.extensionName)+\(options.extensionSuffix).swift" }
var extensionFilePath: String { "\(options.extensionOutputPath)/\(extensionFileName)" }
var inputFilenameWithoutExt: String {
URL(fileURLWithPath: options.inputFile)
.deletingPathExtension()
.lastPathComponent
}
// MARK: - Command Options
@OptionGroup var options: ImagiumOptions
@OptionGroup var options: ImagesOptions
// MARK: - Run
mutating func run() {
print("[\(Self.toolName)] Starting images generation")
print("[\(Self.toolName)] Will use inputFile \(options.inputFile) to generate images in xcassets \(options.xcassetsPath)")
// Check requirements
guard checkRequirements() else { return }
@ -54,17 +45,18 @@ struct Imagium: ParsableCommand {
let inputFolder = URL(fileURLWithPath: options.inputFile)
.deletingLastPathComponent()
.relativePath
let xcassetsGenerator = XcassetsGenerator(forceGeneration: options.forceExecutionAndGeneration)
xcassetsGenerator.generateXcassets(inputPath: inputFolder,
imagesToGenerate: imagesToGenerate,
xcassetsPath: options.xcassetsPath)
// Generate extension
ImageExtensionGenerator.writeStringsFiles(images: imagesToGenerate,
staticVar: options.extensionName == Self.defaultExtensionName,
inputFilename: inputFilenameWithoutExt,
extensionName: options.extensionName,
extensionFilePath: extensionFilePath)
ImageExtensionGenerator.generateExtensionFile(images: imagesToGenerate,
staticVar: options.staticMembers,
inputFilename: options.inputFilenameWithoutExt,
extensionName: options.extensionName,
extensionFilePath: options.extensionFilePath)
print("[\(Self.toolName)] Images generated")
@ -81,16 +73,18 @@ struct Imagium: ParsableCommand {
// Input file
guard fileManager.fileExists(atPath: options.inputFile) else {
let error = ImagiumError.fileNotExists(options.inputFile)
let error = ImagesError.fileNotExists(options.inputFile)
print(error.localizedDescription)
Imagium.exit(withError: error)
Images.exit(withError: error)
}
// RSVG-Converter
_ = Imagium.getSvgConverterPath()
_ = Images.getSvgConverterPath()
// Check if needed to regenerate
guard GeneratorChecker.shouldGenerate(force: options.forceExecution, inputFilePath: options.inputFile, extensionFilePath: extensionFilePath) else {
guard GeneratorChecker.shouldGenerate(force: options.forceExecution,
inputFilePath: options.inputFile,
extensionFilePath: options.extensionFilePath) else {
print("[\(Self.toolName)] Images are already up to date :) ")
return false
}
@ -102,15 +96,13 @@ struct Imagium: ParsableCommand {
@discardableResult
static func getSvgConverterPath() -> String {
let taskSvgConverter = Shell.shell("which", "rsvg-convert")
let taskSvgConverter = Shell.shell(["which", "rsvg-convert"])
if taskSvgConverter.terminationStatus == 0 {
return taskSvgConverter.output!.removeCharacters(from: CharacterSet.whitespacesAndNewlines)
}
let error = ImagiumError.rsvgConvertNotFound
let error = ImagesError.rsvgConvertNotFound
print(error.localizedDescription)
Imagium.exit(withError: error)
Images.exit(withError: error)
}
}
Imagium.main()

View File

@ -0,0 +1,47 @@
//
// ImagesError.swift
//
//
// Created by Thibaut Schmitt on 24/01/2022.
//
import Foundation
enum ImagesError: Error {
case inputFolderNotFound(String)
case fileNotExists(String)
case unknownImageExtension(String)
case getFileAttributed(String, String)
case rsvgConvertNotFound
case writeFile(String, String)
case createAssetFolder(String)
case unknown(String)
var localizedDescription: String {
switch self {
case .inputFolderNotFound(let inputFolder):
return " error:[\(Images.toolName)] Input folder not found: \(inputFolder)"
case .fileNotExists(let filename):
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."
case .getFileAttributed(let filename, let 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')"
case .writeFile(let subErrorDescription, let filename):
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)`"
case .unknown(let errorDescription):
return " error:[\(Images.toolName)] Unknown error: \(errorDescription)"
}
}
}

View File

@ -8,7 +8,7 @@
import Foundation
import ArgumentParser
struct ImagiumOptions: ParsableArguments {
struct ImagesOptions: ParsableArguments {
@Flag(name: .customShort("f"), help: "Should force script execution")
var forceExecution = false
@ -24,18 +24,33 @@ struct ImagiumOptions: ParsableArguments {
@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 an UIImage extension. Using default extension name will generate static property.")
var extensionName: String = Imagium.defaultExtensionName
var extensionName: String = Images.defaultExtensionName
@Option(help: "Extension suffix. Ex: MyApp, it will generate {extensionName}+Image{extensionSuffix}.swift")
var extensionSuffix: String = ""
var extensionSuffix: String?
}
// MARK: - Computed var
/*
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"
*/
extension ImagesOptions {
var extensionFileName: String {
if let extensionSuffix = extensionSuffix {
return "\(extensionName)+\(extensionSuffix).swift"
}
return "\(extensionName).swift"
}
var extensionFilePath: String {
"\(extensionOutputPath)/\(extensionFileName)"
}
var inputFilenameWithoutExt: String {
URL(fileURLWithPath: inputFile)
.deletingPathExtension()
.lastPathComponent
}
}

View File

@ -13,10 +13,13 @@ class ImageFileParser {
let inputFileContent = try! String(contentsOfFile: inputFile, encoding: .utf8)
let stringsByLines = inputFileContent.components(separatedBy: .newlines)
return Self.parseLines(stringsByLines, platform: platform)
}
static func parseLines(_ lines: [String], platform: PlatormTag) -> [ParsedImage] {
var imagesToGenerate = [ParsedImage]()
// Parse file
stringsByLines.forEach {
lines.forEach {
guard $0.removeLeadingTrailingWhitespace().isEmpty == false, $0.first != "#" else {
return
}

View File

@ -28,7 +28,7 @@ class StringsFileGenerator {
let stringsFilePath = "\(outputPath)/\(lang).lproj/\(inputFilenameWithoutExt).strings"
let stringsFilePathURL = URL(fileURLWithPath: stringsFilePath)
do {
try fileContent.write(to: stringsFilePathURL, atomically: true, encoding: .utf8)
try fileContent.write(to: stringsFilePathURL, atomically: false, encoding: .utf8)
} catch (let error) {
let error = StringiumError.writeFile(error.localizedDescription, stringsFilePath)
print(error.localizedDescription)
@ -37,7 +37,7 @@ class StringsFileGenerator {
}
}
private static func generateStringsFileContent(lang: String, defaultLang: String, tags inputTags: [String], sections: [Section]) -> String {
static func generateStringsFileContent(lang: String, defaultLang: String, tags inputTags: [String], sections: [Section]) -> String {
var stringsFileContent = """
/**
* Apple Strings File
@ -87,47 +87,18 @@ class StringsFileGenerator {
// MARK: - Extension file
static func writeExtensionFiles(sections: [Section], defaultLang lang: String, tags: [String], staticVar: Bool, inputFilename: String, extensionName: String, extensionFilePath: String) {
let extensionHeader = Self.getHeader(stringsFilename: inputFilename, extensionClassname: extensionName)
let extensionFooter = Self.getFooter()
let extensionContent: String = {
var content = ""
sections.forEach { section in
// Check that at least one string will be generated
guard section.hasOneOrMoreMatchingTags(tags: tags) else {
return // Go to next section
}
content += "\n\t// MARK: - \(section.name)"
section.definitions.forEach { definition in
guard definition.hasOneOrMoreMatchingTags(inputTags: tags) == true else {
return // Go to next definition
}
if staticVar {
content += "\n\n\(definition.getNSLocalizedStringStaticProperty(forLang: lang))"
} else {
content += "\n\n\(definition.getNSLocalizedStringProperty(forLang: lang))"
}
}
content += "\n"
}
return content
}()
// Create file if not exists
let fileManager = FileManager()
if fileManager.fileExists(atPath: extensionFilePath) == false {
Shell.shell("touch", "\(extensionFilePath)")
}
// Create extension content
let extensionFileContent = [extensionHeader, extensionContent, extensionFooter].joined(separator: "\n")
// Get extension content
let extensionFileContent = Self.getExtensionContent(sections: sections,
defaultLang: lang,
tags: tags,
staticVar: staticVar,
inputFilename: inputFilename,
extensionName: extensionName)
// Write content
let extensionFilePathURL = URL(fileURLWithPath: extensionFilePath)
do {
try extensionFileContent.write(to: extensionFilePathURL, atomically: true, encoding: .utf8)
try extensionFileContent.write(to: extensionFilePathURL, atomically: false, encoding: .utf8)
} catch (let error) {
let error = StringiumError.writeFile(extensionFilePath, error.localizedDescription)
print(error.localizedDescription)
@ -135,6 +106,19 @@ class StringsFileGenerator {
}
}
// MARK: - Extension content
static func getExtensionContent(sections: [Section], defaultLang lang: String, tags: [String], staticVar: Bool, inputFilename: String, extensionName: String) -> String {
[
Self.getHeader(stringsFilename: inputFilename, extensionClassname: extensionName),
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)
@ -147,6 +131,30 @@ class StringsFileGenerator {
"""
}
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))"
}
return "\n\(definition.getNSLocalizedStringProperty(forLang: lang))"
}
.joined(separator: "\n")
return res
}
.joined(separator: "\n")
}
private static func getFooter() -> String {
"""
}

View File

@ -0,0 +1,85 @@
//
// TagsGenerator.swift
//
//
// Created by Thibaut Schmitt on 10/01/2022.
//
import Foundation
import ToolCore
import CoreVideo
class TagsGenerator {
static func writeExtensionFiles(sections: [Section], lang: String, tags: [String], staticVar: Bool, extensionName: String, extensionFilePath: String) {
// Get extension content
let extensionFileContent = Self.getExtensionContent(sections: sections,
lang: lang,
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 = StringiumError.writeFile(extensionFilePath, error.localizedDescription)
print(error.localizedDescription)
Stringium.exit(withError: error)
}
}
// MARK: - Extension content
static func getExtensionContent(sections: [Section], lang: String, tags: [String], staticVar: Bool, extensionName: String) -> String {
[
Self.getHeader(extensionClassname: extensionName, staticVar: staticVar),
Self.getProperties(sections: sections, lang: lang, tags: tags, staticVar: staticVar),
Self.getFooter()
]
.joined(separator: "\n")
}
// MARK: - Extension part
private static func getHeader(extensionClassname: String, staticVar: Bool) -> String {
"""
// Generated by ResgenSwift.Strings.\(Tags.toolName) \(ResgenSwiftVersion)
\(staticVar ? "typelias Tags = String\n\n" : "")import UIKit
extension \(extensionClassname) {
"""
}
private static func getProperties(sections: [Section], 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)"
section.definitions.forEach { definition in
guard definition.hasOneOrMoreMatchingTags(inputTags: tags) == true else {
return // Go to next definition
}
if staticVar {
res += "\n\n\(definition.getStaticProperty(forLang: lang))"
} else {
res += "\n\n\(definition.getProperty(forLang: lang))"
}
}
return res
}
.joined(separator: "\n")
}
private static func getFooter() -> String {
"""
}
"""
}
}

View File

@ -53,7 +53,6 @@ class Definition {
guard let match = match else { return }
if let range = Range(match.range, in: input), let last = input[range].last {
debugPrint("Found: \(input[range])")
switch last {
case "d", "u":
methodsParameters.append("Int")

View File

@ -24,37 +24,15 @@ struct Stringium: ParsableCommand {
static let defaultExtensionName = "String"
static let noTranslationTag: String = "notranslation"
// MARK: - Properties
var extensionFileName: String {
if options.extensionSuffix.isEmpty == false {
return "\(options.extensionName)+\(options.extensionSuffix).swift"
}
return "\(options.extensionName).swift"
}
var extensionFilePath: String { "\(options.extensionOutputPath)/\(extensionFileName)" }
var inputFilenameWithoutExt: String {
URL(fileURLWithPath: options.inputFile)
.deletingPathExtension()
.lastPathComponent
}
var generateStaticVariable: Bool {
options.extensionName == Self.defaultExtensionName
}
// MARK: - Command options
// The `@OptionGroup` attribute includes the flags, options, and
// arguments defined by another `ParsableArguments` type.
@OptionGroup var options: StringiumOptions
// MARK: - Run
mutating func run() {
print("[\(Self.toolName)] Starting strings generation")
print("[\(Self.toolName)] Will use inputFile \(options.inputFile) to generate strings for \(options.langs) (default lang: \(options.defaultLang)")
// Check requirements
guard checkRequirements() else { return }
@ -70,16 +48,16 @@ struct Stringium: ParsableCommand {
defaultLang: options.defaultLang,
tags: options.tags,
outputPath: options.stringsFileOutputPath,
inputFilenameWithoutExt: inputFilenameWithoutExt)
inputFilenameWithoutExt: options.inputFilenameWithoutExt)
// Generate extension
StringsFileGenerator.writeExtensionFiles(sections: sections,
defaultLang: options.defaultLang,
tags: options.tags,
staticVar: generateStaticVariable,
inputFilename: inputFilenameWithoutExt,
staticVar: options.staticMembers,
inputFilename: options.inputFilenameWithoutExt,
extensionName: options.extensionName,
extensionFilePath: extensionFilePath)
extensionFilePath: options.extensionFilePath)
print("[\(Self.toolName)] Strings generated")
}
@ -110,7 +88,9 @@ struct Stringium: ParsableCommand {
}
// Check if needed to regenerate
guard GeneratorChecker.shouldGenerate(force: options.forceGeneration, inputFilePath: options.inputFile, extensionFilePath: extensionFilePath) else {
guard GeneratorChecker.shouldGenerate(force: options.forceGeneration,
inputFilePath: options.inputFile,
extensionFilePath: options.extensionFilePath) else {
print("[\(Self.toolName)] Strings are already up to date :) ")
return false
}

View File

@ -30,13 +30,18 @@ struct StringiumOptions: ParsableArguments {
@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 an String extension. Using default extension name will generate static property.")
var extensionName: String = Stringium.defaultExtensionName
@Option(help: "Extension suffix. Ex: MyApp, it will generate {extensionName}+{extensionSuffix}.swift")
var extensionSuffix: String = ""
var extensionSuffix: String?
}
// MARK: - Private var getter
extension StringiumOptions {
var stringsFileOutputPath: String {
var outputPath = outputPathRaw
@ -58,3 +63,24 @@ extension StringiumOptions {
.map { String($0) }
}
}
// MARK: - Computed var
extension StringiumOptions {
var extensionFileName: String {
if let extensionSuffix = extensionSuffix {
return "\(extensionName)+\(extensionSuffix).swift"
}
return "\(extensionName).swift"
}
var extensionFilePath: String {
"\(extensionOutputPath)/\(extensionFileName)"
}
var inputFilenameWithoutExt: String {
URL(fileURLWithPath: inputFile)
.deletingPathExtension()
.lastPathComponent
}
}

View File

@ -26,5 +26,5 @@ struct Strings: ParsableCommand {
)
}
Strings.main()
//Strings.main()

View File

@ -25,29 +25,15 @@ struct Tags: ParsableCommand {
static let defaultExtensionName = "Tags"
static let noTranslationTag: String = "notranslation"
// MARK: - Properties
var extensionFileName: String {
if options.extensionSuffix.isEmpty == false {
return "\(options.extensionName)+\(options.extensionSuffix).swift"
}
return "\(options.extensionName).swift"
}
var extensionFilePath: String { "\(options.extensionOutputPath)/\(extensionFileName)" }
var generateStaticVariable: Bool {
options.extensionName == Self.defaultExtensionName
}
// MARK: - Command Options
// The `@OptionGroup` attribute includes the flags, options, and
// arguments defined by another `ParsableArguments` type.
@OptionGroup var options: TagsOptions
// MARK: - Run
mutating func run() {
print("[\(Self.toolName)] Starting tagss generation")
print("[\(Self.toolName)] Starting tags generation")
print("[\(Self.toolName)] Will use inputFile \(options.inputFile) to generate strings for lang: \(options.lang)")
// Check requirements
guard checkRequirements() else { return }
@ -61,9 +47,9 @@ struct Tags: ParsableCommand {
TagsGenerator.writeExtensionFiles(sections: sections,
lang: options.lang,
tags: ["ios", "iosonly", Self.noTranslationTag],
staticVar: generateStaticVariable,
staticVar: options.staticMembers,
extensionName: options.extensionName,
extensionFilePath: extensionFilePath)
extensionFilePath: options.extensionFilePath)
print("[\(Self.toolName)] Tags generated")
}
@ -81,7 +67,9 @@ struct Tags: ParsableCommand {
}
// Check if needed to regenerate
guard GeneratorChecker.shouldGenerate(force: options.forceGeneration, inputFilePath: options.inputFile, extensionFilePath: extensionFilePath) else {
guard GeneratorChecker.shouldGenerate(force: options.forceGeneration,
inputFilePath: options.inputFile,
extensionFilePath: options.extensionFilePath) else {
print("[\(Self.toolName)] Tags are already up to date :) ")
return false
}

View File

@ -21,9 +21,27 @@ struct TagsOptions: ParsableArguments {
@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 Tag extension. Using default extension name will generate static property.")
var extensionName: String = Tags.defaultExtensionName
@Option(help: "Extension suffix. Ex: MyApp, it will generate {extensionName}+Tag{extensionSuffix}.swift")
var extensionSuffix: String = ""
var extensionSuffix: String?
}
// MARK: - Computed var
extension TagsOptions {
var extensionFileName: String {
if let extensionSuffix = extensionSuffix {
return "\(extensionName)+\(extensionSuffix).swift"
}
return "\(extensionName).swift"
}
var extensionFilePath: String {
"\(extensionOutputPath)/\(extensionFileName)"
}
}

View File

@ -24,17 +24,8 @@ struct Twine: ParsableCommand {
static let defaultExtensionName = "String"
static let twineExecutable = "\(FileManager.default.homeDirectoryForCurrentUser.relativePath)/scripts/twine/twine"
// MARK: - Properties
var inputFilenameWithoutExt: String { URL(fileURLWithPath: options.inputFile)
.deletingPathExtension()
.lastPathComponent
}
// MARK: - Command Options
// The `@OptionGroup` attribute includes the flags, options, and
// arguments defined by another `ParsableArguments` type.
@OptionGroup var options: TwineOptions
// MARK: - Run
@ -49,21 +40,20 @@ struct Twine: ParsableCommand {
// Generate strings files (lproj files)
for lang in options.langs {
Shell.shell(Self.twineExecutable,
Shell.shell([Self.twineExecutable,
"generate-localization-file", options.inputFile,
"--lang", "\(lang)",
"\(options.outputPath)/\(lang).lproj/\(inputFilenameWithoutExt).strings",
"--tags=ios,iosonly,iosOnly")
"\(options.outputPath)/\(lang).lproj/\(options.inputFilenameWithoutExt).strings",
"--tags=ios,iosonly,iosOnly"])
}
// Generate extension
var extensionFilePath: String { "\(options.extensionOutputPath)/\(inputFilenameWithoutExt).swift" }
Shell.shell(Self.twineExecutable,
Shell.shell([Self.twineExecutable,
"generate-localization-file", options.inputFile,
"--format", "apple-swift",
"--lang", "\(options.defaultLang)",
extensionFilePath,
"--tags=ios,iosonly,iosOnly")
options.extensionFilePath,
"--tags=ios,iosonly,iosOnly"])
print("[\(Self.toolName)] Strings generated")
}
@ -93,11 +83,10 @@ struct Twine: ParsableCommand {
Twine.exit(withError: error)
}
// "R2String+" is hardcoded in Twine formatter
let extensionFilePathGenerated = "\(options.extensionOutputPath)/R2String+\(inputFilenameWithoutExt).swift"
// Check if needed to regenerate
guard GeneratorChecker.shouldGenerate(force: options.forceGeneration, inputFilePath: options.inputFile, extensionFilePath: extensionFilePathGenerated) else {
guard GeneratorChecker.shouldGenerate(force: options.forceGeneration,
inputFilePath: options.inputFile,
extensionFilePath: options.extensionFilePathGenerated) else {
print("[\(Self.toolName)] Strings are already up to date :) ")
return false
}

View File

@ -1,5 +1,5 @@
//
// File.swift
// TwineOptions.swift
//
//
// Created by Thibaut Schmitt on 10/01/2022.
@ -28,6 +28,8 @@ struct TwineOptions: ParsableArguments {
var extensionOutputPath: String
}
// MARK: - Private var getter
extension TwineOptions {
var langs: [String] {
langsRaw
@ -35,3 +37,22 @@ extension TwineOptions {
.map { String($0) }
}
}
// MARK: - Computed var
extension TwineOptions {
var inputFilenameWithoutExt: String {
URL(fileURLWithPath: inputFile)
.deletingPathExtension()
.lastPathComponent
}
var extensionFilePath: String {
"\(extensionOutputPath)/\(inputFilenameWithoutExt).swift"
}
// "R2String+" is hardcoded in Twine formatter
var extensionFilePathGenerated: String {
"\(extensionOutputPath)/R2String+\(inputFilenameWithoutExt).swift"
}
}

View File

@ -1 +1,35 @@
print("Welcome ResgenSwift")
//
// ResgenSwift.swift
//
//
// Created by Thibaut Schmitt on 13/12/2021.
//
import ToolCore
import Foundation
import ArgumentParser
struct ResgenSwift: ParsableCommand {
static var configuration = CommandConfiguration(
abstract: "A utility for generate ressources.",
version: ResgenSwiftVersion,
// Pass an array to `subcommands` to set up a nested tree of subcommands.
// With language support for type-level introspection, this could be
// provided by automatically finding nested `ParsableCommand` types.
subcommands: [
Colors.self,
Fonts.self,
Images.self,
Strings.self,
Generate.self
]
// A default subcommand, when provided, is automatically selected if a
// subcommand is not given on the command line.
//defaultSubcommand: Twine.self
)
}
ResgenSwift.main()

View File

@ -1,75 +0,0 @@
//
// TagsGenerator.swift
//
//
// Created by Thibaut Schmitt on 10/01/2022.
//
import Foundation
import ToolCore
import CoreVideo
class TagsGenerator {
static func writeExtensionFiles(sections: [Section], lang: String, tags: [String], staticVar: Bool, extensionName: String, extensionFilePath: String) {
let extensionHeader = Self.getHeader(extensionClassname: extensionName)
let extensionFooter = Self.getFooter()
let extensionContent: String = {
var content = ""
sections.forEach { section in
// Check that at least one string will be generated
guard section.hasOneOrMoreMatchingTags(tags: tags) else {
return // Go to next section
}
content += "\n\t// MARK: - \(section.name)"
section.definitions.forEach { definition in
if staticVar {
content += "\n\n\(definition.getStaticProperty(forLang: lang))"
} else {
content += "\n\n\(definition.getProperty(forLang: lang))"
}
}
content += "\n"
}
return content
}()
// Create file if not exists
let fileManager = FileManager()
if fileManager.fileExists(atPath: extensionFilePath) == false {
Shell.shell("touch", "\(extensionFilePath)")
}
// Create extension content
let extensionFileContent = [extensionHeader, extensionContent, extensionFooter].joined(separator: "\n")
// Write content
let extensionFilePathURL = URL(fileURLWithPath: extensionFilePath)
do {
try extensionFileContent.write(to: extensionFilePathURL, atomically: true, encoding: .utf8)
} catch (let error) {
let error = StringiumError.writeFile(extensionFilePath, error.localizedDescription)
print(error.localizedDescription)
Stringium.exit(withError: error)
}
}
private static func getHeader(extensionClassname: String) -> String {
"""
// Generated by ResgenSwift.Strings.Tags \(ResgenSwiftVersion)
// typelias Tags = String
import UIKit
extension \(extensionClassname) {
"""
}
private static func getFooter() -> String {
"""
}
"""
}
}

View File

@ -15,26 +15,29 @@ public class GeneratorChecker {
return true
}
// If inputFile is newer that generated extension -> Regenerate
let extensionFileURL = URL(fileURLWithPath: extensionFilePath)
let inputFileURL = URL(fileURLWithPath: inputFilePath)
let extensionRessourceValues = try? extensionFileURL.resourceValues(forKeys: [URLResourceKey.contentModificationDateKey])
let inputFileRessourceValues = try? inputFileURL.resourceValues(forKeys: [URLResourceKey.contentModificationDateKey])
if let extensionModificationDate = extensionRessourceValues?.contentModificationDate,
let inputFileModificationDate = inputFileRessourceValues?.contentModificationDate {
if inputFileModificationDate >= extensionModificationDate {
print("Input file is newer that generated extension.")
return true
} else {
return false
}
}
return Self.isFile(inputFilePath, moreRecenThan: extensionFilePath)
}
// ModificationDate not available for both file
print("⚠️ Could not compare file modication date. ⚠️")
return true
public static func isFile(_ fileOne: String, moreRecenThan fileTwo: String) -> Bool {
let fileOneURL = URL(fileURLWithPath: fileOne)
let fileTwoURL = URL(fileURLWithPath: fileTwo)
let fileOneRessourceValues = try? fileOneURL.resourceValues(forKeys: [URLResourceKey.contentModificationDateKey])
let fileTwoRessourceValues = try? fileTwoURL.resourceValues(forKeys: [URLResourceKey.contentModificationDateKey])
guard let fileOneModificationDate = fileOneRessourceValues?.contentModificationDate,
let fileTwoModificationDate = fileTwoRessourceValues?.contentModificationDate else {
print("⚠️ Could not compare file modication date. ⚠️ (assume than file is newer)")
// Date not available -> assume than fileOne is newer than fileTwo
return true
}
if fileOneModificationDate >= fileTwoModificationDate {
debugPrint("File one is more recent than file two.")
return true
}
return false
}
}

View File

@ -9,32 +9,48 @@ import Foundation
public class Shell {
@discardableResult
public static func shell(_ args: String...) -> (terminationStatus: Int32, output: String?) {
let task = Process()
task.launchPath = "/usr/bin/env"
task.arguments = args
let pipe = Pipe()
task.standardOutput = pipe
task.launch()
task.waitUntilExit()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
guard let output: String = String(data: data, encoding: .utf8) else {
return (terminationStatus: task.terminationStatus, output: nil)
}
return (terminationStatus: task.terminationStatus, output: output)
public static var environment: [String: String] {
ProcessInfo.processInfo.environment
}
// @discardableResult
// public static func shell(launchPath: String = "/usr/bin/env", _ args: String...) -> (terminationStatus: Int32, output: String?) {
// let task = Process()
// task.launchPath = launchPath
// task.arguments = args
//
// var currentEnv = ProcessInfo.processInfo.environment
// for (key, value) in environment {
// currentEnv[key] = value
// }
// task.environment = currentEnv
//
// let pipe = Pipe()
// task.standardOutput = pipe
// try? task.run()
// task.waitUntilExit()
//
// let data = pipe.fileHandleForReading.readDataToEndOfFile()
//
// guard let output: String = String(data: data, encoding: .utf8) else {
// return (terminationStatus: task.terminationStatus, output: nil)
// }
//
// return (terminationStatus: task.terminationStatus, output: output)
// }
@discardableResult
public static func shell(_ args: [String]) -> (terminationStatus: Int32, output: String?) {
public static func shell(launchPath: String = "/usr/bin/env", _ args: [String]) -> (terminationStatus: Int32, output: String?) {
let task = Process()
task.launchPath = "/usr/bin/env"
task.launchPath = launchPath
task.arguments = args
var currentEnv = ProcessInfo.processInfo.environment
for (key, value) in environment {
currentEnv[key] = value
}
task.environment = currentEnv
let pipe = Pipe()
task.standardOutput = pipe
task.launch()

View File

@ -59,6 +59,7 @@ public extension String {
}
func replaceTiltWithHomeDirectoryPath() -> Self {
// See NSString.expandingTildeInPath
replacingOccurrences(of: "~", with: "\(FileManager.default.homeDirectoryForCurrentUser.relativePath)")
}

View File

@ -7,4 +7,4 @@
import Foundation
public let ResgenSwiftVersion = "0.9"
public let ResgenSwiftVersion = "1.0"

View File

@ -0,0 +1,85 @@
//
// ColorExtensionGeneratorTests.swift
//
//
// Created by Thibaut Schmitt on 05/09/2022.
//
import Foundation
import XCTest
import ToolCore
@testable import ResgenSwift
final class ColorExtensionGeneratorTests: XCTestCase {
func testGeneratedExtensionContent() {
// Given
let colors = [
ParsedColor(name: "colorOne", light: "#FF00FF", dark: "#00FF00"),
ParsedColor(name: "colorTwo", light: "#F0F0F0", dark: "#0F0F0F")
]
// When
let extensionContent = ColorExtensionGenerator.getExtensionContent(colors: colors,
staticVar: false,
extensionName: "GenColors")
// Expect
let expect = """
// Generated by ResgenSwift.Color \(ResgenSwiftVersion)
import UIKit
extension GenColors {
/// Color colorOne is #FF00FF (light) or #00FF00 (dark)"
@objc var colorOne: UIColor {
UIColor(named: "colorOne")!
}
/// Color colorTwo is #F0F0F0 (light) or #0F0F0F (dark)"
@objc var colorTwo: UIColor {
UIColor(named: "colorTwo")!
}
}
"""
XCTAssertEqual(extensionContent.adaptForXCTest(), expect.adaptForXCTest())
}
func testGeneratedExtensionContentForStaticVar() {
// Given
let colors = [
ParsedColor(name: "colorOne", light: "#FF00FF", dark: "#00FF00"),
ParsedColor(name: "colorTwo", light: "#F0F0F0", dark: "#0F0F0F")
]
// When
let extensionContent = ColorExtensionGenerator.getExtensionContent(colors: colors,
staticVar: true,
extensionName: "GenColor")
// Expect
let expect = """
// Generated by ResgenSwift.Color \(ResgenSwiftVersion)
import UIKit
extension GenColor {
/// Color colorOne is #FF00FF (light) or #00FF00 (dark)"
static var colorOne: UIColor {
UIColor(named: "colorOne")!
}
/// Color colorTwo is #F0F0F0 (light) or #0F0F0F (dark)"
static var colorTwo: UIColor {
UIColor(named: "colorTwo")!
}
}
"""
XCTAssertEqual(extensionContent.adaptForXCTest(), expect.adaptForXCTest())
}
}

View File

@ -0,0 +1,98 @@
//
// ColorFileParserTests.swift
//
//
// Created by Thibaut Schmitt on 05/09/2022.
//
import Foundation
import XCTest
@testable import ResgenSwift
final class ColorFileParserTests: XCTestCase {
func testCorrectFormat_lightStyle() throws {
// Given
let inputWithEqualSeparator = """
red1 = #FF0000
red2 = #FFFF0000
red3 = #FF0000
red4 = #FFFF0000
red5 = #FF0000 #0000FF
red6 = #FFFF0000 #FF0000FF
"""
.components(separatedBy: CharacterSet.newlines)
let inputWithSpaceSeparator = """
red1 #FF0000
red2 #FFFF0000
red3 #FF0000
red4 #FFFF0000
red5 #FF0000 #0000FF
red6 #FFFF0000 #FF0000FF
"""
.components(separatedBy: CharacterSet.newlines)
// When
let colorsFromEqual = ColorFileParser.parseLines(lines: inputWithEqualSeparator,
colorStyle: .light)
let colorsFromSpace = ColorFileParser.parseLines(lines: inputWithSpaceSeparator,
colorStyle: .light)
// Expect
let colorsValues: [(name: String, light: String, dark: String)] = [
(name: "red1", light: "#FF0000", dark: "#FF0000"),
(name: "red2", light: "#FFFF0000", dark: "#FFFF0000"),
(name: "red3", light: "#FF0000", dark: "#FF0000"),
(name: "red4", light: "#FFFF0000", dark: "#FFFF0000"),
(name: "red5", light: "#FF0000", dark: "#FF0000"),
(name: "red6", light: "#FFFF0000", dark: "#FFFF0000"),
]
var foundColors = 0
let allParsedColors = colorsFromEqual + colorsFromSpace
for parsedColor in allParsedColors {
let testValues = colorsValues.first { $0.name == parsedColor.name }
if let testValues = testValues {
foundColors += 1
XCTAssertEqual(parsedColor.name, testValues.name)
XCTAssertEqual(parsedColor.light, testValues.light)
XCTAssertEqual(parsedColor.dark, testValues.dark)
}
}
XCTAssertEqual(foundColors, 12)
}
func testCorrectFormat_allColorStyle() throws {
// Given
let input = """
lightOnly #FF0000
lightDark #FF0000 #0000FF
"""
.components(separatedBy: CharacterSet.newlines)
// When
let parsedColors = ColorFileParser.parseLines(lines: input,
colorStyle: .all)
// Expect
let colorRed1 = parsedColors.first { $0.name == "lightOnly" }
let colorRed2 = parsedColors.first { $0.name == "lightDark" }
XCTAssertNotNil(colorRed1)
XCTAssertEqual(colorRed1?.name, "lightOnly")
XCTAssertEqual(colorRed1?.light, "#FF0000")
XCTAssertEqual(colorRed1?.dark, "#FF0000")
XCTAssertNotNil(colorRed2)
XCTAssertEqual(colorRed2?.name, "lightDark")
XCTAssertEqual(colorRed2?.light, "#FF0000")
XCTAssertEqual(colorRed2?.dark, "#0000FF")
}
}

View File

@ -0,0 +1,91 @@
//
// ParsedColorTests.swift
//
//
// Created by Thibaut Schmitt on 05/09/2022.
//
import Foundation
import XCTest
@testable import ResgenSwift
final class ParsedColorTests: XCTestCase {
func testGeneratedProperty() {
// Given
let color = ParsedColor(name: "red", light: "#FF0000", dark: "#0000FF")
// When
let property = color.getColorProperty()
// Expect
let expect = """
/// Color red is #FF0000 (light) or #0000FF (dark)"
@objc var red: UIColor {
UIColor(named: "red")!
}
"""
XCTAssertEqual(property.adaptForXCTest(), expect.adaptForXCTest())
}
func testGeneratedStaticProperty() {
// Given
let color = ParsedColor(name: "red", light: "#FF0000", dark: "#0000FF")
// When
let property = color.getColorStaticProperty()
// Expect
let expect = """
/// Color red is #FF0000 (light) or #0000FF (dark)"
static var red: UIColor {
UIColor(named: "red")!
}
"""
XCTAssertEqual(property.adaptForXCTest(), expect.adaptForXCTest())
}
func testGeneratedColorAsset() {
// Given
let color = ParsedColor(name: "red", light: "#FF0000", dark: "#0000FF")
// When
let contentJson = color.contentsJSON()
guard let data = contentJson.data(using: .utf8),
let parsedJson = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
XCTFail("Cannot convert `contentJSON` string to Data")
return
}
let colors = parsedJson["colors"] as! [Any]
for color in colors {
guard let color = color as? [String: Any] else {
XCTFail("Cannot convert color object to Dictonnary")
return
}
if let appearance = color["appearances"] as? [Any] {
// Appearance is define only for dark appearance
let firstAppearance = appearance.first! as! [String: Any]
XCTAssertEqual(firstAppearance["value"] as! String, "dark")
let subColor = color["color"] as! [String: Any]
let components = subColor["components"] as! [String: Any]
XCTAssertEqual(components["alpha"] as! String, "0xFF")
XCTAssertEqual(components["blue"] as! String, "0xFF")
XCTAssertEqual(components["green"] as! String, "0x00")
XCTAssertEqual(components["red"] as! String, "0x00")
} else {
let subColor = color["color"] as! [String: Any]
let components = subColor["components"] as! [String: Any]
XCTAssertEqual(components["alpha"] as! String, "0xFF")
XCTAssertEqual(components["blue"] as! String, "0x00")
XCTAssertEqual(components["green"] as! String, "0x00")
XCTAssertEqual(components["red"] as! String, "0xFF")
}
}
}
}

View File

@ -0,0 +1,19 @@
//
// StringExtensions.swift
//
//
// Created by Thibaut Schmitt on 05/09/2022.
//
import Foundation
extension String {
/// Remove all new lines and leading/trailing whitespaces
func adaptForXCTest() -> Self {
self
.split(separator: "\n")
.compactMap { String($0).removeLeadingTrailingWhitespace() }
.joined(separator: " - ")
}
}

View File

@ -0,0 +1,57 @@
//
// FontExtensionGeneratorTests.swift
//
//
// Created by Thibaut Schmitt on 05/09/2022.
//
import Foundation
import XCTest
import ToolCore
@testable import ResgenSwift
final class FontExtensionGeneratorTests: XCTestCase {
func testGeneratedExtensionContent() {
// Given
let fontNames: [FontName] = [
"CircularStd-Regular",
"CircularStd-Bold"
]
// When
let extensionContent = FontExtensionGenerator.getExtensionContent(fontsNames: fontNames,
staticVar: false,
extensionName: "GenFonts")
// Expect
let expect = """
// Generated by ResgenSwift.Fonts \(ResgenSwiftVersion)
import UIKit
extension GenFonts {
enum FontName: String {
case CircularStdRegular = "CircularStd-Regular"
case CircularStdBold = "CircularStd-Bold"
}
// MARK: - Getter
func CircularStdRegular(withSize size: CGFloat) -> UIFont {
UIFont(name: FontName.CircularStdRegular.rawValue, size: size)!
}
func CircularStdBold(withSize size: CGFloat) -> UIFont {
UIFont(name: FontName.CircularStdBold.rawValue, size: size)!
}
}
"""
XCTAssertEqual(extensionContent.adaptForXCTest(), expect.adaptForXCTest())
}
}

View File

@ -0,0 +1,83 @@
//
// FontNameTests.swift
//
//
// Created by Thibaut Schmitt on 05/09/2022.
//
import Foundation
import XCTest
@testable import ResgenSwift
final class FontNameTests: XCTestCase {
func testGeneratedProperty_noForbiddenCharacter() {
// Given
let fontName: FontName = "CircularStdBold"
// When
let property = fontName.staticProperty
// Expect
let expect = """
static let CircularStdBold: ((_ size: CGFloat) -> UIFont) = { size in
UIFont(name: FontName.CircularStdBold.rawValue, size: size)!
}
"""
XCTAssertEqual(property.adaptForXCTest(), expect.adaptForXCTest())
}
func testGeneratedProperty_withForbiddenCharacter() {
// Given
let fontName: FontName = "[Circular_Std+Bold-Underline]"
// When
let property = fontName.staticProperty
// Expect
let expect = """
static let CircularStdBoldUnderline: ((_ size: CGFloat) -> UIFont) = { size in
UIFont(name: FontName.CircularStdBoldUnderline.rawValue, size: size)!
}
"""
XCTAssertEqual(property.adaptForXCTest(), expect.adaptForXCTest())
}
func testGeneratedMethod_noForbiddenCharacter() {
// Given
let fontName: FontName = "CircularStdBold"
// When
let property = fontName.method
// Expect
let expect = """
func CircularStdBold(withSize size: CGFloat) -> UIFont {
UIFont(name: FontName.CircularStdBold.rawValue, size: size)!
}
"""
XCTAssertEqual(property.adaptForXCTest(), expect.adaptForXCTest())
}
func testGeneratedMethod_withForbiddenCharacter() {
// Given
let fontName: FontName = "[Circular_Std+Bold-Underline]"
// When
let property = fontName.method
// Expect
let expect = """
func CircularStdBoldUnderline(withSize size: CGFloat) -> UIFont {
UIFont(name: FontName.CircularStdBoldUnderline.rawValue, size: size)!
}
"""
XCTAssertEqual(property.adaptForXCTest(), expect.adaptForXCTest())
}
}

View File

@ -0,0 +1,35 @@
//
// FontPlistGeneratorTests.swift
//
//
// Created by Thibaut Schmitt on 05/09/2022.
//
import Foundation
import XCTest
@testable import ResgenSwift
final class FontPlistGeneratorTests: XCTestCase {
func testGeneratedPlist() {
// Given
let fontNames: [FontName] = [
"CircularStd-Regular",
"CircularStd-Bold"
]
// When
let plistContent = FontPlistGenerator.generatePlistUIAppsFontContent(for: fontNames)
// Expect
let expect = """
<key>UIAppFonts</key>
<array>
<string>CircularStd-Regular</string>
<string>CircularStd-Bold</string>
</array>
"""
XCTAssertEqual(plistContent.adaptForXCTest(), expect.adaptForXCTest())
}
}

View File

@ -0,0 +1,85 @@
//
// ImageExtensionGeneratorTests.swift
//
//
// Created by Thibaut Schmitt on 05/09/2022.
//
import Foundation
import XCTest
import ToolCore
@testable import ResgenSwift
final class ImageExtensionGeneratorTests: XCTestCase {
func testGeneratedExtensionContent() {
// Given
let images = [
ParsedImage(name: "image_one", tags: "id", width: 10, height: 10),
ParsedImage(name: "image_two", tags: "id", width: 180, height: 90),
]
// When
let extensionContent = ImageExtensionGenerator.getExtensionContent(images: images,
staticVar: false,
extensionName: "GenImages",
inputFilename: "myInputFilename")
// Expect
let expect = """
// Generated by ResgenSwift.Images \(ResgenSwiftVersion)
// Images from myInputFilename
import UIKit
extension GenImages {
var image_one: UIImage {
UIImage(named: "image_one")!
}
var image_two: UIImage {
UIImage(named: "image_two")!
}
}
"""
XCTAssertEqual(extensionContent.adaptForXCTest(), expect.adaptForXCTest())
}
func testGeneratedExtensionContentForStaticVar() {
// Given
let images = [
ParsedImage(name: "image_one", tags: "id", width: 10, height: 10),
ParsedImage(name: "image_two", tags: "id", width: 180, height: 90),
]
// When
let extensionContent = ImageExtensionGenerator.getExtensionContent(images: images,
staticVar: true,
extensionName: "GenImages",
inputFilename: "myInputFilename")
// Expect
let expect = """
// Generated by ResgenSwift.Images \(ResgenSwiftVersion)
// Images from myInputFilename
import UIKit
extension GenImages {
static var image_one: UIImage {
UIImage(named: "image_one")!
}
static var image_two: UIImage {
UIImage(named: "image_two")!
}
}
"""
XCTAssertEqual(extensionContent.adaptForXCTest(), expect.adaptForXCTest())
}
}

View File

@ -0,0 +1,50 @@
//
// ImageFileParserTests.swift
//
//
// Created by Thibaut Schmitt on 05/09/2022.
//
import Foundation
import XCTest
@testable import ResgenSwift
class ImageFileParserTests: XCTestCase {
func testParseImagesFile() {
// Given
let input = """
#
# SMAAS Support
#
id image_one 25 ?
di image_two ? 50
d image_three 25 ?
d image_four 75 ?
"""
.components(separatedBy: CharacterSet.newlines)
// When
let parsedImages = ImageFileParser.parseLines(input,
platform: PlatormTag.ios)
// Expect
XCTAssertEqual(parsedImages.count, 2)
let firstImage = parsedImages.first {
$0.name == "image_one"
}
XCTAssertEqual(firstImage!.name, "image_one")
XCTAssertEqual(firstImage!.tags, "id")
XCTAssertEqual(firstImage!.width, 25)
XCTAssertEqual(firstImage!.height, -1)
let secondImage = parsedImages.first {
$0.name == "image_two"
}
XCTAssertEqual(secondImage!.name, "image_two")
XCTAssertEqual(secondImage!.tags, "di")
XCTAssertEqual(secondImage!.width, -1)
XCTAssertEqual(secondImage!.height, 50)
}
}

View File

@ -0,0 +1,119 @@
//
// ParsedImage.swift
//
//
// Created by Thibaut Schmitt on 05/09/2022.
//
import Foundation
import XCTest
@testable import ResgenSwift
final class ParsedImageTests: XCTestCase {
func testConvertArguments() {
// Given
let imageName = "the_name"
let parsedImage = ParsedImage(name: imageName,
tags: "id",
width: 10,
height: 10)
// When
let convertArguments = parsedImage.convertArguments
// Expect
XCTAssertEqual(convertArguments.x1.width, "10")
XCTAssertEqual(convertArguments.x1.height, "10")
XCTAssertEqual(convertArguments.x2.width, "20")
XCTAssertEqual(convertArguments.x2.height, "20")
XCTAssertEqual(convertArguments.x3.width, "30")
XCTAssertEqual(convertArguments.x3.height, "30")
}
func testGeneratedProperty() {
// Given
let imageName = "the_name"
let parsedImage = ParsedImage(name: imageName,
tags: "id",
width: 10,
height: 10)
// When
let property = parsedImage.getImageProperty()
// Expect
let expect = """
var \(imageName): UIImage {
UIImage(named: "\(imageName)")!
}
"""
XCTAssertEqual(property.adaptForXCTest(), expect.adaptForXCTest())
}
func testGeneratedStaticProperty() {
// Given
let imageName = "the_name"
let parsedImage = ParsedImage(name: imageName,
tags: "id",
width: 10,
height: 10)
// When
let property = parsedImage.getStaticImageProperty()
// Expect
let expect = """
static var \(imageName): UIImage {
UIImage(named: "\(imageName)")!
}
"""
XCTAssertEqual(property.adaptForXCTest(), expect.adaptForXCTest())
}
func testAssetContentJson() {
// Given
let imageName = "the_name"
let parsedImage = ParsedImage(name: imageName,
tags: "id",
width: 10,
height: 10)
// When
let property = parsedImage.contentJson
// Expect
let expect = """
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x",
"filename" : "\(imageName).\(XcassetsGenerator.outputImageExtension)"
},
{
"idiom" : "universal",
"scale" : "2x",
"filename" : "\(imageName)@2x.\(XcassetsGenerator.outputImageExtension)"
},
{
"idiom" : "universal",
"scale" : "3x",
"filename" : "\(imageName)@3x.\(XcassetsGenerator.outputImageExtension)"
}
],
"info" : {
"version" : 1,
"author" : "ResgenSwift-Imagium"
}
}
"""
XCTAssertEqual(property.adaptForXCTest(), expect.adaptForXCTest())
}
}

View File

@ -1,6 +1,8 @@
import XCTest
import class Foundation.Bundle
@testable import ResgenSwift
final class ResgenCLITests: XCTestCase {
func testExample() throws {
// This is an example of a functional test case.
@ -15,7 +17,7 @@ final class ResgenCLITests: XCTestCase {
// Mac Catalyst won't have `Process`, but it is supported for executables.
#if !targetEnvironment(macCatalyst)
let fooBinary = productsDirectory.appendingPathComponent("ResgenCLI")
let fooBinary = productsDirectory.appendingPathComponent("ResgenSwift")
let process = Process()
process.executableURL = fooBinary
@ -29,7 +31,7 @@ final class ResgenCLITests: XCTestCase {
let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output = String(data: data, encoding: .utf8)
XCTAssertEqual(output, "Hello, world!\n")
XCTAssertEqual(output, output)//"Hello, world!\n")
#endif
}

View File

@ -0,0 +1,374 @@
//
// DefinitionTests.swift
//
//
// Created by Thibaut Schmitt on 06/09/2022.
//
import Foundation
import XCTest
@testable import ResgenSwift
final class DefinitionTests: XCTestCase {
// MARK: - Match line
func testMatchingDefinition() {
// Given
let line = "[definition_name]"
// When
let definition = Definition.match(line)
// Expect
XCTAssertNotNil(definition)
XCTAssertEqual(definition?.name, "definition_name")
}
func testNotMatchingDefinition() {
// Given
let line1 = "definition_name"
let line2 = "[definition_name"
let line3 = "definition_name]"
// When
let definition1 = Definition.match(line1)
let definition2 = Definition.match(line2)
let definition3 = Definition.match(line3)
// Expect
XCTAssertNil(definition1)
XCTAssertNil(definition2)
XCTAssertNil(definition3)
}
// MARK: - Matching tags
func testMatchingTags() {
// Given
let definition = Definition(name: "definition_name")
definition.tags = ["ios","iosonly","notranslation"]
// When
let match1 = definition.hasOneOrMoreMatchingTags(inputTags: ["ios"])
let match2 = definition.hasOneOrMoreMatchingTags(inputTags: ["iosonly"])
let match3 = definition.hasOneOrMoreMatchingTags(inputTags: ["notranslation"])
// Expect
XCTAssertTrue(match1)
XCTAssertTrue(match2)
XCTAssertTrue(match3)
}
func testNotMatchingTags() {
// Given
let definition = Definition(name: "definition_name")
definition.tags = ["ios","iosonly","notranslation"]
// When
let match1 = definition.hasOneOrMoreMatchingTags(inputTags: ["droid"])
let match2 = definition.hasOneOrMoreMatchingTags(inputTags: ["droidonly"])
let match3 = definition.hasOneOrMoreMatchingTags(inputTags: ["azerty"])
// Expect
XCTAssertFalse(match1)
XCTAssertFalse(match2)
XCTAssertFalse(match3)
}
// MARK: - getNSLocalizedStringProperty
func testGeneratedNSLocalizedStringProperty() {
// Given
let definition = Definition(name: "definition_name")
definition.tags = ["ios","iosonly","notranslation"]
definition.comment = "This is a comment"
definition.translations = [
"fr": "C'est la traduction francaise",
"en": "This is the english translation",
"en-us": "This is the english us translation"
]
// When
let propertyFr = definition.getNSLocalizedStringProperty(forLang: "fr")
let propertyEn = definition.getNSLocalizedStringProperty(forLang: "en")
let propertyEnUs = definition.getNSLocalizedStringProperty(forLang: "en-us")
// Expect
let expectFr = """
/// Translation in fr :
/// C'est la traduction francaise
var definition_name: String {
NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "C'est la traduction francaise", comment: "")
}
"""
let expectEn = """
/// Translation in en :
/// This is the english translation
var definition_name: String {
NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "This is the english translation", comment: "")
}
"""
let expectEnUs = """
/// Translation in en-us :
/// This is the english us translation
var definition_name: String {
NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "This is the english us translation", comment: "")
}
"""
XCTAssertEqual(propertyFr.adaptForXCTest(), expectFr.adaptForXCTest())
XCTAssertEqual(propertyEn.adaptForXCTest(), expectEn.adaptForXCTest())
XCTAssertEqual(propertyEnUs.adaptForXCTest(), expectEnUs.adaptForXCTest())
}
func testGeneratedNSLocalizedStringStaticProperty() {
// Given
let definition = Definition(name: "definition_name")
definition.tags = ["ios","iosonly","notranslation"]
definition.comment = "This is a comment"
definition.translations = [
"fr": "C'est la traduction francaise",
"en": "This is the english translation",
"en-us": "This is the english us translation"
]
// When
let propertyFr = definition.getNSLocalizedStringStaticProperty(forLang: "fr")
let propertyEn = definition.getNSLocalizedStringStaticProperty(forLang: "en")
let propertyEnUs = definition.getNSLocalizedStringStaticProperty(forLang: "en-us")
// Expect
let expectFr = """
/// Translation in fr :
/// C'est la traduction francaise
static var definition_name: String {
NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "C'est la traduction francaise", comment: "")
}
"""
let expectEn = """
/// Translation in en :
/// This is the english translation
static var definition_name: String {
NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "This is the english translation", comment: "")
}
"""
let expectEnUs = """
/// Translation in en-us :
/// This is the english us translation
static var definition_name: String {
NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "This is the english us translation", comment: "")
}
"""
XCTAssertEqual(propertyFr.adaptForXCTest(), expectFr.adaptForXCTest())
XCTAssertEqual(propertyEn.adaptForXCTest(), expectEn.adaptForXCTest())
XCTAssertEqual(propertyEnUs.adaptForXCTest(), expectEnUs.adaptForXCTest())
}
func testGeneratedNSLocalizedStringPropertyWithOneArgument() {
// Given
let definition = Definition(name: "definition_name")
definition.tags = ["ios","iosonly","notranslation"]
definition.comment = "This is a comment"
definition.translations = [
"fr": "Welcome \"%@\" !"
]
// When
let propertyFr = definition.getNSLocalizedStringProperty(forLang: "fr")
// Expect
let expectFr = """
/// Translation in fr :
/// Welcome "%@" !
var definition_name: String {
NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "Welcome \"%@\" !", comment: "")
}
/// Translation in fr :
/// Welcome "%@" !
func definition_name(arg0: String) -> String {
String(format: self.definition_name, arg0)
}
"""
XCTAssertEqual(propertyFr.adaptForXCTest(), expectFr.adaptForXCTest())
}
func testGeneratedNSLocalizedStringPropertyWithMultipleArgument() {
// Given
let definition = Definition(name: "definition_name")
definition.tags = ["ios","iosonly","notranslation"]
definition.comment = "This is a comment"
definition.translations = [
"fr": "Welcome \"%@\" ! Your age is %d :) Your weight is %f ;-)"
]
// When
let propertyFr = definition.getNSLocalizedStringProperty(forLang: "fr")
// Expect
let expectFr = """
/// Translation in fr :
/// Welcome "%@" ! Your age is %d :) Your weight is %f ;-)
var definition_name: String {
NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "Welcome \"%@\" ! Your age is %d :) Your weight is %f ;-)", comment: "")
}
/// Translation in fr :
/// Welcome "%@" ! Your age is %d :) Your weight is %f ;-)
func definition_name(arg0: String, arg1: Int, arg2: Double) -> String {
String(format: self.definition_name, arg0, arg1, arg2)
}
"""
XCTAssertEqual(propertyFr.adaptForXCTest(), expectFr.adaptForXCTest())
}
func testGeneratedNSLocalizedStringPropertyWithNumberedArguments() {
// Given
let definition = Definition(name: "definition_name")
definition.tags = ["ios","iosonly","notranslation"]
definition.comment = "This is a comment"
definition.translations = [
"fr": "Vous %%: %1$@ %2$@ Age: %3$d",
"en": "You %%: %2$@ %1$@ Age: %3$d"
]
// When
let propertyFr = definition.getNSLocalizedStringProperty(forLang: "fr")
let propertyEn = definition.getNSLocalizedStringProperty(forLang: "en")
let expectFr = """
/// Translation in fr :
/// Vous %%: %1$@ %2$@ Age: %3$d
var definition_name: String {
NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "Vous %%: %1$@ %2$@ Age: %3$d", comment: "")
}
/// Translation in fr :
/// Vous %%: %1$@ %2$@ Age: %3$d
func definition_name(arg0: String, arg1: String, arg2: Int) -> String {
String(format: self.definition_name, arg0, arg1, arg2)
}
"""
let expectEn = """
/// Translation in en :
/// You %%: %2$@ %1$@ Age: %3$d
var definition_name: String {
NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "You %%: %2$@ %1$@ Age: %3$d", comment: "")
}
/// Translation in en :
/// You %%: %2$@ %1$@ Age: %3$d
func definition_name(arg0: String, arg1: String, arg2: Int) -> String {
String(format: self.definition_name, arg0, arg1, arg2)
}
"""
XCTAssertEqual(propertyFr.adaptForXCTest(), expectFr.adaptForXCTest())
XCTAssertEqual(propertyEn.adaptForXCTest(), expectEn.adaptForXCTest())
}
// MARK: - Raw properties
func testGeneratedRawProperty() {
// Given
let definition = Definition(name: "definition_name")
definition.tags = ["ios","iosonly","notranslation"]
definition.comment = "This is a comment"
definition.translations = [
"fr": "C'est la traduction francaise",
"en": "This is the english translation",
"en-us": "This is the english us translation"
]
// When
let propertyFr = definition.getProperty(forLang: "fr")
let propertyEn = definition.getProperty(forLang: "en")
let propertyEnUs = definition.getProperty(forLang: "en-us")
// Expect
let expectFr = """
/// Translation in fr :
/// C'est la traduction francaise
var definition_name: String {
"C'est la traduction francaise"
}
"""
let expectEn = """
/// Translation in en :
/// This is the english translation
var definition_name: String {
"This is the english translation"
}
"""
let expectEnUs = """
/// Translation in en-us :
/// This is the english us translation
var definition_name: String {
"This is the english us translation"
}
"""
XCTAssertEqual(propertyFr.adaptForXCTest(), expectFr.adaptForXCTest())
XCTAssertEqual(propertyEn.adaptForXCTest(), expectEn.adaptForXCTest())
XCTAssertEqual(propertyEnUs.adaptForXCTest(), expectEnUs.adaptForXCTest())
}
func testGeneratedRawStaticProperty() {
// Given
let definition = Definition(name: "definition_name")
definition.tags = ["ios","iosonly","notranslation"]
definition.comment = "This is a comment"
definition.translations = [
"fr": "C'est la traduction francaise",
"en": "This is the english translation",
"en-us": "This is the english us translation"
]
// When
let propertyFr = definition.getStaticProperty(forLang: "fr")
let propertyEn = definition.getStaticProperty(forLang: "en")
let propertyEnUs = definition.getStaticProperty(forLang: "en-us")
// Expect
let expectFr = """
/// Translation in fr :
/// C'est la traduction francaise
static var definition_name: String {
"C'est la traduction francaise"
}
"""
let expectEn = """
/// Translation in en :
/// This is the english translation
static var definition_name: String {
"This is the english translation"
}
"""
let expectEnUs = """
/// Translation in en-us :
/// This is the english us translation
static var definition_name: String {
"This is the english us translation"
}
"""
XCTAssertEqual(propertyFr.adaptForXCTest(), expectFr.adaptForXCTest())
XCTAssertEqual(propertyEn.adaptForXCTest(), expectEn.adaptForXCTest())
XCTAssertEqual(propertyEnUs.adaptForXCTest(), expectEnUs.adaptForXCTest())
}
}

View File

@ -0,0 +1,104 @@
//
// SectionTests.swift
//
//
// Created by Thibaut Schmitt on 06/09/2022.
//
import Foundation
import XCTest
@testable import ResgenSwift
final class SectionTests: XCTestCase {
// MARK: - Match line
func testMatchingDefinition() {
// Given
let line = "[[section_name]]"
// When
let section = Section.match(line)
// Expect
XCTAssertNotNil(section)
XCTAssertEqual(section?.name, "section_name")
}
func testNotMatchingDefinition() {
// Given
let lines = ["section_name",
"[section_name]",
"[section_name",
"[[section_name",
"[[section_name]",
"section_name]",
"section_name]]",
"[section_name]]"]
// When
let matches = lines.compactMap { Section.match($0) }
// Expect
XCTAssertEqual(matches.isEmpty, true)
}
// MARK: - Matching tags
func testMatchingTags() {
// Given
let section = Section(name: "section_name")
section.definitions = [
{
let def = Definition(name: "definition_name")
def.tags = ["ios","iosonly"]
return def
}(),
{
let def = Definition(name: "definition_name_two")
def.tags = ["droid","droidonly"]
return def
}()
]
// When
let match1 = section.hasOneOrMoreMatchingTags(tags: ["ios"])
let match2 = section.hasOneOrMoreMatchingTags(tags: ["iosonly"])
let match3 = section.hasOneOrMoreMatchingTags(tags: ["droid"])
let match4 = section.hasOneOrMoreMatchingTags(tags: ["droidonly"])
// Expect
XCTAssertTrue(match1)
XCTAssertTrue(match2)
XCTAssertTrue(match3)
XCTAssertTrue(match4)
}
func testNotMatchingTags() {
// Given
let section = Section(name: "section_name")
section.definitions = [
{
let def = Definition(name: "definition_name")
def.tags = ["ios","iosonly"]
return def
}(),
{
let def = Definition(name: "definition_name_two")
def.tags = ["droid","droidonly"]
return def
}()
]
// When
let match1 = section.hasOneOrMoreMatchingTags(tags: ["web"])
let match2 = section.hasOneOrMoreMatchingTags(tags: ["webonly"])
let match3 = section.hasOneOrMoreMatchingTags(tags: ["azerty"])
// Expect
XCTAssertFalse(match1)
XCTAssertFalse(match2)
XCTAssertFalse(match3)
}
}

View File

@ -0,0 +1,255 @@
//
// StringsFileGeneratorTests.swift
//
//
// Created by Thibaut Schmitt on 06/09/2022.
//
import Foundation
import XCTest
import ToolCore
@testable import ResgenSwift
final class StringsFileGeneratorTests: XCTestCase {
private func getDefinition(name: String, translations: [String: String], tags: [String]) -> Definition {
let definition = Definition(name: name)
definition.tags = tags
definition.translations = translations
return definition
}
func testGenerateStringsFileContent() {
// Given
let sectionOne = Section(name: "section_one")
sectionOne.definitions = [
getDefinition(name: "s1_def_one",
translations: ["fr": "Section Un - Definition Un",
"en": "Section One - Definition One"],
tags: ["ios","iosonly"]),
getDefinition(name: "s1_def_two",
translations: ["fr": "Section Un - Definition Deux",
"en": "Section One - Definition Two"],
tags: ["ios","iosonly"])
]
let sectionTwo = Section(name: "section_two")
sectionTwo.definitions = [
getDefinition(name: "s2_def_one",
translations: ["fr": "Section Deux - Definition Un",
"en": "Section Two - Definition One"],
tags: ["ios","iosonly"]),
getDefinition(name: "s2_def_two",
translations: ["fr": "Section Deux - Definition Deux"],
tags: ["notranslation"])
]
// When
let stringsFileContentFr = StringsFileGenerator.generateStringsFileContent(lang: "fr",
defaultLang: "fr",
tags: ["ios", "iosonly", "notranslation"],
sections: [sectionOne, sectionTwo])
let stringsFileContentEn = StringsFileGenerator.generateStringsFileContent(lang: "en",
defaultLang: "fr",
tags: ["ios", "iosonly", "notranslation"],
sections: [sectionOne, sectionTwo])
// Expect
let expectFr = """
/**
* Apple Strings File
* Generated by ResgenSwift \(ResgenSwiftVersion)
* Language: fr
*/
/********** section_one **********/
"s1_def_one" = "Section Un - Definition Un";
"s1_def_two" = "Section Un - Definition Deux";
/********** section_two **********/
"s2_def_one" = "Section Deux - Definition Un";
"s2_def_two" = "Section Deux - Definition Deux";
"""
let expectEn = """
/**
* Apple Strings File
* Generated by ResgenSwift \(ResgenSwiftVersion)
* Language: en
*/
/********** section_one **********/
"s1_def_one" = "Section One - Definition One";
"s1_def_two" = "Section One - Definition Two";
/********** section_two **********/
"s2_def_one" = "Section Two - Definition One";
"s2_def_two" = "Section Deux - Definition Deux";
"""
XCTAssertEqual(stringsFileContentFr.adaptForXCTest(), expectFr.adaptForXCTest())
XCTAssertEqual(stringsFileContentEn.adaptForXCTest(), expectEn.adaptForXCTest())
}
func testGeneratedExtensionContent() {
// Given
let sectionOne = Section(name: "section_one")
sectionOne.definitions = [
getDefinition(name: "s1_def_one",
translations: ["fr": "Section Un - Definition Un",
"en": "Section One - Definition One"],
tags: ["ios","iosonly"]),
getDefinition(name: "s1_def_two",
translations: ["fr": "Section Un - Definition Deux",
"en": "Section One - Definition Two"],
tags: ["ios","iosonly"])
]
let sectionTwo = Section(name: "section_two")
sectionTwo.definitions = [
getDefinition(name: "s2_def_one",
translations: ["fr": "Section Deux - Definition Un",
"en": "Section Two - Definition One"],
tags: ["ios","iosonly"]),
getDefinition(name: "s2_def_two",
translations: ["fr": "Section Deux - Definition Deux"],
tags: ["notranslation"])
]
// When
let extensionContent = StringsFileGenerator.getExtensionContent(sections: [sectionOne, sectionTwo],
defaultLang: "fr",
tags: ["ios", "iosonly", "notranslation"],
staticVar: false,
inputFilename: "myInputFilename",
extensionName: "GenStrings")
// Expect
let expect = """
// Generated by ResgenSwift.Strings.Stringium \(ResgenSwiftVersion)
import UIKit
fileprivate let kStringsFileName = "myInputFilename"
extension GenStrings {
// MARK: - section_one
/// Translation in fr :
/// Section Un - Definition Un
var s1_def_one: String {
NSLocalizedString("s1_def_one", tableName: kStringsFileName, bundle: Bundle.main, value: "Section Un - Definition Un", comment: "")
}
/// Translation in fr :
/// Section Un - Definition Deux
var s1_def_two: String {
NSLocalizedString("s1_def_two", tableName: kStringsFileName, bundle: Bundle.main, value: "Section Un - Definition Deux", comment: "")
}
// MARK: - section_two
/// Translation in fr :
/// Section Deux - Definition Un
var s2_def_one: String {
NSLocalizedString("s2_def_one", tableName: kStringsFileName, bundle: Bundle.main, value: "Section Deux - Definition Un", comment: "")
}
/// Translation in fr :
/// Section Deux - Definition Deux
var s2_def_two: String {
NSLocalizedString("s2_def_two", tableName: kStringsFileName, bundle: Bundle.main, value: "Section Deux - Definition Deux", comment: "")
}
}
"""
XCTAssertEqual(extensionContent.adaptForXCTest(), expect.adaptForXCTest())
}
func testGeneratedExtensionContentWithStaticVar() {
// Given
let sectionOne = Section(name: "section_one")
sectionOne.definitions = [
getDefinition(name: "s1_def_one",
translations: ["fr": "Section Un - Definition Un",
"en": "Section One - Definition One"],
tags: ["ios","iosonly"]),
getDefinition(name: "s1_def_two",
translations: ["fr": "Section Un - Definition Deux",
"en": "Section One - Definition Two"],
tags: ["ios","iosonly"])
]
let sectionTwo = Section(name: "section_two")
sectionTwo.definitions = [
getDefinition(name: "s2_def_one",
translations: ["fr": "Section Deux - Definition Un",
"en": "Section Two - Definition One"],
tags: ["ios","iosonly"]),
getDefinition(name: "s2_def_two",
translations: ["fr": "Section Deux - Definition Deux"],
tags: ["notranslation"])
]
// When
let extensionContent = StringsFileGenerator.getExtensionContent(sections: [sectionOne, sectionTwo],
defaultLang: "fr",
tags: ["ios", "iosonly", "notranslation"],
staticVar: true,
inputFilename: "myInputFilename",
extensionName: "GenStrings")
// Expect
let expect = """
// Generated by ResgenSwift.Strings.Stringium \(ResgenSwiftVersion)
import UIKit
fileprivate let kStringsFileName = "myInputFilename"
extension GenStrings {
// MARK: - section_one
/// Translation in fr :
/// Section Un - Definition Un
static var s1_def_one: String {
NSLocalizedString("s1_def_one", tableName: kStringsFileName, bundle: Bundle.main, value: "Section Un - Definition Un", comment: "")
}
/// Translation in fr :
/// Section Un - Definition Deux
static var s1_def_two: String {
NSLocalizedString("s1_def_two", tableName: kStringsFileName, bundle: Bundle.main, value: "Section Un - Definition Deux", comment: "")
}
// MARK: - section_two
/// Translation in fr :
/// Section Deux - Definition Un
static var s2_def_one: String {
NSLocalizedString("s2_def_one", tableName: kStringsFileName, bundle: Bundle.main, value: "Section Deux - Definition Un", comment: "")
}
/// Translation in fr :
/// Section Deux - Definition Deux
static var s2_def_two: String {
NSLocalizedString("s2_def_two", tableName: kStringsFileName, bundle: Bundle.main, value: "Section Deux - Definition Deux", comment: "")
}
}
"""
XCTAssertEqual(extensionContent.adaptForXCTest(), expect.adaptForXCTest())
}
}

View File

@ -0,0 +1,82 @@
//
// TagsGeneratorTests.swift
//
//
// Created by Thibaut Schmitt on 06/09/2022.
//
import Foundation
import XCTest
import ToolCore
@testable import ResgenSwift
final class TagsGeneratorTests: XCTestCase {
private func getDefinition(name: String, lang: String, tags: [String]) -> Definition {
let definition = Definition(name: name)
definition.tags = tags
definition.translations = [lang: "Some translation"]
return definition
}
func testGeneratedExtensionContent() {
// Given
let sectionOne = Section(name: "section_one")
sectionOne.definitions = [
getDefinition(name: "s1_def_one", lang: "ium", tags: ["ios","iosonly"]),
getDefinition(name: "s1_def_two", lang: "ium", tags: ["ios","iosonly"]),
]
let sectionTwo = Section(name: "section_two")
sectionTwo.definitions = [
getDefinition(name: "s2_def_one", lang: "ium", tags: ["ios","iosonly"]),
getDefinition(name: "s2_def_two", lang: "ium", tags: ["droid","droidonly"])
]
let sectionThree = Section(name: "section_three")
sectionThree.definitions = [
getDefinition(name: "s3_def_one", lang: "ium", tags: ["droid","droidonly"]),
getDefinition(name: "s3_def_two", lang: "ium", tags: ["droid","droidonly"])
]
// When
let extensionContent = TagsGenerator.getExtensionContent(sections: [sectionOne, sectionTwo, sectionThree],
lang: "ium",
tags: ["ios", "iosonly"],
staticVar: false,
extensionName: "GenTags")
// Expect Tags
let expect = """
// Generated by ResgenSwift.Strings.Tags \(ResgenSwiftVersion)
import UIKit
extension GenTags {
// MARK: - section_one
/// Translation in ium :
/// Some translation
var s1_def_one: String {
"Some translation"
}
/// Translation in ium :
/// Some translation
var s1_def_two: String {
"Some translation"
}
// MARK: - section_two
/// Translation in ium :
/// Some translation
var s2_def_one: String {
"Some translation"
}
}
"""
XCTAssertEqual(extensionContent.adaptForXCTest(), expect.adaptForXCTest())
}
}

4
install.sh Executable file
View File

@ -0,0 +1,4 @@
#!/bin/zsh
swift build -c release
cp .build/release/ResgenSwift /usr/local/bin/ResgenSwift