Compare commits

..

No commits in common. "master" and "v0.9" have entirely different histories.
master ... v0.9

160 changed files with 2832 additions and 10305 deletions

View File

@ -0,0 +1,78 @@
<?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

@ -0,0 +1,78 @@
<?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

@ -0,0 +1,78 @@
<?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

@ -0,0 +1,237 @@
<?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,34 +34,6 @@
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
@ -102,20 +74,6 @@
ReferencedContainer = "container:">
</BuildableReference>
</BuildableProductRunnable>
<CommandLineArguments>
<CommandLineArgument
argument = "generate"
isEnabled = "YES">
</CommandLineArgument>
<CommandLineArgument
argument = "&quot;$(PROJECT_DIR)/../SampleFiles/resgenConfiguration.yml&quot;"
isEnabled = "YES">
</CommandLineArgument>
<CommandLineArgument
argument = "--project-directory &quot;$(PROJECT_DIR)&quot;"
isEnabled = "YES">
</CommandLineArgument>
</CommandLineArguments>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"

View File

@ -1,34 +0,0 @@
# v1.2 - Architecture generation
## New
- New section in configuration file: `architecture`. Define your ressources accessors achitecture and let ResgenSwift generate it for you. Check out Readme to know more.
## Fixes
- Errors and Warnings are now shown in Xcode Issue Navigator
---
# v1.1 - SwiftUI compatibility
## New
- Update plist `UIAppFonts` when generated fonts (use plistBuddy)
- New parameter: `infoPlistPaths`
- Generate SwiftUI extensions for colors, fonts and images
- New parameter: `extensionNameUIKit`
- Adding Makefile to install, unsintall and create man page.
## Fixes
Fix SwiftLint rule `trailing_newline`
---
# v1.0 - Configuration file
## Major
- A new command has been added: `generate`. Instead of runnning every single command, it will run all necessary command based on a `yaml` configuration file. Check out Readme to know more.
## Minors
- Code refactoring
- Huge performance improvements
- Readme.md update
- Add option to generate static properties/methods (`staticMembers`)
- Add option to specify the project directory (`--project-directory`). It allows to run ResgenSwift from anywhere
- Add `install.sh` script to install ResgenSwift in `/usr/local/bin`

4
Jenkinsfile vendored
View File

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

View File

@ -1,63 +0,0 @@
SHELL = /bin/zsh
#INSTALL_DIR ?= /usr/local/bin
INSTALL_DIR = /tmp/ResgenYolo
MAN_DIR := /usr/local/share/man
MAN_PAGE_NAME = resgen-swift.1
REPO_DIR = $(shell pwd)
BUILD_DIR = $(REPO_DIR)/.build
#
# Man pages
#
# create-man-files:
# swift package plugin generate-manual
# cp $(BUILDDIR)/plugins/GenerateManualPlugin/outputs/ResgenSwift/resgen-swift.1 $(REPODIR)/man/resgen-swift.1
# install-man-files:
# mkdir -p ${DESTDIR}${mandir}/man1
# cp $(REPODIR)/man/resgen-swift.1 ${DESTDIR}${mandir}/man1/resgen-swift.1
create-and-install-man-files:
swift package plugin generate-manual
mkdir -p ${MAN_DIR}/man1
cp $(BUILD_DIR)/plugins/GenerateManualPlugin/outputs/ResgenSwift/${MAN_PAGE_NAME} ${MAN_DIR}/man1/${MAN_PAGE_NAME}
#
# Build and install
#
build-debug:
@swift build \
-c debug \
--build-path "$(BUILD_DIR)"
build-release:
@swift build \
-c release \
--build-path "$(BUILD_DIR)"
.PHONY: install
install: build-release
@install -d "$(INSTALL_DIR)"
@install "$(wildcard $(BUILD_DIR)/**/release/ResgenSwift)" "$(INSTALL_DIR)/resgen-swift"
@make create-and-install-man-files
#
# Uninstall and cleaning
#
.PHONY: uninstall
uninstall:
@rm -rf "$(INSTALL_DIR)/resgen-swift"
@rm -rf ${MAN_DIR}/man1/${MAN_PAGE_NAME}
.PHONY: clean
distclean:
@rm -f $(BUILD_DIR)/release
.PHONY: clean
clean: distclean
@rm -rf $(BUILD_DIR)

View File

@ -1,86 +1,16 @@
{
"pins" : [
{
"identity" : "collectionconcurrencykit",
"kind" : "remoteSourceControl",
"location" : "https://github.com/JohnSundell/CollectionConcurrencyKit.git",
"state" : {
"revision" : "b4f23e24b5a1bff301efc5e70871083ca029ff95",
"version" : "0.2.0"
"object": {
"pins": [
{
"package": "swift-argument-parser",
"repositoryURL": "https://github.com/apple/swift-argument-parser",
"state": {
"branch": null,
"revision": "e1465042f195f374b94f915ba8ca49de24300a0d",
"version": "1.0.2"
}
}
},
{
"identity" : "cryptoswift",
"kind" : "remoteSourceControl",
"location" : "https://github.com/krzyzanowskim/CryptoSwift.git",
"state" : {
"revision" : "c9c3df6ab812de32bae61fc0cd1bf6d45170ebf0",
"version" : "1.8.2"
}
},
{
"identity" : "sourcekitten",
"kind" : "remoteSourceControl",
"location" : "https://github.com/jpsim/SourceKitten.git",
"state" : {
"revision" : "b6dc09ee51dfb0c66e042d2328c017483a1a5d56",
"version" : "0.34.1"
}
},
{
"identity" : "swift-argument-parser",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-argument-parser",
"state" : {
"revision" : "8f4d2753f0e4778c76d5f05ad16c74f707390531",
"version" : "1.2.3"
}
},
{
"identity" : "swift-syntax",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-syntax.git",
"state" : {
"revision" : "6ad4ea24b01559dde0773e3d091f1b9e36175036",
"version" : "509.0.2"
}
},
{
"identity" : "swiftlint",
"kind" : "remoteSourceControl",
"location" : "https://github.com/realm/SwiftLint.git",
"state" : {
"revision" : "f17a4f9dfb6a6afb0408426354e4180daaf49cee",
"version" : "0.54.0"
}
},
{
"identity" : "swiftytexttable",
"kind" : "remoteSourceControl",
"location" : "https://github.com/scottrhoyt/SwiftyTextTable.git",
"state" : {
"revision" : "c6df6cf533d120716bff38f8ff9885e1ce2a4ac3",
"version" : "0.9.0"
}
},
{
"identity" : "swxmlhash",
"kind" : "remoteSourceControl",
"location" : "https://github.com/drmohundro/SWXMLHash.git",
"state" : {
"revision" : "a853604c9e9a83ad9954c7e3d2a565273982471f",
"version" : "7.0.2"
}
},
{
"identity" : "yams",
"kind" : "remoteSourceControl",
"location" : "https://github.com/jpsim/Yams.git",
"state" : {
"revision" : "0d9ee7ea8c4ebd4a489ad7a73d5c6cad55d6fed3",
"version" : "5.0.6"
}
}
],
"version" : 2
]
},
"version": 1
}

View File

@ -1,33 +1,53 @@
// swift-tools-version:5.6
// swift-tools-version:5.3
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "ResgenSwift",
platforms: [.macOS(.v12)],
platforms: [.macOS(.v10_12)],
dependencies: [
// Dependencies declare other packages that this package depends on.
.package(url: "https://github.com/apple/swift-argument-parser", from: "1.0.0"),
.package(url: "https://github.com/jpsim/Yams.git", from: "5.0.1"),
.package(url: "https://github.com/realm/SwiftLint.git", .upToNextMajor(from: "0.54.0")),
.package(url: "https://github.com/apple/swift-argument-parser", from: "1.0.0")
],
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.
.executableTarget(
.target(
name: "ResgenSwift",
dependencies: ["FontTool", "ColorTool", "Strings", "Imagium"]
),
.target(
name: "FontTool",
dependencies: [
"ToolCore",
.product(name: "ArgumentParser", package: "swift-argument-parser"),
"Yams"
],
plugins: [.plugin(name: "SwiftLintPlugin", package: "SwiftLint")]
.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")
]
),
// Helper targets
.target(name: "ToolCore"),
// Test targets
.testTarget(
name: "ResgenSwiftTests",

353
README.md
View File

@ -4,22 +4,18 @@ 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` and `Font` (or custom classes). It also prints content of `UIAppFonts` from your project `.plist`. If project `.plist` is specified, it will update `UIAppFonts` content of all `.plist`.
iOS required to use the **real name** of the font, this name can be different from its filename. To get the **real name**, it uses `fc-scan`. So, be sure that the `$PATH` contains path of `fc-scan`.
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**
```sh
swift run -c release ResgenSwift fonts $FORCE_FLAG "./Fonts/fonts.txt" \
```
swift run -c release FontTool $FORCE_FLAG "./Fonts/fonts.txt" \
--extension-output-path "./Fonts/Generated" \
--extension-name "AppFont" \
--extension-name-ui-kit "UIAppFont" \
--extension-suffix "GreatApp" \
--static-members true \
--info-plist-paths "./path/one/to/Info.plist ./path/two/to/Info.plist"
--extension-suffix "GreatApp"
```
**Parameters**
@ -27,26 +23,23 @@ swift run -c release ResgenSwift fonts $FORCE_FLAG "./Fonts/fonts.txt" \
1. `-f`: force generation
2. Font input folder, it will search for every `.ttf` and `.otf` files specified in `fonts.txt`
3. `--extension-output-path`: path where to generate generated extension
4. `--extension-name` *(optional)* : name of the class to add SwiftUI getters
5. `--extension-name-ui-kit` *(optional)* : name of the class to add UIKit getters
6. `--extension-suffix` *(optional)* : additional text which is added to the filename (ex: `AppFont+GreatApp.swift`)
7. `--static-members` *(optional)*: generate static properties or not
8. `--info-plist-paths` *(optional)*: array of `.plist`, you can specify multiple `Info.plist` for multiple targets
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.
## Colors
# Colors
Colors generator generates an extension of `UIColor` (or a custom class) along with colorsets in specified xcassets.
```sh
swift run -c release ResgenSwift colors $FORCE_FLAG "./Colors/colors.txt" \
```
swift run -c release ColorTool $FORCE_FLAG "./Colors/colors.txt" \
--style all \
--xcassets-path "./Colors/colors.xcassets" \
--extension-output-path "./Colors/Generated/" \
--extension-name "AppColor" \
--extension-name-ui-kit "UIAppColor" \
--extension-suffix "GreatApp" \
--static-members true
--extension-suffix "GreatApp"
```
**Parameters**
@ -55,25 +48,25 @@ swift run -c release ResgenSwift colors $FORCE_FLAG "./Colors/colors.txt" \
2. Input colors file
3. `--style` can be `all` or `light`
4. `--extension-output-path`: path where to generate generated extension
5. `--extension-name` *(optional)* : name of the class to add SwiftUI getters
6. `--extension-name-ui-kit` *(optional)* : name of the class to add UIKit getters
7. `--extension-suffix` *(optional)* : additional text which is added to filename (ex: `AppColor+GreatApp.swift`)
8. `--static-members` *(optional)*: generate static properties or not
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`)
> ⚠️ 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)
```sh
swift run -c release ResgenSwift strings twine $FORCE_FLAG "./Twine/strings.txt" \
```
swift run -c release 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**
@ -81,20 +74,18 @@ swift run -c release ResgenSwift strings twine $FORCE_FLAG "./Twine/strings.txt"
2. Input translations file (must be Twine formatted)
3. `--langs`: langs to generate (string with space between each lang)
4. `--default-lang`: default lang that will be in `Base.lproj`. It must be in `langs` as well
5. `--extension-output-path`: path where to generate generated extension
4. `--extension-output-path`: path where to generate generated extension
### Stringium (recommended)
## Stringium (recommended)
```sh
swift run -c release ResgenSwift strings stringium $FORCE_FLAG "./Strings/strings.txt" \
```
swift run -c release 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" \
--xcStrings true
--static-members true
--extension-suffix "GreatApp"
```
**Parameters**
@ -106,21 +97,19 @@ swift run -c release ResgenSwift strings stringium $FORCE_FLAG "./Strings/string
4. `--extension-output-path`: path where to generate generated extension
5. `--extension-name` *(optional)* : name of class to add the extension
6. `--extension-suffix` *(optional)* : additional text which is added to filename (ex: `AppString+GreatApp.swift`)
6. `--xcStrings`*(optional)* : generate string catalog
7. `--static-members` *(optional)*: generate static properties or not
> ⚠️ 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.
```sh
swift run -c release ResgenSwift strings tags $FORCE_FLAG "./Tags/tags.txt" \
```
swift run -c release Strings tags $FORCE_FLAG "./Tags/tags.txt" \
--lang "ium" \
--extension-output-path "./Tags/Generated" \
--extension-name "AppTags" \
--extension-suffix "GreatApp" \
--static-members true
--extension-suffix "GreatApp"
```
**Parameters**
@ -131,87 +120,19 @@ swift run -c release ResgenSwift 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 the following typaloas `typealias Tags = String`.
> ⚠️ 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.
# Images
## Analytics
Analytics will generate all you need to analyze UX with Matomo or Firebase Analytics. Input files are formatted in YAML. This command will generate a manager for each target and an AnalyticsManager. This is this one you will need to use. And it will generate a method for all tags you have declared in the YAML file. Next, you will need to use the `configure()` method of AnalyticsManager and if you want to use matomo to set up the `siteId` and the `url` of the site.
```sh
swift run -c release ResgenSwift strings tags $FORCE_FLAG "./Tags/tags.txt" \
--target "matomo firebase" \
--extension-output-path "./Analytics/Generated" \
--extension-name "AppAnalytics" \
--extension-suffix "GreatApp" \
--static-members true
```
**Parameters**
1. `-f`: force generation
2. Input tags file (must be YAML formatted)
3. `--target`: target with you will log UX
4. `--extension-output-path`: path where to generate generated extension
5. `--extension-name` *(optional)* : name of class to add the extension
6. `--extension-suffix` *(optional)* : additional text which is added to filename (ex: `AppAnalytics+GreatApp.swift`)
7. `--static-members` *(optional)*: generate static properties or not
> ⚠️ If extension name is not set or is `Analytics`, it will generate the following typaloas `typealias Analytics = String`.
### YAML
Images generator will generate images assets along with extensions to access those images easily.
```
- id: s1_def_one
name: s1 def one _TITLE_
path: s1_def_one/_TITLE_
action: Tap
category: User
tags: ios,droid
comments:
parameters:
- name: title
type: String
replaceIn: name,path
```
1. `id`: name of the method (method name will be composed of `log` + `Event|Screen` + `id`)
2. `name`: name of the tag
3. `path` *(optional with firebase)* : needed for matomo but not with firebase (log screen)
4. `action` *(optional with firebase)* : needed for matomo but not with firebase (log event)
5. `category` *(optional with firebase)* : needed for matomo but not with firebase (log event)
6. `tags`: which platform target
7. `comments` *(optional)*
8. `parameters` *(optional)*
**Parameters**
You can use parameters in generate methods.
1. `name`: name of the parameter
2. `type`: type of the parameter (Int, String, Bool, Double)
3. `replaceIn` *(optional)*
**Replace in**
This is section is equivalent of `%s | %d | %f | %@`. You can put the content of the parameter in *name*, *path*, *action*, *category*.
You need to put `_` + `NAME OF THE PARAMETER` + `_` in the target and which target you want in the value of `replaceIn`. (name need to be in uppercase)
## Images
Images generator will generate images assets along with extensions to access those images easily.
```sh
swift run -c release ResgenSwift images $FORCE_FLAG "./Images/images.txt" \
swift run -c release Imagium $FORCE_FLAG "./Images/images.txt" \
--xcassets-path "./Images/app.xcassets" \
--extension-output-path "./Images/Generated" \
--extension-name "AppImage" \
--extension-name-ui-kit "UIAppImage" \
--extension-suffix "GreatApp" \
--static-members true
--extension-suffix "GreatApp"
```
**Parameters**
@ -220,201 +141,11 @@ swift run -c release ResgenSwift images $FORCE_FLAG "./Images/images.txt" \
2. Input images definitions file
3. `--xcassets-path`: xcasset path where to generate imagesets
4. `--extension-output-path`: path where to generate generated extension
5. `--extension-name` *(optional)* : name of the class to add SwiftUI getters
6. `--extension-name-ui-kit` *(optional)* : name of the class to add UIKit getters
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
> ⚠️ Svg images will be copied in the assets and rendered as "Original", however if those images are not rendered correctly you can force the png generation by adding the key word "png" like this: id arrow_back 15 ? png
> ⚠️ If extension name is not set or is `UIImage`, it will generate static property on `UIImage`.
## All at once
# TODO
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?
extensionNameUIKit: String?
extensionSuffix: String?
staticMembers: Bool?
fonts:
-
inputFile: String
extensionOutputPath: String
extensionName: String?
extensionNameUIKit: String?
extensionSuffix: String?
staticMembers: Bool?
infoPlistPaths: [String]
images:
-
inputFile: String
xcassetsPath: String
extensionOutputPath: String
extensionName: String?
extensionNameUIKit: String?
extensionSuffix: String?
staticMembers: Bool?
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?
extensionNameUIKit: String?
extensionSuffix: String?
staticMembers: Bool?
-
inputFile: String
style: [light/all]
xcassetsPath: String
extensionOutputPath: String
extensionName: String?
extensionNameUIKit: 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: []
...
```
### File architecture
ResgenSwift generate extension of classes. Those classes must exists in your project. You can create them yourself OR you can let ResgenSwift create them by specifying what you want. Do as follow:
```
architecture:
  property: R *(required but not used)*
  classname: R
  path: ./path/to/generate
  children:
    - property: images
      classname: R2Image
    - property: strings
      classname: R2String
    - property: fonts
      classname: R2Font
    - property: images
      classname: R2Image
    - property: uikit
      classname: R2UI
      children:
        - property: images
          classname: R2UIImage
        - property: fonts
          classname: R2UIFont
        - property: images
          classname: R2UIImage
```
This will generate a file named as the architecture classname: `R.swift`. Based on the previous architecture, it will generate:
```
class R {
static let images = R2Image()
static let strings = R2String()
static let fonts = R2Font()
static let images = R2Image()
static let uikit = R2UI()
}
class R2Image {}
class R2String {}
class R2Font {}
class R2Image {}
class R2UI {
let images = R2UIImage()
let fonts = R2UIFont()
let images = R2UIImage()
}
class R2UIImage {}
class R2UIFont {}
class R2UIImage {}
```
### Usage
```sh
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 `/`)
## Binary usage
### Installation
Run `make install`. Binary will be install in `/usr/local/bin`.
Usage:
```sh
resgen-swift generate path/to/configuration.yml --project-directory ${PROJECT_DIR}
```
### Man page
Commands parameters and details can be find by running `resgen-swift --help`. If you prefer, a man page is also available. Run `man resgen-swift`. Man page is installed on `make install` but you can install manually by running `make create-and-install-man-file`.
### Uninstallation
To uninstall ResgenSwift: `make uninstall`.
[ ] Allow static variable generation on custom extension

View File

@ -1,21 +0,0 @@
// Generated by ResgenSwift.Color 1.2
import SwiftUI
extension ColorYolo {
/// Color red is #FF0000 (light) or #FF0000 (dark)"
var red: Color {
Color("red")
}
/// Color green_alpha_50 is #A000FF00 (light) or #A000FF00 (dark)"
var green_alpha_50: Color {
Color("green_alpha_50")
}
/// Color blue_light_dark is #0000FF (light) or #0000AA (dark)"
var blue_light_dark: Color {
Color("blue_light_dark")
}
}

View File

@ -1,8 +1,8 @@
// Generated by ResgenSwift.Color 1.2
// Generated from ColorToolCore at 2021-12-20 15:34:55 +0000
import UIKit
extension UIColorYolo {
extension R2Color {
/// Color red is #FF0000 (light) or #FF0000 (dark)"
@objc var red: UIColor {
@ -14,8 +14,8 @@ extension UIColorYolo {
UIColor(named: "green_alpha_50")!
}
/// Color blue_light_dark is #0000FF (light) or #0000AA (dark)"
/// Color blue_light_dark is #0000FF (light) or #0000FF (dark)"
@objc var blue_light_dark: UIColor {
UIColor(named: "blue_light_dark")!
}
}
}

View File

@ -0,0 +1,21 @@
// Generated from ColorToolCore at 2021-12-20 15:16:18 +0000
import UIKit
extension UIColor {
/// Color red is #FF0000 (light) or #FF0000 (dark)"
static var red: UIColor {
UIColor(named: "red")!
}
/// Color green_alpha_50 is #A000FF00 (light) or #A000FF00 (dark)"
static var green_alpha_50: UIColor {
UIColor(named: "green_alpha_50")!
}
/// Color blue_light_dark is #0000FF (light) or #0000AA (dark)"
static var blue_light_dark: UIColor {
UIColor(named: "blue_light_dark")!
}
}

View File

@ -0,0 +1,21 @@
// Generated from ColorToolCore at 2022-02-14 09:30:19 +0000
import UIKit
extension UIColor {
/// Color red is #FF0000 (light) or #FF0000 (dark)"
static var red: UIColor {
UIColor(named: "red")!
}
/// Color green_alpha_50 is #A000FF00 (light) or #A000FF00 (dark)"
static var green_alpha_50: UIColor {
UIColor(named: "green_alpha_50")!
}
/// Color blue_light_dark is #0000FF (light) or #0000AA (dark)"
static var blue_light_dark: UIColor {
UIColor(named: "blue_light_dark")!
}
}

View File

@ -0,0 +1,21 @@
// Generated from ColorToolCore at 2021-12-20 15:17:10 +0000
import UIKit
extension UIColor {
/// Color red is #FF0000 (light) or #FF0000 (dark)"
static var red: UIColor {
UIColor(named: "red")!
}
/// Color green_alpha_50 is #A000FF00 (light) or #A000FF00 (dark)"
static var green_alpha_50: UIColor {
UIColor(named: "green_alpha_50")!
}
/// Color blue_light_dark is #0000FF (light) or #0000AA (dark)"
static var blue_light_dark: UIColor {
UIColor(named: "blue_light_dark")!
}
}

View File

@ -0,0 +1,21 @@
// Generated by ResgenSwift.ColorTool 1.0.0
import UIKit
extension UIColor {
/// Color red is #FF0000 (light) or #FF0000 (dark)"
static var red: UIColor {
UIColor(named: "red")!
}
/// Color green_alpha_50 is #A000FF00 (light) or #A000FF00 (dark)"
static var green_alpha_50: UIColor {
UIColor(named: "green_alpha_50")!
}
/// Color blue_light_dark is #0000FF (light) or #0000AA (dark)"
static var blue_light_dark: UIColor {
UIColor(named: "blue_light_dark")!
}
}

View File

@ -1,61 +0,0 @@
// Generated by ResgenSwift.Fonts 1.2
import SwiftUI
extension FontYolo {
enum FontName: String {
case LatoItalic = "Lato-Italic"
case LatoLightItalic = "Lato-LightItalic"
case LatoHairline = "Lato-Hairline"
case LatoBold = "Lato-Bold"
case LatoBlack = "Lato-Black"
case LatoRegular = "Lato-Regular"
case LatoBlackItalic = "Lato-BlackItalic"
case LatoBoldItalic = "Lato-BoldItalic"
case LatoLight = "Lato-Light"
case LatoHairlineItalic = "Lato-HairlineItalic"
}
// MARK: - Getter
func LatoItalic(withSize size: CGFloat) -> Font {
Font.custom(FontName.LatoItalic.rawValue, size: size)
}
func LatoLightItalic(withSize size: CGFloat) -> Font {
Font.custom(FontName.LatoLightItalic.rawValue, size: size)
}
func LatoHairline(withSize size: CGFloat) -> Font {
Font.custom(FontName.LatoHairline.rawValue, size: size)
}
func LatoBold(withSize size: CGFloat) -> Font {
Font.custom(FontName.LatoBold.rawValue, size: size)
}
func LatoBlack(withSize size: CGFloat) -> Font {
Font.custom(FontName.LatoBlack.rawValue, size: size)
}
func LatoRegular(withSize size: CGFloat) -> Font {
Font.custom(FontName.LatoRegular.rawValue, size: size)
}
func LatoBlackItalic(withSize size: CGFloat) -> Font {
Font.custom(FontName.LatoBlackItalic.rawValue, size: size)
}
func LatoBoldItalic(withSize size: CGFloat) -> Font {
Font.custom(FontName.LatoBoldItalic.rawValue, size: size)
}
func LatoLight(withSize size: CGFloat) -> Font {
Font.custom(FontName.LatoLight.rawValue, size: size)
}
func LatoHairlineItalic(withSize size: CGFloat) -> Font {
Font.custom(FontName.LatoHairlineItalic.rawValue, size: size)
}
}

View File

@ -0,0 +1,62 @@
// Generated from FontToolCore
import UIKit
extension R2Font {
enum FontName: String {
case LatoItalic = "Lato-Italic"
case LatoLightItalic = "Lato-LightItalic"
case LatoHairline = "Lato-Hairline"
case LatoBold = "Lato-Bold"
case LatoBlack = "Lato-Black"
case LatoRegular = "Lato-Regular"
case LatoBlackItalic = "Lato-BlackItalic"
case LatoBoldItalic = "Lato-BoldItalic"
case LatoLight = "Lato-Light"
case LatoHairlineItalic = "Lato-HairlineItalic"
}
// MARK: - Getter
func LatoItalic(withSize size: CGFloat) -> UIFont {
UIFont(name: FontName.LatoItalic.rawValue, size: size)!
}
func LatoLightItalic(withSize size: CGFloat) -> UIFont {
UIFont(name: FontName.LatoLightItalic.rawValue, size: size)!
}
func LatoHairline(withSize size: CGFloat) -> UIFont {
UIFont(name: FontName.LatoHairline.rawValue, size: size)!
}
func LatoBold(withSize size: CGFloat) -> UIFont {
UIFont(name: FontName.LatoBold.rawValue, size: size)!
}
func LatoBlack(withSize size: CGFloat) -> UIFont {
UIFont(name: FontName.LatoBlack.rawValue, size: size)!
}
func LatoRegular(withSize size: CGFloat) -> UIFont {
UIFont(name: FontName.LatoRegular.rawValue, size: size)!
}
func LatoBlackItalic(withSize size: CGFloat) -> UIFont {
UIFont(name: FontName.LatoBlackItalic.rawValue, size: size)!
}
func LatoBoldItalic(withSize size: CGFloat) -> UIFont {
UIFont(name: FontName.LatoBoldItalic.rawValue, size: size)!
}
func LatoLight(withSize size: CGFloat) -> UIFont {
UIFont(name: FontName.LatoLight.rawValue, size: size)!
}
func LatoHairlineItalic(withSize size: CGFloat) -> UIFont {
UIFont(name: FontName.LatoHairlineItalic.rawValue, size: size)!
}
}

View File

@ -0,0 +1,62 @@
// Generated from FontToolCore
import UIKit
extension R2Font {
enum FontName: String {
case LatoItalic = "Lato-Italic"
case LatoLightItalic = "Lato-LightItalic"
case LatoHairline = "Lato-Hairline"
case LatoBold = "Lato-Bold"
case LatoBlack = "Lato-Black"
case LatoRegular = "Lato-Regular"
case LatoBlackItalic = "Lato-BlackItalic"
case LatoBoldItalic = "Lato-BoldItalic"
case LatoLight = "Lato-Light"
case LatoHairlineItalic = "Lato-HairlineItalic"
}
// MARK: - Getter
func LatoItalic(withSize size: CGFloat) -> UIFont {
UIFont(name: FontName.LatoItalic.rawValue, size: size)!
}
func LatoLightItalic(withSize size: CGFloat) -> UIFont {
UIFont(name: FontName.LatoLightItalic.rawValue, size: size)!
}
func LatoHairline(withSize size: CGFloat) -> UIFont {
UIFont(name: FontName.LatoHairline.rawValue, size: size)!
}
func LatoBold(withSize size: CGFloat) -> UIFont {
UIFont(name: FontName.LatoBold.rawValue, size: size)!
}
func LatoBlack(withSize size: CGFloat) -> UIFont {
UIFont(name: FontName.LatoBlack.rawValue, size: size)!
}
func LatoRegular(withSize size: CGFloat) -> UIFont {
UIFont(name: FontName.LatoRegular.rawValue, size: size)!
}
func LatoBlackItalic(withSize size: CGFloat) -> UIFont {
UIFont(name: FontName.LatoBlackItalic.rawValue, size: size)!
}
func LatoBoldItalic(withSize size: CGFloat) -> UIFont {
UIFont(name: FontName.LatoBoldItalic.rawValue, size: size)!
}
func LatoLight(withSize size: CGFloat) -> UIFont {
UIFont(name: FontName.LatoLight.rawValue, size: size)!
}
func LatoHairlineItalic(withSize size: CGFloat) -> UIFont {
UIFont(name: FontName.LatoHairlineItalic.rawValue, size: size)!
}
}

View File

@ -0,0 +1,63 @@
// Lato/Lato-Italic.ttf Lato/Lato-LightItalic.ttf Lato/Lato-Thin.ttf Lato/Lato-Bold.ttf Lato/Lato-Black.ttf Lato/Lato-Regular.ttf Lato/Lato-BlackItalic.ttf Lato/Lato-BoldItalic.ttf Lato/Lato-Light.ttf Lato/Lato-ThinItalic.ttf
// Generated from FontToolCore
import UIKit
extension UIFont {
enum FontName: String {
case LatoItalic = "Lato-Italic"
case LatoLightItalic = "Lato-LightItalic"
case LatoHairline = "Lato-Hairline"
case LatoBold = "Lato-Bold"
case LatoBlack = "Lato-Black"
case LatoRegular = "Lato-Regular"
case LatoBlackItalic = "Lato-BlackItalic"
case LatoBoldItalic = "Lato-BoldItalic"
case LatoLight = "Lato-Light"
case LatoHairlineItalic = "Lato-HairlineItalic"
}
// MARK: - Getter
static let LatoItalic: ((_ size: CGFloat) -> UIFont) = { size in
UIFont(name: FontName.LatoItalic.rawValue, size: size)!
}
static let LatoLightItalic: ((_ size: CGFloat) -> UIFont) = { size in
UIFont(name: FontName.LatoLightItalic.rawValue, size: size)!
}
static let LatoHairline: ((_ size: CGFloat) -> UIFont) = { size in
UIFont(name: FontName.LatoHairline.rawValue, size: size)!
}
static let LatoBold: ((_ size: CGFloat) -> UIFont) = { size in
UIFont(name: FontName.LatoBold.rawValue, size: size)!
}
static let LatoBlack: ((_ size: CGFloat) -> UIFont) = { size in
UIFont(name: FontName.LatoBlack.rawValue, size: size)!
}
static let LatoRegular: ((_ size: CGFloat) -> UIFont) = { size in
UIFont(name: FontName.LatoRegular.rawValue, size: size)!
}
static let LatoBlackItalic: ((_ size: CGFloat) -> UIFont) = { size in
UIFont(name: FontName.LatoBlackItalic.rawValue, size: size)!
}
static let LatoBoldItalic: ((_ size: CGFloat) -> UIFont) = { size in
UIFont(name: FontName.LatoBoldItalic.rawValue, size: size)!
}
static let LatoLight: ((_ size: CGFloat) -> UIFont) = { size in
UIFont(name: FontName.LatoLight.rawValue, size: size)!
}
static let LatoHairlineItalic: ((_ size: CGFloat) -> UIFont) = { size in
UIFont(name: FontName.LatoHairlineItalic.rawValue, size: size)!
}
}

View File

@ -0,0 +1,62 @@
// Generated from FontToolCore
import UIKit
extension UIFont {
enum FontName: String {
case LatoItalic = "Lato-Italic"
case LatoLightItalic = "Lato-LightItalic"
case LatoHairline = "Lato-Hairline"
case LatoBold = "Lato-Bold"
case LatoBlack = "Lato-Black"
case LatoRegular = "Lato-Regular"
case LatoBlackItalic = "Lato-BlackItalic"
case LatoBoldItalic = "Lato-BoldItalic"
case LatoLight = "Lato-Light"
case LatoHairlineItalic = "Lato-HairlineItalic"
}
// MARK: - Getter
static let LatoItalic: ((_ size: CGFloat) -> UIFont) = { size in
UIFont(name: FontName.LatoItalic.rawValue, size: size)!
}
static let LatoLightItalic: ((_ size: CGFloat) -> UIFont) = { size in
UIFont(name: FontName.LatoLightItalic.rawValue, size: size)!
}
static let LatoHairline: ((_ size: CGFloat) -> UIFont) = { size in
UIFont(name: FontName.LatoHairline.rawValue, size: size)!
}
static let LatoBold: ((_ size: CGFloat) -> UIFont) = { size in
UIFont(name: FontName.LatoBold.rawValue, size: size)!
}
static let LatoBlack: ((_ size: CGFloat) -> UIFont) = { size in
UIFont(name: FontName.LatoBlack.rawValue, size: size)!
}
static let LatoRegular: ((_ size: CGFloat) -> UIFont) = { size in
UIFont(name: FontName.LatoRegular.rawValue, size: size)!
}
static let LatoBlackItalic: ((_ size: CGFloat) -> UIFont) = { size in
UIFont(name: FontName.LatoBlackItalic.rawValue, size: size)!
}
static let LatoBoldItalic: ((_ size: CGFloat) -> UIFont) = { size in
UIFont(name: FontName.LatoBoldItalic.rawValue, size: size)!
}
static let LatoLight: ((_ size: CGFloat) -> UIFont) = { size in
UIFont(name: FontName.LatoLight.rawValue, size: size)!
}
static let LatoHairlineItalic: ((_ size: CGFloat) -> UIFont) = { size in
UIFont(name: FontName.LatoHairlineItalic.rawValue, size: size)!
}
}

View File

@ -0,0 +1,61 @@
// Generated by ResgenSwift.FontTool 1.0.0
import UIKit
extension UIFont {
enum FontName: String {
case LatoItalic = "Lato-Italic"
case LatoLightItalic = "Lato-LightItalic"
case LatoHairline = "Lato-Hairline"
case LatoBold = "Lato-Bold"
case LatoBlack = "Lato-Black"
case LatoRegular = "Lato-Regular"
case LatoBlackItalic = "Lato-BlackItalic"
case LatoBoldItalic = "Lato-BoldItalic"
case LatoLight = "Lato-Light"
case LatoHairlineItalic = "Lato-HairlineItalic"
}
// MARK: - Getter
static let LatoItalic: ((_ size: CGFloat) -> UIFont) = { size in
UIFont(name: FontName.LatoItalic.rawValue, size: size)!
}
static let LatoLightItalic: ((_ size: CGFloat) -> UIFont) = { size in
UIFont(name: FontName.LatoLightItalic.rawValue, size: size)!
}
static let LatoHairline: ((_ size: CGFloat) -> UIFont) = { size in
UIFont(name: FontName.LatoHairline.rawValue, size: size)!
}
static let LatoBold: ((_ size: CGFloat) -> UIFont) = { size in
UIFont(name: FontName.LatoBold.rawValue, size: size)!
}
static let LatoBlack: ((_ size: CGFloat) -> UIFont) = { size in
UIFont(name: FontName.LatoBlack.rawValue, size: size)!
}
static let LatoRegular: ((_ size: CGFloat) -> UIFont) = { size in
UIFont(name: FontName.LatoRegular.rawValue, size: size)!
}
static let LatoBlackItalic: ((_ size: CGFloat) -> UIFont) = { size in
UIFont(name: FontName.LatoBlackItalic.rawValue, size: size)!
}
static let LatoBoldItalic: ((_ size: CGFloat) -> UIFont) = { size in
UIFont(name: FontName.LatoBoldItalic.rawValue, size: size)!
}
static let LatoLight: ((_ size: CGFloat) -> UIFont) = { size in
UIFont(name: FontName.LatoLight.rawValue, size: size)!
}
static let LatoHairlineItalic: ((_ size: CGFloat) -> UIFont) = { size in
UIFont(name: FontName.LatoHairlineItalic.rawValue, size: size)!
}
}

View File

@ -1,61 +0,0 @@
// Generated by ResgenSwift.Fonts 1.2
import UIKit
extension UIFontYolo {
enum FontName: String {
case LatoItalic = "Lato-Italic"
case LatoLightItalic = "Lato-LightItalic"
case LatoHairline = "Lato-Hairline"
case LatoBold = "Lato-Bold"
case LatoBlack = "Lato-Black"
case LatoRegular = "Lato-Regular"
case LatoBlackItalic = "Lato-BlackItalic"
case LatoBoldItalic = "Lato-BoldItalic"
case LatoLight = "Lato-Light"
case LatoHairlineItalic = "Lato-HairlineItalic"
}
// MARK: - Getter
func LatoItalic(withSize size: CGFloat) -> UIFont {
UIFont(name: FontName.LatoItalic.rawValue, size: size)!
}
func LatoLightItalic(withSize size: CGFloat) -> UIFont {
UIFont(name: FontName.LatoLightItalic.rawValue, size: size)!
}
func LatoHairline(withSize size: CGFloat) -> UIFont {
UIFont(name: FontName.LatoHairline.rawValue, size: size)!
}
func LatoBold(withSize size: CGFloat) -> UIFont {
UIFont(name: FontName.LatoBold.rawValue, size: size)!
}
func LatoBlack(withSize size: CGFloat) -> UIFont {
UIFont(name: FontName.LatoBlack.rawValue, size: size)!
}
func LatoRegular(withSize size: CGFloat) -> UIFont {
UIFont(name: FontName.LatoRegular.rawValue, size: size)!
}
func LatoBlackItalic(withSize size: CGFloat) -> UIFont {
UIFont(name: FontName.LatoBlackItalic.rawValue, size: size)!
}
func LatoBoldItalic(withSize size: CGFloat) -> UIFont {
UIFont(name: FontName.LatoBoldItalic.rawValue, size: size)!
}
func LatoLight(withSize size: CGFloat) -> UIFont {
UIFont(name: FontName.LatoLight.rawValue, size: size)!
}
func LatoHairlineItalic(withSize size: CGFloat) -> UIFont {
UIFont(name: FontName.LatoHairlineItalic.rawValue, size: size)!
}
}

View File

@ -0,0 +1,62 @@
// Generated from FontToolCore
import UIKit
extension UIFont {
enum FontName: String {
case LatoItalic = "Lato-Italic"
case LatoLightItalic = "Lato-LightItalic"
case LatoHairline = "Lato-Hairline"
case LatoBold = "Lato-Bold"
case LatoBlack = "Lato-Black"
case LatoRegular = "Lato-Regular"
case LatoBlackItalic = "Lato-BlackItalic"
case LatoBoldItalic = "Lato-BoldItalic"
case LatoLight = "Lato-Light"
case LatoHairlineItalic = "Lato-HairlineItalic"
}
// MARK: - Getter
static let LatoItalic: ((_ size: CGFloat) -> UIFont) = { size in
UIFont(name: FontName.LatoItalic.rawValue, size: size)!
}
static let LatoLightItalic: ((_ size: CGFloat) -> UIFont) = { size in
UIFont(name: FontName.LatoLightItalic.rawValue, size: size)!
}
static let LatoHairline: ((_ size: CGFloat) -> UIFont) = { size in
UIFont(name: FontName.LatoHairline.rawValue, size: size)!
}
static let LatoBold: ((_ size: CGFloat) -> UIFont) = { size in
UIFont(name: FontName.LatoBold.rawValue, size: size)!
}
static let LatoBlack: ((_ size: CGFloat) -> UIFont) = { size in
UIFont(name: FontName.LatoBlack.rawValue, size: size)!
}
static let LatoRegular: ((_ size: CGFloat) -> UIFont) = { size in
UIFont(name: FontName.LatoRegular.rawValue, size: size)!
}
static let LatoBlackItalic: ((_ size: CGFloat) -> UIFont) = { size in
UIFont(name: FontName.LatoBlackItalic.rawValue, size: size)!
}
static let LatoBoldItalic: ((_ size: CGFloat) -> UIFont) = { size in
UIFont(name: FontName.LatoBoldItalic.rawValue, size: size)!
}
static let LatoLight: ((_ size: CGFloat) -> UIFont) = { size in
UIFont(name: FontName.LatoLight.rawValue, size: size)!
}
static let LatoHairlineItalic: ((_ size: CGFloat) -> UIFont) = { size in
UIFont(name: FontName.LatoHairlineItalic.rawValue, size: size)!
}
}

View File

@ -1,21 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NewArr</key>
<array/>
<key>UIAppFonts</key>
<array>
<string>Lato-Italic</string>
<string>Lato-LightItalic</string>
<string>Lato-Hairline</string>
<string>Lato-Bold</string>
<string>Lato-Black</string>
<string>Lato-Regular</string>
<string>Lato-BlackItalic</string>
<string>Lato-BoldItalic</string>
<string>Lato-Light</string>
<string>Lato-HairlineItalic</string>
</array>
</dict>
</plist>

View File

@ -1,21 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NewArr</key>
<array/>
<key>UIAppFonts</key>
<array>
<string>Lato-Italic</string>
<string>Lato-LightItalic</string>
<string>Lato-Hairline</string>
<string>Lato-Bold</string>
<string>Lato-Black</string>
<string>Lato-Regular</string>
<string>Lato-BlackItalic</string>
<string>Lato-BoldItalic</string>
<string>Lato-Light</string>
<string>Lato-HairlineItalic</string>
</array>
</dict>
</plist>

View File

@ -1,31 +0,0 @@
// Generated by ResgenSwift.Images 1.2
// Images from sampleImages
import SwiftUI
extension ImageYolo {
var article_notification_pull_detail: Image {
Image("article_notification_pull_detail")
}
var article_notification_pull: Image {
Image("article_notification_pull")
}
var new_article: Image {
Image("new_article")
}
var welcome_background: Image {
Image("welcome_background")
}
var article_trash: Image {
Image("article_trash")
}
var ic_close_article: Image {
Image("ic_close_article")
}
}

View File

@ -1,31 +1,32 @@
// Generated by ResgenSwift.Images 1.2
// Generated by ResgenSwift.Imagium 1.0.0
// Images from sampleImages
import UIKit
extension UIImage {
var article_notification_pull_detail: UIImage {
static var article_notification_pull_detail: UIImage {
UIImage(named: "article_notification_pull_detail")!
}
var article_notification_pull: UIImage {
static var article_notification_pull: UIImage {
UIImage(named: "article_notification_pull")!
}
var new_article: UIImage {
static var new_article: UIImage {
UIImage(named: "new_article")!
}
var welcome_background: UIImage {
static var welcome_background: UIImage {
UIImage(named: "welcome_background")!
}
var article_trash: UIImage {
static var article_trash: UIImage {
UIImage(named: "article_trash")!
}
var ic_close_article: UIImage {
static var ic_close_article: UIImage {
UIImage(named: "ic_close_article")!
}
}
}

View File

@ -0,0 +1,32 @@
// Generated from Imagium at 2022-02-14 09:30:23 +0000
// Images from sampleImages
import UIKit
extension UIImage {
static var article_notification_pull_detail: UIImage {
UIImage(named: "article_notification_pull_detail")!
}
static var article_notification_pull: UIImage {
UIImage(named: "article_notification_pull")!
}
static var new_article: UIImage {
UIImage(named: "new_article")!
}
static var welcome_background: UIImage {
UIImage(named: "welcome_background")!
}
static var article_trash: UIImage {
UIImage(named: "article_trash")!
}
static var ic_close_article: UIImage {
UIImage(named: "ic_close_article")!
}
}

View File

@ -0,0 +1,37 @@
// Generated from StringToolCore at 2022-01-10 08:27:11 +0000
import UIKit
fileprivate let kStringsFileName = "sampleStrings"
extension MyString {
// MARK: - Webservice
/// Translation in en :
/// en
var param_lang: String {
NSLocalizedString("param_lang", tableName: kStringsFileName, bundle: Bundle.main, value: "en", comment: "")
}
// MARK: - Generic
/// Translation in en :
/// Back
var generic_back: String {
NSLocalizedString("generic_back", tableName: kStringsFileName, bundle: Bundle.main, value: "Back", comment: "")
}
/// Translation in en :
/// Loading data...
var generic_loading_data: String {
NSLocalizedString("generic_loading_data", tableName: kStringsFileName, bundle: Bundle.main, value: "Loading data...", comment: "")
}
/// Translation in en :
/// Welcome %@ !
var generic_welcome_firstname_format: String {
NSLocalizedString("generic_welcome_firstname_format", tableName: kStringsFileName, bundle: Bundle.main, value: "Welcome %@ !", comment: "")
}
}

View File

@ -1,4 +1,4 @@
// Generated by ResgenSwift.Strings.Stringium 1.2
// Generated by ResgenSwift.Strings.Stringium 1.0.0
import UIKit
@ -6,79 +6,60 @@ fileprivate let kStringsFileName = "sampleStrings"
extension String {
enum KeyGenAllScript: String {
case param_lang = "param_lang"
case generic_back = "generic_back"
case generic_loading_data = "generic_loading_data"
case generic_welcome_firstname_format = "generic_welcome_firstname_format"
case test_equal_symbol = "test_equal_symbol"
case placeholders_test_one = "placeholders_test_one"
var keyPath: KeyPath<String, String> {
switch self {
case .param_lang: return \String.param_lang
case .generic_back: return \String.generic_back
case .generic_loading_data: return \String.generic_loading_data
case .generic_welcome_firstname_format: return \String.generic_welcome_firstname_format
case .test_equal_symbol: return \String.test_equal_symbol
case .placeholders_test_one: return \String.placeholders_test_one
}
}
}
// MARK: - Webservice
// MARK: - Webservice
/// Translation in en :
/// en
var param_lang: String {
static var param_lang: String {
NSLocalizedString("param_lang", tableName: kStringsFileName, bundle: Bundle.main, value: "en", comment: "")
}
// MARK: - Generic
// MARK: - Generic
/// Translation in en :
/// Back
var generic_back: String {
static var generic_back: String {
NSLocalizedString("generic_back", tableName: kStringsFileName, bundle: Bundle.main, value: "Back", comment: "")
}
/// Translation in en :
/// Loading data...
var generic_loading_data: String {
static var generic_loading_data: String {
NSLocalizedString("generic_loading_data", tableName: kStringsFileName, bundle: Bundle.main, value: "Loading data...", comment: "")
}
/// Translation in en :
/// Welcome \"%@\" !
var generic_welcome_firstname_format: String {
static var generic_welcome_firstname_format: String {
NSLocalizedString("generic_welcome_firstname_format", tableName: kStringsFileName, bundle: Bundle.main, value: "Welcome \"%@\" !", comment: "")
}
/// Translation in en :
/// Welcome \"%@\" !
func generic_welcome_firstname_format(arg0: String) -> String {
String(format: self.generic_welcome_firstname_format, arg0)
static func generic_welcome_firstname_format(arg0: String) -> String {
String(format: Self.generic_welcome_firstname_format, arg0)
}
// MARK: - EqualSymbol
// MARK: - EqualSymbol
/// Translation in en :
/// 1 = 1 point !
var test_equal_symbol: String {
static var test_equal_symbol: String {
NSLocalizedString("test_equal_symbol", tableName: kStringsFileName, bundle: Bundle.main, value: "1€ = 1 point !", comment: "")
}
// MARK: - Placeholders
// MARK: - Placeholders
/// Translation in en :
/// You %%: %2$@ %1$@ Age: %3$d
var placeholders_test_one: String {
static var placeholders_test_one: String {
NSLocalizedString("placeholders_test_one", tableName: kStringsFileName, bundle: Bundle.main, value: "You %%: %2$@ %1$@ Age: %3$d", comment: "")
}
/// Translation in en :
/// You %%: %2$@ %1$@ Age: %3$d
func placeholders_test_one(arg0: String, arg1: String, arg2: Int) -> String {
String(format: self.placeholders_test_one, arg0, arg1, arg2)
static func placeholders_test_one(arg0: String, arg1: String, arg2: Int) -> String {
String(format: Self.placeholders_test_one, arg0, arg1, arg2)
}
}
}

View File

@ -0,0 +1,57 @@
// Generated from Strings-Stringium at 2022-03-07 11:02:15 +0000
import UIKit
fileprivate let kStringsFileName = "sampleStrings"
extension String {
// MARK: - Webservice
/// Translation in en :
/// en
static var param_lang: String {
NSLocalizedString("param_lang", tableName: kStringsFileName, bundle: Bundle.main, value: "en", comment: "")
}
// MARK: - Generic
/// Translation in en :
/// Back
static var generic_back: String {
NSLocalizedString("generic_back", tableName: kStringsFileName, bundle: Bundle.main, value: "Back", comment: "")
}
/// Translation in en :
/// Loading data...
static var generic_loading_data: String {
NSLocalizedString("generic_loading_data", tableName: kStringsFileName, bundle: Bundle.main, value: "Loading data...", comment: "")
}
/// Translation in en :
/// Welcome \"%@\" !
static var generic_welcome_firstname_format: String {
NSLocalizedString("generic_welcome_firstname_format", tableName: kStringsFileName, bundle: Bundle.main, value: "Welcome \"%@\" !", comment: "")
}
/// Translation in en :
/// Welcome \"%@\" !
static func generic_welcome_firstname_format(arg0: String) -> String {
String(format: Self.generic_welcome_firstname_format, arg0)
}
// MARK: - Placeholders
/// Translation in en :
/// You %%: %2$@ %1$@ Age: %3$d
static var placeholders_test_one: String {
NSLocalizedString("placeholders_test_one", tableName: kStringsFileName, bundle: Bundle.main, value: "You %%: %2$@ %1$@ Age: %3$d", comment: "")
}
/// Translation in en :
/// You %%: %2$@ %1$@ Age: %3$d
static func placeholders_test_one(arg0: String, arg1: String, arg2: Int) -> String {
String(format: Self.placeholders_test_one, arg0, arg1, arg2)
}
}

View File

@ -0,0 +1,37 @@
// Generated from StringToolCore at 2022-01-10 08:39:52 +0000
import UIKit
fileprivate let kStringsFileName = "sampleStrings"
extension String {
// MARK: - Webservice
/// Translation in en :
/// en
static var param_lang: String {
NSLocalizedString("param_lang", tableName: kStringsFileName, bundle: Bundle.main, value: "en", comment: "")
}
// MARK: - Generic
/// Translation in en :
/// Back
static var generic_back: String {
NSLocalizedString("generic_back", tableName: kStringsFileName, bundle: Bundle.main, value: "Back", comment: "")
}
/// Translation in en :
/// Loading data...
static var generic_loading_data: String {
NSLocalizedString("generic_loading_data", tableName: kStringsFileName, bundle: Bundle.main, value: "Loading data...", comment: "")
}
/// Translation in en :
/// Welcome \"%@\" !
static var generic_welcome_firstname_format: String {
NSLocalizedString("generic_welcome_firstname_format", tableName: kStringsFileName, bundle: Bundle.main, value: "Welcome \"%@\" !", comment: "")
}
}

View File

@ -0,0 +1,57 @@
// Generated from Strings-Stringium at 2022-03-07 11:00:52 +0000
import UIKit
fileprivate let kStringsFileName = "sampleStrings"
extension StringTest {
// MARK: - Webservice
/// Translation in en :
/// en
var param_lang: String {
NSLocalizedString("param_lang", tableName: kStringsFileName, bundle: Bundle.main, value: "en", comment: "")
}
// MARK: - Generic
/// Translation in en :
/// Back
var generic_back: String {
NSLocalizedString("generic_back", tableName: kStringsFileName, bundle: Bundle.main, value: "Back", comment: "")
}
/// Translation in en :
/// Loading data...
var generic_loading_data: String {
NSLocalizedString("generic_loading_data", tableName: kStringsFileName, bundle: Bundle.main, value: "Loading data...", comment: "")
}
/// Translation in en :
/// Welcome \"%@\" !
var generic_welcome_firstname_format: String {
NSLocalizedString("generic_welcome_firstname_format", tableName: kStringsFileName, bundle: Bundle.main, value: "Welcome \"%@\" !", comment: "")
}
/// Translation in en :
/// Welcome \"%@\" !
func generic_welcome_firstname_format(arg0: String) -> String {
String(format: self.generic_welcome_firstname_format, arg0)
}
// MARK: - Placeholders
/// Translation in en :
/// You %%: %2$@ %1$@ Age: %3$d
var placeholders_test_one: String {
NSLocalizedString("placeholders_test_one", tableName: kStringsFileName, bundle: Bundle.main, value: "You %%: %2$@ %1$@ Age: %3$d", comment: "")
}
/// Translation in en :
/// You %%: %2$@ %1$@ Age: %3$d
func placeholders_test_one(arg0: String, arg1: String, arg2: Int) -> String {
String(format: self.placeholders_test_one, arg0, arg1, arg2)
}
}

View File

@ -1,6 +1,6 @@
/**
* Apple Strings File
* Generated by ResgenSwift 1.2
* Generated by ResgenSwift 1.0.0
* Language: en-us
*/

View File

@ -1,6 +1,6 @@
/**
* Apple Strings File
* Generated by ResgenSwift 1.2
* Generated by ResgenSwift 1.0.0
* Language: en
*/

View File

@ -1,6 +1,6 @@
/**
* Apple Strings File
* Generated by ResgenSwift 1.2
* Generated by ResgenSwift 1.0.0
* Language: fr
*/

View File

@ -1,205 +0,0 @@
// Generated by ResgenSwift.Analytics 1.2
import MatomoTracker
import FirebaseAnalytics
// MARK: - Protocol
protocol AnalyticsManagerProtocol {
func logScreen(name: String, path: String)
func logEvent(
name: String,
action: String,
category: String,
params: [String: Any]?
)
}
// MARK: - Matomo
class MatomoAnalyticsManager: AnalyticsManagerProtocol {
// MARK: - Properties
private var tracker: MatomoTracker
// MARK: - Init
init(siteId: String, url: String) {
debugPrint("[Matomo service] Server URL: \(url)")
debugPrint("[Matomo service] Site ID: \(siteId)")
tracker = MatomoTracker(
siteId: siteId,
baseURL: URL(string: url)!
)
#if DEBUG
tracker.dispatchInterval = 5
#endif
#if DEBUG
tracker.logger = DefaultLogger(minLevel: .verbose)
#endif
debugPrint("[Matomo service] Configured with content base: \(tracker.contentBase?.absoluteString ?? "-")")
debugPrint("[Matomo service] Opt out: \(tracker.isOptedOut)")
}
// MARK: - Methods
func logScreen(name: String, path: String) {
guard !tracker.isOptedOut else { return }
guard let trackerUrl = tracker.contentBase?.absoluteString else { return }
let urlString = URL(string: "\(trackerUrl)" + "/" + "\(path)" + "iOS")
tracker.track(
view: [name],
url: urlString
)
}
func logEvent(
name: String,
action: String,
category: String,
params: [String: Any]?
) {
guard !tracker.isOptedOut else { return }
tracker.track(
eventWithCategory: category,
action: action,
name: name,
number: nil,
url: nil
)
}
}
// MARK: - Firebase
class FirebaseAnalyticsManager: AnalyticsManagerProtocol {
func logScreen(name: String, path: String) {
var parameters = [
AnalyticsParameterScreenName: name as NSObject
]
Analytics.logEvent(
AnalyticsEventScreenView,
parameters: parameters
)
}
func logEvent(
name: String,
action: String,
category: String,
params: [String: Any]?
) {
var parameters: [String:NSObject] = [
"action": action as NSObject,
"category": category as NSObject,
]
if let supplementaryParameters = params {
for (newKey, newValue) in supplementaryParameters {
if parameters.contains(where: { (key: String, value: NSObject) in
key == newKey
}) {
continue
}
parameters[newKey] = newValue as? NSObject
}
}
Analytics.logEvent(
name.replacingOccurrences(of: [" "], with: "_"),
parameters: parameters
)
}
}
// MARK: - Manager
class AnalyticsManager {
static var shared = AnalyticsManager()
// MARK: - Properties
var managers: [AnalyticsManagerProtocol] = []
private var isEnabled: Bool = true
// MARK: - Methods
func setAnalyticsEnabled(_ enable: Bool) {
isEnabled = enable
}
func configure(siteId: String, url: String) {
managers.append(
MatomoAnalyticsManager(
siteId: siteId,
url: url
)
)
managers.append(FirebaseAnalyticsManager())
}
private func logScreen(name: String, path: String) {
guard isEnabled else { return }
managers.forEach { manager in
manager.logScreen(name: name, path: path)
}
}
private func logEvent(
name: String,
action: String,
category: String,
params: [String: Any]?
) {
guard isEnabled else { return }
managers.forEach { manager in
manager.logEvent(
name: name,
action: action,
category: category,
params: params
)
}
}
// MARK: - section_one
func logScreenS1DefOne(title: String) {
logScreen(
name: "s1 def one \(title)",
path: "s1_def_one/\(title)"
)
}
func logEventS1DefTwo(title: String, count: String) {
logEvent(
name: "s1 def two",
action: "test",
category: "test",
params: [
"title": title,
"count": count
]
)
}
// MARK: - section_two
func logScreenS2DefOne() {
logScreen(
name: "s2 def one",
path: "s2_def_one/"
)
}
}

View File

@ -1,20 +1,23 @@
// Generated by ResgenSwift.Strings.Tags 1.2
// Generated by ResgenSwift.Strings.Tags 1.0.0
// typelias Tags = String
import UIKit
extension Tags {
// MARK: - ScreenTag
// MARK: - ScreenTag
/// Translation in ium :
/// Ecran un
var screen_one: String {
static var screen_one: String {
"Ecran un"
}
/// Translation in ium :
/// Ecran deux
var screen_two: String {
static var screen_two: String {
"Ecran deux"
}
}
}

View File

@ -0,0 +1,23 @@
// Generated from Strings-Tags at 2022-02-14 09:30:20 +0000
// typelias Tags = String
import UIKit
extension Tags {
// MARK: - ScreenTag
/// Translation in ium :
/// Ecran un
static var screen_one: String {
"Ecran un"
}
/// Translation in ium :
/// Ecran deux
static var screen_two: String {
"Ecran deux"
}
}

View File

@ -1,52 +0,0 @@
---
categories:
- id: section_one
screens:
- id: s1_def_one
name: s1 def one _TITLE_
path: s1_def_one/_TITLE_
tags: ios,droid
parameters:
- name: title
type: String
replaceIn: name,path
events:
- id: s1_def_two
name: s1 def two
action: test
category: test
tags: ios
parameters:
- name: title
type: String
- name: count
type: String
- id: section_two
screens:
- id: s2_def_one
name: s2 def one
path: s2_def_one/
tags: ios
events:
- id: s2_def_two
name: s2 def two
action: test
category: test
tags: droid
- id: section_three
screens:
- id: s3_def_one
name: s3 def one
path: s3_def_one/
tags: droid
events:
- id: s3_def_two
name: s3 def two
action: test
category: test
tags: droid

View File

@ -2,69 +2,56 @@
FORCE_FLAG="$1"
## Font
#swift run -c release ResgenSwift fonts $FORCE_FLAG "./Fonts/sampleFontsAll.txt" \
# --extension-output-path "./Fonts/Generated" \
# --extension-name "FontYolo" \
# --extension-name-ui-kit "UIFontYolo" \
# --extension-suffix "GenAllScript" \
# --info-plist-paths "./Fonts/Generated/test.plist ./Fonts/Generated/test2.plist"
#
#echo "\n-------------------------\n"
#
## Color
#swift run -c release ResgenSwift colors $FORCE_FLAG "./Colors/sampleColors1.txt" \
# --style all \
# --xcassets-path "./Colors/colors.xcassets" \
# --extension-output-path "./Colors/Generated/" \
# --extension-name "ColorYolo" \
# --extension-name-ui-kit "UIhkjhkColorYolo" \
# --extension-suffix "GenAllScript"
#
#echo "\n-------------------------\n"
#
## Twine
#swift run -c release ResgenSwift strings twine $FORCE_FLAG "./Twine/sampleStrings.txt" \
# --output-path "./Twine/Generated" \
# --langs "fr en en-us" \
# --default-lang "en" \
# --extension-output-path "./Twine/Generated"
#echo "\n-------------------------\n"
## Strings
#swift run -c release ResgenSwift strings stringium $FORCE_FLAG "./Strings/sampleStrings.txt" \
# --output-path "./Strings/Generated" \
# --langs "fr en en-us" \
# --default-lang "en" \
# --extension-output-path "./Strings/Generated" \
# --extension-name "String" \
# --extension-suffix "GenAllScript"
#echo "\n-------------------------\n"
## Tags
#swift run -c release ResgenSwift strings tags $FORCE_FLAG "./Tags/sampleTags.txt" \
# --lang "ium" \
# --extension-output-path "./Tags/Generated" \
# --extension-name "Tags" \
# --extension-suffix "GenAllScript"
#echo "\n-------------------------\n"
# Analytics
swift run -c release ResgenSwift analytics $FORCE_FLAG "./Tags/sampleTags.yml" \
--target "matomo firebase" \
--extension-output-path "./Tags/Generated" \
--extension-name "Analytics" \
# Font
swift run -c release FontTool $FORCE_FLAG "./Fonts/sampleFontsAll.txt" \
--extension-output-path "./Fonts/Generated" \
--extension-name "UIFont" \
--extension-suffix "GenAllScript"
#echo "\n-------------------------\n"
#
## Images
#swift run -c release ResgenSwift images $FORCE_FLAG "./Images/sampleImages.txt" \
# --xcassets-path "./Images/imagium.xcassets" \
# --extension-output-path "./Images/Generated" \
# --extension-name "ImageYolo" \
# --extension-name-ui-kit "UIImageYolo" \
# --extension-suffix "GenAllScript"
echo "\n-------------------------\n"
# Color
swift run -c release ColorTool $FORCE_FLAG "./Colors/sampleColors1.txt" \
--style all \
--xcassets-path "./Colors/colors.xcassets" \
--extension-output-path "./Colors/Generated/" \
--extension-name "UIColor" \
--extension-suffix "GenAllScript"
echo "\n-------------------------\n"
# Twine
swift run -c release Strings twine $FORCE_FLAG "./Twine/sampleStrings.txt" \
--output-path "./Twine/Generated" \
--langs "fr en en-us" \
--default-lang "en" \
--extension-output-path "./Twine/Generated"
echo "\n-------------------------\n"
# Strings
swift run -c release Strings stringium $FORCE_FLAG "./Strings/sampleStrings.txt" \
--output-path "./Strings/Generated" \
--langs "fr en en-us" \
--default-lang "en" \
--extension-output-path "./Strings/Generated" \
--extension-name "String" \
--extension-suffix "GenAllScript"
echo "\n-------------------------\n"
# Tags
swift run -c release Strings tags $FORCE_FLAG "./Tags/sampleTags.txt" \
--lang "ium" \
--extension-output-path "./Tags/Generated" \
--extension-name "Tags" \
--extension-suffix "GenAllScript"
echo "\n-------------------------\n"
# Images
swift run -c release Imagium $FORCE_FLAG "./Images/sampleImages.txt" \
--xcassets-path "./Images/imagium.xcassets" \
--extension-output-path "./Images/Generated" \
--extension-name "UIImage" \
--extension-suffix "GenAllScript"

View File

@ -1,103 +0,0 @@
---
#
# Class architecture
#
architecture:
property: R
classname: R
path: ./Tags
children:
- property: images
classname: R2Image
- property: strings
classname: R2String
- property: fonts
classname: R2Font
- property: images
classname: R2Image
- property: ui
classname: R2UI
children:
- property: images
classname: R2UIImage
- property: fonts
classname: R2UIFont
- property: images
classname: R2UIImage
#
# Strings
#
strings:
-
inputFile: ./Strings/sampleStrings.txt
outputPath: ./Strings/Generated
langs: "fr en en-us"
defaultLang: en
extensionOutputPath: ./Strings/Generated
extensionName: String
extensionSuffix: GenAllScript
#
# Images
#
images:
-
inputFile: ./Images/sampleImages.txt
xcassetsPath: ./Images/imagium.xcassets
extensionOutputPath: ./Images/Generated
extensionName: ImageYolo
extensionNameUIKit: UIImageYolo
extensionSuffix: GenAllScript
#
# Colors
#
colors:
-
inputFile: ./Colors/sampleColors1.txt
style: all
xcassetsPath: ./Colors/colors.xcassets
extensionOutputPath: ./Colors/Generated/
extensionName: ColorYolo
extensionNameUIKit: UIColorYolo
extensionSuffix: GenAllScript
#
# Tags
#
tags:
-
inputFile: ./Tags/sampleTags.txt
lang: ium
extensionOutputPath: ./Tags/Generated
extensionName: Tags
extensionSuffix: GenAllScript
#
# Analytics
#
analytics:
-
inputFile: ./Tags/sampleTags.yml
target: "matomo firebase"
extensionOutputPath: ./Tags/Generated
extensionName: Analytics
extensionSuffix: GenAllScript
#
# Fonts
#
fonts:
-
inputFile: ./Fonts/sampleFontsAll.txt
extensionOutputPath: ./Fonts/Generated
extensionName: FontYolo
extensionNameUIKit: UIFontYolo
extensionSuffix: GenAllScript
infoPlistPaths: "./Fonts/Generated/test.plist ./Fonts/Generated/test2.plist"

View File

@ -0,0 +1,35 @@
//
// 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

@ -0,0 +1,38 @@
//
// ColorToolOptions.swift
//
//
// Created by Thibaut Schmitt on 17/01/2022.
//
import Foundation
import ArgumentParser
struct ColorToolOptions: ParsableArguments {
@Flag(name: [.customShort("f"), .customShort("F")], help: "Should force generation")
var forceGeneration = false
@Argument(help: "Input files where colors ared defined.", transform: { $0.replaceTiltWithHomeDirectoryPath() })
var inputFile: String
@Option(help: "Color style to generate: light for light colors only, or all for dark and light colors")
fileprivate var style: String
@Option(help: "Path of xcassets where to generate colors", transform: { $0.replaceTiltWithHomeDirectoryPath() })
var xcassetsPath: String
@Option(help: "Path where to generate the extension.", transform: { $0.replaceTiltWithHomeDirectoryPath() })
var extensionOutputPath: String
@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
@Option(help: "Extension suffix. Ex: MyApp, it will generate {extensionName}+ColorsMyApp.swift")
var extensionSuffix: String = ""
}
extension ColorToolOptions {
var colorStyle: ColorStyle {
ColorStyle(rawValue: style) ?? .all
}
}

View File

@ -0,0 +1,67 @@
//
// ColorExtensionGenerator.swift
//
//
// Created by Thibaut Schmitt on 20/12/2021.
//
import Foundation
import ToolCore
struct ColorExtensionGenerator {
let colors: [ParsedColor]
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 = [
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)
import UIKit
extension \(extensionClassname) {\n
"""
}
private static func getFooter() -> String {
"""
}
"""
}
private static func getProperties(for colors: [ParsedColor], withStaticVar staticVar: Bool) -> String {
colors.map {
if staticVar {
return $0.getColorStaticProperty()
}
return $0.getColorProperty()
}
.joined(separator: "\n\n")
}
}

View File

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

View File

@ -0,0 +1,13 @@
//
// File.swift
//
//
// Created by Thibaut Schmitt on 29/08/2022.
//
import Foundation
enum ColorStyle: String, Decodable {
case light
case all
}

View File

@ -25,9 +25,9 @@ struct ParsedColor {
}
guard allComponents.contains(true) == false else {
let error = ColorsToolError.badColorDefinition(light, dark)
print(error.description)
Colors.exit(withError: error)
let error = ColorToolError.badColorDefinition(light, dark)
print(error.localizedDescription)
ColorTool.exit(withError: error)
}
return """
@ -72,20 +72,19 @@ struct ParsedColor {
"""
}
// MARK: - UIKit
func getColorProperty(isStatic: Bool, isSwiftUI: Bool) -> String {
if isSwiftUI {
return """
/// Color \(name) is \(light) (light) or \(dark) (dark)"
\(isStatic ? "static " : "")var \(name): Color {
Color("\(name)")
}
"""
}
return """
func getColorProperty() -> String {
"""
/// Color \(name) is \(light) (light) or \(dark) (dark)"
\(isStatic ? "static " : "@objc ")var \(name): UIColor {
@objc var \(name): UIColor {
UIColor(named: "\(name)")!
}
"""
}
func getColorStaticProperty() -> String {
"""
/// Color \(name) is \(light) (light) or \(dark) (dark)"
static var \(name): UIColor {
UIColor(named: "\(name)")!
}
"""

View File

@ -0,0 +1,49 @@
//
// 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

@ -0,0 +1,123 @@
//
// main.swift
//
//
// Created by Thibaut Schmitt on 20/12/2021.
//
import ToolCore
import Foundation
import ArgumentParser
struct ColorTool: ParsableCommand {
// MARK: - CommandConfiguration
static var configuration = CommandConfiguration(
abstract: "A utility for generate colors assets and their getters.",
version: ResgenSwiftVersion
)
// MARK: - Static
static let toolName = "ColorTool"
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
// 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 }
print("[\(Self.toolName)] Will generate colors")
// Delete current colors
deleteCurrentColors()
// Get colors to generate
let parsedColors = ColorFileParser.parse(options.inputFile,
colorStyle: options.colorStyle)
// Generate all colors in xcassets
ColorXcassetHelper.generateXcassetColors(colors: parsedColors,
to: options.xcassetsPath)
// Generate extension
ColorExtensionGenerator.writeExtensionFile(colors: parsedColors,
staticVar: generateStaticVariable,
extensionName: options.extensionName,
extensionFilePath: extensionFilePath)
print("[\(Self.toolName)] Colors generated")
}
// MARK: - Requirements
private func checkRequirements() -> Bool {
let fileManager = FileManager()
// Check if input file exists
guard fileManager.fileExists(atPath: options.inputFile) else {
let error = ColorToolError.fileNotExists(options.inputFile)
print(error.localizedDescription)
ColorTool.exit(withError: error)
}
// Check if xcassets file exists
guard fileManager.fileExists(atPath: options.xcassetsPath) else {
let error = ColorToolError.fileNotExists(options.xcassetsPath)
print(error.localizedDescription)
ColorTool.exit(withError: error)
}
// Check if needed to regenerate
guard GeneratorChecker.shouldGenerate(force: options.forceGeneration, inputFilePath: options.inputFile, extensionFilePath: extensionFilePath) else {
print("[\(Self.toolName)] Colors are already up to date :) ")
return false
}
return true
}
// MARK: - Helpers
private func deleteCurrentColors() {
Shell.shell("rm", "-rf", "\(options.xcassetsPath)/Colors/*")
}
}
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,26 @@
//
// FontOptions.swift
//
//
// Created by Thibaut Schmitt on 17/01/2022.
//
import Foundation
import ArgumentParser
struct FontOptions: ParsableArguments {
@Flag(name: [.customShort("f"), .customShort("F")], help: "Should force generation")
var forceGeneration = false
@Argument(help: "Input files where fonts ared defined.", transform: { $0.replaceTiltWithHomeDirectoryPath() })
var inputFile: String
@Option(help: "Path where to generate the extension.", transform: { $0.replaceTiltWithHomeDirectoryPath() })
var extensionOutputPath: String
@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
@Option(help: "Extension suffix. Ex: MyApp, it will generate {extensionName}+FontsMyApp.swift")
var extensionSuffix: String = ""
}

View File

@ -0,0 +1,31 @@
//
// FontToolError.swift
//
//
// Created by Thibaut Schmitt on 13/12/2021.
//
import Foundation
enum FontToolError: Error {
case fcScan(String, Int32, String?)
case inputFolderNotFound(String)
case fileNotExists(String)
case writeExtension(String, String)
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")"
case .inputFolderNotFound(let inputFolder):
return " error:[\(FontTool.toolName)] Input folder not found: \(inputFolder)"
case .fileNotExists(let filename):
return " error:[\(FontTool.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)"
}
}
}

View File

@ -1,5 +1,5 @@
//
// FontsToolHelper.swift
// FontToolHelper.swift
//
//
// Created by Thibaut Schmitt on 13/12/2021.
@ -8,7 +8,7 @@
import Foundation
import ToolCore
class FontsToolHelper {
class FontToolHelper {
static func getFontPostScriptName(for fonts: [String], inputFolder: String) -> [FontName] {
let fontsFilenames = Self.getFontsFilenames(fromInputFolder: inputFolder)
@ -38,9 +38,9 @@ class FontsToolHelper {
// Get a enumerator for all files
let fileManager = FileManager()
guard fileManager.fileExists(atPath: inputFolder) else {
let error = FontsToolError.inputFolderNotFound(inputFolder)
print(error.description)
Fonts.exit(withError: error)
let error = FontToolError.inputFolderNotFound(inputFolder)
print(error.localizedDescription)
FontTool.exit(withError: error)
}
let enumerator: FileManager.DirectoryEnumerator = fileManager.enumerator(atPath: inputFolder)!
@ -60,12 +60,12 @@ class FontsToolHelper {
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 = FontsToolError.fcScan(path, task.terminationStatus, task.output)
print(error.description)
Fonts.exit(withError: error)
let error = FontToolError.fcScan(path, task.terminationStatus, task.output)
print(error.localizedDescription)
FontTool.exit(withError: error)
}
return fontName

View File

@ -0,0 +1,22 @@
//
// File.swift
//
//
// Created by Thibaut Schmitt on 29/08/2022.
//
import Foundation
class FontPlistGenerator {
static func generatePlistUIAppsFontContent(for fonts: [FontName]) -> String {
var plistData = "<key>UIAppFonts</key>\n\t<array>\n"
fonts
.compactMap { $0 }
.forEach {
plistData += "\t\t<string>\($0)</string>\n"
}
plistData += "\t</array>\n*/"
return plistData
}
}

View File

@ -0,0 +1,83 @@
//
// FontToolContentGenerator.swift
//
//
// Created by Thibaut Schmitt on 13/12/2021.
//
import Foundation
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 = [
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)
import UIKit
extension \(extensionClassname) {\n
"""
}
private static func getFontNameEnum(fontsNames: [String]) -> String {
var enumDefinition = "\tenum FontName: String {\n"
fontsNames.forEach {
enumDefinition += "\t\tcase \($0.removeCharacters(from: "[]+-_")) = \"\($0)\"\n"
}
enumDefinition += "\t}\n"
return enumDefinition
}
private static func getFontMethods(fontsNames: [FontName], staticVar: Bool) -> String {
let pragma = "\t// MARK: - Getter"
var propertiesOrMethods: [String] = fontsNames
.unique()
.map {
if staticVar {
return $0.staticProperty
} else {
return $0.method
}
}
propertiesOrMethods.insert(pragma, at: 0)
return propertiesOrMethods.joined(separator: "\n\n")
}
private static func getFooter() -> String {
"""
}
"""
}
}

View File

@ -0,0 +1,32 @@
//
// File.swift
//
//
// Created by Thibaut Schmitt on 29/08/2022.
//
import Foundation
typealias FontName = String
extension FontName {
var fontNameSanitize: String {
self.removeCharacters(from: "[]+-_")
}
var method: String {
"""
func \(fontNameSanitize)(withSize size: CGFloat) -> UIFont {
UIFont(name: FontName.\(fontNameSanitize).rawValue, size: size)!
}
"""
}
var staticProperty: String {
"""
static let \(fontNameSanitize): ((_ size: CGFloat) -> UIFont) = { size in
UIFont(name: FontName.\(fontNameSanitize).rawValue, size: size)!
}
"""
}
}

View File

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

View File

@ -1,5 +1,5 @@
//
// Fonts.swift
// FontTool.swift
//
//
// Created by Thibaut Schmitt on 13/12/2021.
@ -9,7 +9,7 @@ import ToolCore
import Foundation
import ArgumentParser
struct Fonts: ParsableCommand {
struct FontTool: ParsableCommand {
// MARK: - CommandConfiguration
@ -20,19 +20,30 @@ struct Fonts: ParsableCommand {
// MARK: - Static
static let toolName = "Fonts"
static let defaultExtensionName = "Font"
static let defaultExtensionNameUIKit = "UIFont"
static let toolName = "FontTool"
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: FontsOptions
@OptionGroup var options: FontOptions
// 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 }
@ -44,24 +55,17 @@ struct Fonts: ParsableCommand {
// Get real font names
let inputFolder = URL(fileURLWithPath: options.inputFile).deletingLastPathComponent().relativePath
let fontsNames = FontsToolHelper.getFontPostScriptName(for: fontsToGenerate,
let fontsNames = FontToolHelper.getFontPostScriptName(for: fontsToGenerate,
inputFolder: inputFolder)
// Generate extension
FontExtensionGenerator.writeExtensionFile(fontsNames: fontsNames,
staticVar: options.staticMembers,
staticVar: generateStaticVariable,
extensionName: options.extensionName,
extensionFilePath: options.extensionFilePath,
isSwiftUI: true)
extensionFilePath: extensionFilePath)
FontExtensionGenerator.writeExtensionFile(fontsNames: fontsNames,
staticVar: options.staticMembers,
extensionName: options.extensionNameUIKit,
extensionFilePath: options.extensionFilePathUIKit,
isSwiftUI: false)
print("Info.plist has been updated with:")
print("\(FontPlistGenerator.generatePlistUIAppsFontContent(for: fontsNames, infoPlistPaths: options.infoPlistPaths))")
print("Info.plist information:")
print("\(FontPlistGenerator.generatePlistUIAppsFontContent(for: fontsNames))")
print("[\(Self.toolName)] Fonts generated")
}
@ -73,22 +77,13 @@ struct Fonts: ParsableCommand {
// Check input file exists
guard fileManager.fileExists(atPath: options.inputFile) else {
let error = FontsToolError.fileNotExists(options.inputFile)
print(error.description)
Fonts.exit(withError: error)
}
// Extension for UIKit and SwiftUI should have different name
guard options.extensionName != options.extensionNameUIKit else {
let error = FontsToolError.extensionNamesCollision(options.extensionName)
print(error.description)
Fonts.exit(withError: error)
let error = FontToolError.fileNotExists(options.inputFile)
print(error.localizedDescription)
FontTool.exit(withError: error)
}
// Check if needed to regenerate
guard GeneratorChecker.shouldGenerate(force: options.forceGeneration,
inputFilePath: options.inputFile,
extensionFilePath: options.extensionFilePath) else {
guard GeneratorChecker.shouldGenerate(force: options.forceGeneration, inputFilePath: options.inputFile, extensionFilePath: extensionFilePath) else {
print("[\(Self.toolName)] Fonts are already up to date :) ")
return false
}
@ -96,3 +91,5 @@ struct Fonts: ParsableCommand {
return true
}
}
FontTool.main()

View File

@ -11,21 +11,21 @@ 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 = ImagesError.unknown("Cannot enumerate file in \(directory)")
print(error.description)
Images.exit(withError: error)
let error = ImagiumError.unknown("Cannot enumerate file in \(directory)")
print(error.localizedDescription)
Imagium.exit(withError: error)
}
for case let fileURL as URL in enumerator {
do {
let fileAttributes = try fileURL.resourceValues(forKeys: [.isRegularFileKey])
let fileAttributes = try fileURL.resourceValues(forKeys:[.isRegularFileKey])
if fileAttributes.isRegularFile! {
files.append(fileURL.relativePath)
}
} catch {
let error = ImagesError.getFileAttributed(fileURL.relativePath, error.localizedDescription)
print(error.description)
Images.exit(withError: error)
let error = ImagiumError.getFileAttributed(fileURL.relativePath, error.localizedDescription)
print(error.localizedDescription)
Imagium.exit(withError: error)
}
}
return files
@ -34,21 +34,21 @@ 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 = ImagesError.unknown("Cannot enumerate imageset directory in \(directory)")
print(error.description)
Images.exit(withError: error)
let error = ImagiumError.unknown("Cannot enumerate imageset directory in \(directory)")
print(error.localizedDescription)
Imagium.exit(withError: error)
}
for case let fileURL as URL in enumerator {
do {
let fileAttributes = try fileURL.resourceValues(forKeys: [.isDirectoryKey])
let fileAttributes = try fileURL.resourceValues(forKeys:[.isDirectoryKey])
if fileAttributes.isDirectory! && fileURL.lastPathComponent.hasSuffix(".imageset") {
files.append(fileURL.lastPathComponent)
}
} catch {
let error = ImagesError.getFileAttributed(fileURL.relativePath, error.localizedDescription)
print(error.description)
Images.exit(withError: error)
let error = ImagiumError.getFileAttributed(fileURL.relativePath, error.localizedDescription)
print(error.localizedDescription)
Imagium.exit(withError: error)
}
}
return files

View File

@ -0,0 +1,87 @@
//
// 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

@ -0,0 +1,189 @@
//
// File.swift
//
//
// Created by Thibaut Schmitt on 24/01/2022.
//
import Foundation
import ToolCore
class XcassetsGenerator {
static let outputImageExtension = "png"
let forceGeneration: Bool
// MARK: - Init
init(forceGeneration: Bool) {
self.forceGeneration = forceGeneration
}
// MARK: - Assets generation
func generateXcassets(inputPath: String, imagesToGenerate: [ParsedImage], xcassetsPath: String) {
let fileManager = FileManager()
let svgConverter = Imagium.getSvgConverterPath()
let allSubFiles = fileManager.getAllRegularFileIn(directory: inputPath)
var generatedAssetsPaths = [String]()
// Generate new assets
imagesToGenerate.forEach { parsedImage in
// Get image path
let imageData: (path: String, ext: String) = {
for subfile in allSubFiles {
if subfile.hasSuffix("/" + parsedImage.name + ".svg") {
return (subfile, "svg")
}
if subfile.hasSuffix("/" + parsedImage.name + ".png") {
return (subfile, "png")
}
if subfile.hasSuffix("/" + parsedImage.name + ".jpg") {
return (subfile, "jpg")
}
if subfile.hasSuffix("/" + parsedImage.name + ".jepg") {
return (subfile, "jepg")
}
}
let error = ImagiumError.unknownImageExtension(parsedImage.name)
print(error.localizedDescription)
Imagium.exit(withError: error)
}()
// Create imageset folder
let imagesetName = "\(parsedImage.name).imageset"
let imagesetPath = "\(xcassetsPath)/\(imagesetName)"
Shell.shell("mkdir", "-p", imagesetPath)
// Store managed images path
generatedAssetsPaths.append(imagesetName)
// Generate output images path
let output1x = "\(imagesetPath)/\(parsedImage.name).\(XcassetsGenerator.outputImageExtension)"
let output2x = "\(imagesetPath)/\(parsedImage.name)@2x.\(XcassetsGenerator.outputImageExtension)"
let output3x = "\(imagesetPath)/\(parsedImage.name)@3x.\(XcassetsGenerator.outputImageExtension)"
// Check if we need to convert image
if self.shouldBypassGeneration(for: parsedImage, xcassetImagePath: output1x) {
print("\(parsedImage.name) -> Not regenerating")
return
}
// Convert image
let convertArguments = parsedImage.convertArguments
if imageData.ext == "svg" {
// /usr/local/bin/rsvg-convert path/to/image.png -w 200 -h 300 -o path/to/output.png
// /usr/local/bin/rsvg-convert path/to/image.png -w 200 -o path/to/output.png
// /usr/local/bin/rsvg-convert path/to/image.png -h 300 -o path/to/output.png
var command1x = ["\(svgConverter)", "\(imageData.path)"]
var command2x = ["\(svgConverter)", "\(imageData.path)"]
var command3x = ["\(svgConverter)", "\(imageData.path)"]
self.addConvertArgument(command: &command1x, convertArgument: convertArguments.x1)
self.addConvertArgument(command: &command2x, convertArgument: convertArguments.x2)
self.addConvertArgument(command: &command3x, convertArgument: convertArguments.x3)
command1x.append(contentsOf: ["-o", output1x])
command2x.append(contentsOf: ["-o", output2x])
command3x.append(contentsOf: ["-o", output3x])
Shell.shell(command1x)
Shell.shell(command2x)
Shell.shell(command3x)
} else {
// 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)
}
// 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)
print("\(parsedImage.name) -> Generated")
}
// Success info
let generatedAssetsCount = generatedAssetsPaths.count
print("Images generated: \(generatedAssetsCount)")
// Delete old assets
let allImagesetName = Set(fileManager.getAllImageSetFolderIn(directory: xcassetsPath))
let imagesetToRemove = allImagesetName.subtracting(Set(generatedAssetsPaths))
imagesetToRemove.forEach {
print("Will remove: \($0)")
}
imagesetToRemove.forEach { itemToRemove in
try! fileManager.removeItem(atPath: "\(xcassetsPath)/\(itemToRemove)")
}
print("Removed \(imagesetToRemove.count) images")
}
// MARK: - Helpers: SVG command
private func addConvertArgument(command: inout [String], convertArgument: ConvertArgument) {
if let width = convertArgument.width, width.isEmpty == false {
command.append("-w")
command.append("\(width)")
}
if let height = convertArgument.height, height.isEmpty == false {
command.append("-h")
command.append("\(height)")
}
}
// 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 {
return true
}
return false
}
}

View File

@ -0,0 +1,43 @@
//
// 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

@ -0,0 +1,41 @@
//
// ImagiumOptions.swift
//
//
// Created by Thibaut Schmitt on 24/01/2022.
//
import Foundation
import ArgumentParser
struct ImagiumOptions: ParsableArguments {
@Flag(name: .customShort("f"), help: "Should force script execution")
var forceExecution = false
@Flag(name: .customShort("F"), help: "Regenerate all images")
var forceExecutionAndGeneration = false
@Argument(help: "Input files where strings ared defined.", transform: { $0.replaceTiltWithHomeDirectoryPath() })
var inputFile: String
@Option(help: "Xcassets path where to generate images.", transform: { $0.replaceTiltWithHomeDirectoryPath() })
var xcassetsPath: String
@Option(help: "Path where to generate the extension.", transform: { $0.replaceTiltWithHomeDirectoryPath() })
var extensionOutputPath: String
@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
@Option(help: "Extension suffix. Ex: MyApp, it will generate {extensionName}+Image{extensionSuffix}.swift")
var extensionSuffix: String = ""
}
/*
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"
*/

View File

@ -0,0 +1,90 @@
//
// ParsedImage.swift
//
//
// Created by Thibaut Schmitt on 24/01/2022.
//
import Foundation
struct ParsedImage {
let name: String
let tags: String
let width: Int
let height: Int
// MARK: - Convert
var convertArguments: (x1: ConvertArgument, x2: ConvertArgument, x3: ConvertArgument) {
var width1x = ""
var height1x = ""
var width2x = ""
var height2x = ""
var width3x = ""
var height3x = ""
if width != -1 {
width1x = "\(width)"
width2x = "\(width * 2)"
width3x = "\(width * 3)"
}
if height != -1 {
height1x = "\(height)"
height2x = "\(height * 2)"
height3x = "\(height * 3)"
}
return (x1: ConvertArgument(width: width1x, height: height1x),
x2: ConvertArgument(width: width2x, height: height2x),
x3: ConvertArgument(width: width3x, height: height3x))
}
// MARK: - Assets
var contentJson: String {
"""
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x",
"filename" : "\(name).\(XcassetsGenerator.outputImageExtension)"
},
{
"idiom" : "universal",
"scale" : "2x",
"filename" : "\(name)@2x.\(XcassetsGenerator.outputImageExtension)"
},
{
"idiom" : "universal",
"scale" : "3x",
"filename" : "\(name)@3x.\(XcassetsGenerator.outputImageExtension)"
}
],
"info" : {
"version" : 1,
"author" : "ResgenSwift-Imagium"
}
}
"""
}
// MARK: - Extension property
func getImageProperty() -> String {
"""
var \(name): UIImage {
UIImage(named: "\(name)")!
}
"""
}
func getStaticImageProperty() -> String {
"""
static var \(name): UIImage {
UIImage(named: "\(name)")!
}
"""
}
}

View File

@ -13,13 +13,10 @@ 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]()
lines.forEach {
// Parse file
stringsByLines.forEach {
guard $0.removeLeadingTrailingWhitespace().isEmpty == false, $0.first != "#" else {
return
}
@ -38,21 +35,11 @@ class ImageFileParser {
}
return Int(splittedLine[3])!
}()
var imageExtensions: [ImageExtension] = []
splittedLine.forEach { stringExtension in
if let imageExtension = ImageExtension(rawValue: String(stringExtension)) {
imageExtensions.append(imageExtension)
}
}
let image = ParsedImage(name: String(splittedLine[1]), tags: String(splittedLine[0]), width: width, height: height, imageExtensions: imageExtensions)
let image = ParsedImage(name: String(splittedLine[1]), tags: String(splittedLine[0]), width: width, height: height)
imagesToGenerate.append(image)
}
print(imagesToGenerate)
return imagesToGenerate.filter {
$0.tags.contains(platform.rawValue)
}

View File

@ -1,5 +1,5 @@
//
// Images.swift
// Imagium.swift
//
//
// Created by Thibaut Schmitt on 24/01/2022.
@ -9,7 +9,7 @@ import ToolCore
import Foundation
import ArgumentParser
struct Images: ParsableCommand {
struct Imagium: ParsableCommand {
// MARK: - CommandConfiguration
@ -20,19 +20,27 @@ struct Images: ParsableCommand {
// MARK: - Static
static let toolName = "Images"
static let defaultExtensionName = "Image"
static let defaultExtensionNameUIKit = "UIImage"
static let toolName = "Imagium"
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: ImagesOptions
@OptionGroup var options: ImagiumOptions
// 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 }
@ -46,26 +54,18 @@ struct Images: 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.generateExtensionFile(images: imagesToGenerate,
staticVar: options.staticMembers,
inputFilename: options.inputFilenameWithoutExt,
extensionName: options.extensionName,
extensionFilePath: options.extensionFilePath,
isSwiftUI: true)
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.extensionNameUIKit,
extensionFilePath: options.extensionFilePathUIKit,
isSwiftUI: false)
print("[\(Self.toolName)] Images generated")
}
@ -81,25 +81,16 @@ struct Images: ParsableCommand {
// Input file
guard fileManager.fileExists(atPath: options.inputFile) else {
let error = ImagesError.fileNotExists(options.inputFile)
print(error.description)
Images.exit(withError: error)
let error = ImagiumError.fileNotExists(options.inputFile)
print(error.localizedDescription)
Imagium.exit(withError: error)
}
// RSVG-Converter
_ = Images.getSvgConverterPath()
_ = Imagium.getSvgConverterPath()
// Extension for UIKit and SwiftUI should have different name
guard options.extensionName != options.extensionNameUIKit else {
let error = ImagesError.extensionNamesCollision(options.extensionName)
print(error.description)
Images.exit(withError: error)
}
// Check if needed to regenerate
guard GeneratorChecker.shouldGenerate(force: options.forceExecution,
inputFilePath: options.inputFile,
extensionFilePath: options.extensionFilePath) else {
guard GeneratorChecker.shouldGenerate(force: options.forceExecution, inputFilePath: options.inputFile, extensionFilePath: extensionFilePath) else {
print("[\(Self.toolName)] Images are already up to date :) ")
return false
}
@ -111,13 +102,15 @@ struct Images: 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 = ImagesError.rsvgConvertNotFound
print(error.description)
Images.exit(withError: error)
let error = ImagiumError.rsvgConvertNotFound
print(error.localizedDescription)
Imagium.exit(withError: error)
}
}
Imagium.main()

View File

@ -1,86 +0,0 @@
//
// Analytics.swift
//
//
// Created by Loris Perret on 08/12/2023.
//
import ToolCore
import Foundation
import ArgumentParser
struct Analytics: ParsableCommand {
// MARK: - Command Configuration
static var configuration = CommandConfiguration(
abstract: "Generate analytics extension file.",
version: ResgenSwiftVersion
)
// MARK: - Static
static let toolName = "Analytics"
static let defaultExtensionName = "Analytics"
// MARK: - Command Options
@OptionGroup var options: AnalyticsOptions
// MARK: - Run
mutating func run() {
print("[\(Self.toolName)] Starting analytics generation")
print("[\(Self.toolName)] Will use inputFile \(options.inputFile) to generate analytics for target: \(options.target)")
// Check requirements
guard checkRequirements() else { return }
print("[\(Self.toolName)] Will generate analytics")
// Check requirements
guard checkRequirements() else { return }
// Parse input file
let sections = AnalyticsFileParser.parse(options.inputFile, target: options.target)
// Generate extension
AnalyticsGenerator.writeExtensionFiles(sections: sections,
target: options.target,
tags: ["ios", "iosonly"],
staticVar: options.staticMembers,
extensionName: options.extensionName,
extensionFilePath: options.extensionFilePath)
print("[\(Self.toolName)] Analytics generated")
}
// MARK: - Requirements
private func checkRequirements() -> Bool {
let fileManager = FileManager()
// Input file
guard fileManager.fileExists(atPath: options.inputFile) else {
let error = AnalyticsError.fileNotExists(options.inputFile)
print(error.description)
Analytics.exit(withError: error)
}
guard TrackerType.hasValidTarget(in: options.target) else {
let error = AnalyticsError.noValidTracker(options.target)
print(error.description)
Analytics.exit(withError: error)
}
// Check if needed to regenerate
guard GeneratorChecker.shouldGenerate(force: options.forceGeneration,
inputFilePath: options.inputFile,
extensionFilePath: options.extensionFilePath) else {
print("[\(Self.toolName)] Analytics are already up to date :) ")
return false
}
return true
}
}

View File

@ -1,39 +0,0 @@
//
// AnalyticsError.swift
//
//
// Created by Loris Perret on 11/12/2023.
//
import Foundation
enum AnalyticsError: Error {
case noValidTracker(String)
case fileNotExists(String)
case missingElement(String)
case invalidParameter(String)
case parseFailed(String)
case writeFile(String, String)
var description: String {
switch self {
case .noValidTracker(let inputTargets):
return "error: [\(Analytics.toolName)] '\(inputTargets)' ne contient aucun tracker valid"
case .fileNotExists(let filename):
return "error: [\(Analytics.toolName)] File \(filename) does not exists"
case .missingElement(let element):
return "error: [\(Analytics.toolName)] Missing \(element) for Matomo"
case .invalidParameter(let reason):
return "error: [\(Analytics.toolName)] Invalid parameter \(reason)"
case .parseFailed(let baseError):
return "error: [\(Analytics.toolName)] Parse input file failed: \(baseError)"
case .writeFile(let subErrorDescription, let filename):
return "error: [\(Analytics.toolName)] An error occured while writing content to \(filename): \(subErrorDescription)"
}
}
}

View File

@ -1,47 +0,0 @@
//
// AnalyticsOptions.swift
//
//
// Created by Loris Perret on 08/12/2023.
//
import Foundation
import ArgumentParser
struct AnalyticsOptions: ParsableArguments {
@Flag(name: [.customShort("f"), .customShort("F")], help: "Should force generation")
var forceGeneration = false
@Argument(help: "Input files where tags ared defined.", transform: { $0.replaceTiltWithHomeDirectoryPath() })
var inputFile: String
@Option(help: "Target(s) analytics to generate. (\"matomo\" | \"firebase\")")
var target: String
@Option(help: "Path where to generate the extension.", transform: { $0.replaceTiltWithHomeDirectoryPath() })
var extensionOutputPath: String
@Option(help: "Tell if it will generate static properties or not")
var staticMembers: Bool = false
@Option(help: "Extension name. If not specified, it will generate a Analytics extension.")
var extensionName: String = Analytics.defaultExtensionName
@Option(help: "Extension suffix. Ex: MyApp, it will generate {extensionName}+Analytics{extensionSuffix}.swift")
var extensionSuffix: String?
}
// MARK: - Computed var
extension AnalyticsOptions {
var extensionFileName: String {
if let extensionSuffix = extensionSuffix {
return "\(extensionName)+\(extensionSuffix).swift"
}
return "\(extensionName).swift"
}
var extensionFilePath: String {
"\(extensionOutputPath)/\(extensionFileName)"
}
}

View File

@ -1,227 +0,0 @@
//
// AnalyticsGenerator.swift
//
//
// Created by Loris Perret on 08/12/2023.
//
import Foundation
import ToolCore
import CoreVideo
class AnalyticsGenerator {
static var targets: [TrackerType] = []
static func writeExtensionFiles(sections: [AnalyticsCategory], target: String, tags: [String], staticVar: Bool, extensionName: String, extensionFilePath: String) {
// Get target type from enum
let targetsString: [String] = target.components(separatedBy: " ")
TrackerType.allCases.forEach { enumTarget in
if targetsString.contains(enumTarget.value) {
targets.append(enumTarget)
}
}
// Get extension content
let extensionFileContent = Self.getExtensionContent(sections: sections,
tags: tags,
staticVar: staticVar,
extensionName: extensionName)
// Write content
let extensionFilePathURL = URL(fileURLWithPath: extensionFilePath)
do {
try extensionFileContent.write(to: extensionFilePathURL, atomically: false, encoding: .utf8)
} catch let error {
let error = AnalyticsError.writeFile(extensionFilePath, error.localizedDescription)
print(error.description)
Analytics.exit(withError: error)
}
}
// MARK: - Extension content
static func getExtensionContent(sections: [AnalyticsCategory], tags: [String], staticVar: Bool, extensionName: String) -> String {
[
Self.getHeader(extensionClassname: extensionName, staticVar: staticVar),
Self.getProperties(sections: sections, tags: tags, staticVar: staticVar),
Self.getFooter()
]
.joined(separator: "\n")
}
// MARK: - Extension part
private static func getHeader(extensionClassname: String, staticVar: Bool) -> String {
"""
// Generated by ResgenSwift.\(Analytics.toolName) \(ResgenSwiftVersion)
\(Self.getImport())
\(Self.getAnalyticsProtocol())
// MARK: - Manager
class AnalyticsManager {
static var shared = AnalyticsManager()
// MARK: - Properties
var managers: [AnalyticsManagerProtocol] = []
\(Self.getEnabledContent())
\(Self.getAnalyticsProperties())
\(Self.getPrivateLogFunction())
"""
}
private static func getEnabledContent() -> String {
"""
private var isEnabled: Bool = true
// MARK: - Methods
func setAnalyticsEnabled(_ enable: Bool) {
isEnabled = enable
}
"""
}
private static func getImport() -> String {
var result: [String] = []
if targets.contains(TrackerType.matomo) {
result.append("import MatomoTracker")
}
if targets.contains(TrackerType.firebase) {
result.append("import FirebaseAnalytics")
}
return result.joined(separator: "\n")
}
private static func getPrivateLogFunction() -> String {
"""
private func logScreen(name: String, path: String) {
guard isEnabled else { return }
managers.forEach { manager in
manager.logScreen(name: name, path: path)
}
}
private func logEvent(
name: String,
action: String,
category: String,
params: [String: Any]?
) {
guard isEnabled else { return }
managers.forEach { manager in
manager.logEvent(
name: name,
action: action,
category: category,
params: params
)
}
}
"""
}
private static func getAnalyticsProperties() -> String {
var header = ""
var content: [String] = []
let footer = " }"
if targets.contains(TrackerType.matomo) {
header = "func configure(siteId: String, url: String) {"
} else if targets.contains(TrackerType.firebase) {
header = "func configure() {"
}
if targets.contains(TrackerType.matomo) {
content.append("""
managers.append(
MatomoAnalyticsManager(
siteId: siteId,
url: url
)
)
""")
}
if targets.contains(TrackerType.firebase) {
content.append(" managers.append(FirebaseAnalyticsManager())")
}
return [
header,
content.joined(separator: "\n"),
footer
]
.joined(separator: "\n")
}
private static func getAnalyticsProtocol() -> String {
let proto = """
// MARK: - Protocol
protocol AnalyticsManagerProtocol {
func logScreen(name: String, path: String)
func logEvent(
name: String,
action: String,
category: String,
params: [String: Any]?
)
}
"""
var result: [String] = [proto]
if targets.contains(TrackerType.matomo) {
result.append(MatomoGenerator.service)
}
if targets.contains(TrackerType.firebase) {
result.append(FirebaseGenerator.service)
}
return result.joined(separator: "\n")
}
private static func getProperties(sections: [AnalyticsCategory], tags: [String], staticVar: Bool) -> String {
sections
.compactMap { section in
// Check that at least one string will be generated
guard section.hasOneOrMoreMatchingTags(tags: tags) else {
return nil// Go to next section
}
var res = "\n // MARK: - \(section.id)"
section.definitions.forEach { definition in
guard definition.hasOneOrMoreMatchingTags(inputTags: tags) == true else {
return // Go to next definition
}
if staticVar {
res += "\n\n\(definition.getStaticProperty())"
} else {
res += "\n\n\(definition.getProperty())"
}
}
return res
}
.joined(separator: "\n")
}
private static func getFooter() -> String {
"""
}
"""
}
}

View File

@ -1,87 +0,0 @@
//
// FirebaseGenerator.swift
//
//
// Created by Loris Perret on 05/12/2023.
//
import Foundation
enum FirebaseGenerator {
static var service: String {
[
FirebaseGenerator.header,
FirebaseGenerator.logScreen,
FirebaseGenerator.logEvent,
FirebaseGenerator.footer
]
.joined(separator: "\n")
}
// MARK: - Private vars
private static var header: String {
"""
// MARK: - Firebase
class FirebaseAnalyticsManager: AnalyticsManagerProtocol {
"""
}
private static var logScreen: String {
"""
func logScreen(name: String, path: String) {
var parameters = [
AnalyticsParameterScreenName: name as NSObject
]
Analytics.logEvent(
AnalyticsEventScreenView,
parameters: parameters
)
}
"""
}
private static var logEvent: String {
"""
func logEvent(
name: String,
action: String,
category: String,
params: [String: Any]?
) {
var parameters: [String:NSObject] = [
"action": action as NSObject,
"category": category as NSObject,
]
if let supplementaryParameters = params {
for (newKey, newValue) in supplementaryParameters {
if parameters.contains(where: { (key: String, value: NSObject) in
key == newKey
}) {
continue
}
parameters[newKey] = newValue as? NSObject
}
}
Analytics.logEvent(
name.replacingOccurrences(of: [" "], with: "_"),
parameters: parameters
)
}
"""
}
private static var footer: String {
"""
}
"""
}
}

View File

@ -1,110 +0,0 @@
//
// MatomoGenerator.swift
//
//
// Created by Loris Perret on 05/12/2023.
//
import Foundation
enum MatomoGenerator {
static var service: String {
[
MatomoGenerator.header,
MatomoGenerator.setup,
MatomoGenerator.logScreen,
MatomoGenerator.logEvent,
MatomoGenerator.footer
]
.joined(separator: "\n")
}
// MARK: - Private vars
private static var header: String {
"""
// MARK: - Matomo
class MatomoAnalyticsManager: AnalyticsManagerProtocol {
// MARK: - Properties
private var tracker: MatomoTracker
"""
}
private static var setup: String {
"""
// MARK: - Init
init(siteId: String, url: String) {
debugPrint("[Matomo service] Server URL: \\(url)")
debugPrint("[Matomo service] Site ID: \\(siteId)")
tracker = MatomoTracker(
siteId: siteId,
baseURL: URL(string: url)!
)
#if DEBUG
tracker.dispatchInterval = 5
#endif
#if DEBUG
tracker.logger = DefaultLogger(minLevel: .verbose)
#endif
debugPrint("[Matomo service] Configured with content base: \\(tracker.contentBase?.absoluteString ?? "-")")
debugPrint("[Matomo service] Opt out: \\(tracker.isOptedOut)")
}
// MARK: - Methods
"""
}
private static var logScreen: String {
"""
func logScreen(name: String, path: String) {
guard !tracker.isOptedOut else { return }
guard let trackerUrl = tracker.contentBase?.absoluteString else { return }
let urlString = URL(string: "\\(trackerUrl)" + "/" + "\\(path)" + "iOS")
tracker.track(
view: [name],
url: urlString
)
}
"""
}
private static var logEvent: String {
"""
func logEvent(
name: String,
action: String,
category: String,
params: [String: Any]?
) {
guard !tracker.isOptedOut else { return }
tracker.track(
eventWithCategory: category,
action: action,
name: name,
number: nil,
url: nil
)
}
"""
}
private static var footer: String {
"""
}
"""
}
}

View File

@ -1,32 +0,0 @@
//
// AnalyticsCategory.swift
//
//
// Created by Loris Perret on 05/12/2023.
//
import Foundation
class AnalyticsCategory {
let id: String // OnBoarding
var definitions = [AnalyticsDefinition]()
// MARK: - Init
init(id: String) {
self.id = id
}
// MARK: - Methods
func hasOneOrMoreMatchingTags(tags: [String]) -> Bool {
let allTags = definitions.flatMap { $0.tags }
let allTagsSet = Set(allTags)
let intersection = Set(tags).intersection(allTagsSet)
if intersection.isEmpty {
return false
}
return true
}
}

View File

@ -1,157 +0,0 @@
//
// AnalyticsDefinition.swift
//
//
// Created by Loris Perret on 05/12/2023.
//
import Foundation
import ToolCore
class AnalyticsDefinition {
let id: String
var name: String
var path: String = ""
var category: String = ""
var action: String = ""
var comments: String = ""
var tags: [String] = []
var parameters: [AnalyticsParameter] = []
var type: TagType
// MARK: - Init
init(id: String, name: String, type: TagType) {
self.id = id
self.name = name
self.type = type
}
// MARK: - Methods
func hasOneOrMoreMatchingTags(inputTags: [String]) -> Bool {
if Set(inputTags).isDisjoint(with: tags) {
return false
}
return true
}
// MARK: - Private Methods
private func getFuncName() -> String {
var pascalCaseTitle: String = ""
id.components(separatedBy: "_").forEach { word in
pascalCaseTitle.append(contentsOf: word.uppercasedFirst())
}
return "log\(type == .screen ? "Screen" : "Event")\(pascalCaseTitle)"
}
private func getParameters() -> String {
var params = parameters
var result: String
if type == .screen {
params = params.filter { param in
!param.replaceIn.isEmpty
}
}
let paramsString = params.map { parameter in
"\(parameter.name): \(parameter.type)"
}
if paramsString.count > 2 {
result = """
(
\(paramsString.joined(separator: ",\n\t\t"))
)
"""
} else {
result = """
(\(paramsString.joined(separator: ", ")))
"""
}
return result
}
private func replaceIn() {
for parameter in parameters {
for rep in parameter.replaceIn {
switch rep {
case "name": name = name.replacingFirstOccurrence(of: "_\(parameter.name.uppercased())_", with: "\\(\(parameter.name))")
case "path": path = path.replacingFirstOccurrence(of: "_\(parameter.name.uppercased())_", with: "\\(\(parameter.name))")
case "category": category = category.replacingFirstOccurrence(of: "_\(parameter.name.uppercased())_", with: "\\(\(parameter.name))")
case "action": action = action.replacingFirstOccurrence(of: "_\(parameter.name.uppercased())_", with: "\\(\(parameter.name))")
default: break
}
}
}
}
private func getlogFunction() -> String {
var params: [String] = []
var result: String
let supplementaryParams = parameters.filter { param in
param.replaceIn.isEmpty
}
supplementaryParams.forEach { param in
params.append("\"\(param.name)\": \(param.name)")
}
if params.count > 1 {
result = """
[
\(params.joined(separator: ",\n\t\t\t\t"))
]
"""
} else if params.count == 1 {
result = """
[\(params.joined(separator: ", "))]
"""
} else {
result = "[:]"
}
if type == .screen {
return """
logScreen(
name: "\(name)",
path: "\(path)"
)
"""
} else {
return """
logEvent(
name: "\(name)",
action: "\(action)",
category: "\(category)",
params: \(result)
)
"""
}
}
// MARK: - Raw strings
func getProperty() -> String {
replaceIn()
return """
func \(getFuncName())\(getParameters()) {
\(getlogFunction())
}
"""
}
func getStaticProperty() -> String {
replaceIn()
return """
static func \(getFuncName())\(getParameters()) {
\(getlogFunction())
}
"""
}
}

View File

@ -1,45 +0,0 @@
//
// AnalyticsFile.swift
//
//
// Created by Loris Perret on 06/12/2023.
//
import Foundation
struct AnalyticsFile: Codable {
var categories: [AnalyticsCategoryDTO]
}
struct AnalyticsCategoryDTO: Codable {
var id: String
var screens: [AnalyticsDefinitionScreenDTO]?
var events: [AnalyticsDefinitionEventDTO]?
}
struct AnalyticsDefinitionScreenDTO: Codable {
var id: String
var name: String
var tags: String
var comments: String?
var parameters: [AnalyticsParameterDTO]?
var path: String?
}
struct AnalyticsDefinitionEventDTO: Codable {
var id: String
var name: String
var tags: String
var comments: String?
var parameters: [AnalyticsParameterDTO]?
var category: String?
var action: String?
}
struct AnalyticsParameterDTO: Codable {
var name: String
var type: String
var replaceIn: String?
}

View File

@ -1,21 +0,0 @@
//
// AnalyticsParameter.swift
//
//
// Created by Loris Perret on 06/12/2023.
//
import Foundation
class AnalyticsParameter {
var name: String
var type: String
var replaceIn: [String] = []
// MARK: - Init
init(name: String, type: String) {
self.name = name
self.type = type
}
}

View File

@ -1,16 +0,0 @@
//
// TagType.swift
//
//
// Created by Thibaut Schmitt on 08/12/2023.
//
import Foundation
extension AnalyticsDefinition {
enum TagType {
case screen
case event
}
}

View File

@ -1,29 +0,0 @@
//
// TargetType.swift
//
//
// Created by Thibaut Schmitt on 08/12/2023.
//
import Foundation
enum TrackerType: CaseIterable {
case matomo
case firebase
var value: String {
switch self {
case .matomo:
"matomo"
case .firebase:
"firebase"
}
}
static func hasValidTarget(in targets: String) -> Bool {
for tracker in Self.allCases where targets.contains(tracker.value) {
return true
}
return false
}
}

View File

@ -1,178 +0,0 @@
//
// AnalyticsFileParser.swift
//
//
// Created by Loris Perret on 06/12/2023.
//
import Foundation
import Yams
class AnalyticsFileParser {
private static var inputFile: String = ""
private static var target: String = ""
private static func parseYaml() -> AnalyticsFile {
guard let data = FileManager().contents(atPath: inputFile) else {
let error = AnalyticsError.fileNotExists(inputFile)
print(error.description)
Analytics.exit(withError: error)
}
do {
let tagFile = try YAMLDecoder().decode(AnalyticsFile.self, from: data)
return tagFile
} catch {
let error = AnalyticsError.parseFailed(error.localizedDescription)
print(error.description)
Analytics.exit(withError: error)
}
}
private static func getParameters(from parameters: [AnalyticsParameterDTO]) -> [AnalyticsParameter] {
parameters.map { dtoParameter in
// Type
let type = dtoParameter.type.uppercasedFirst()
guard
type == "String" ||
type == "Int" ||
type == "Double" ||
type == "Bool"
else {
let error = AnalyticsError.invalidParameter("type of \(dtoParameter.name)")
print(error.description)
Analytics.exit(withError: error)
}
let parameter = AnalyticsParameter(
name: dtoParameter.name,
type: type
)
if let replaceIn = dtoParameter.replaceIn {
parameter.replaceIn = replaceIn.components(separatedBy: ",")
}
return parameter
}
}
private static func getTagDefinition(
id: String,
name: String,
type: AnalyticsDefinition.TagType,
tags: String,
comments: String?,
parameters: [AnalyticsParameterDTO]?
) -> AnalyticsDefinition {
let definition = AnalyticsDefinition(id: id, name: name, type: type)
definition.tags = tags
.components(separatedBy: ",")
.map { $0.removeLeadingTrailingWhitespace() }
if let comments = comments {
definition.comments = comments
}
if let parameters = parameters {
definition.parameters = Self.getParameters(from: parameters)
}
return definition
}
private static func getTagDefinitionScreen(from screens: [AnalyticsDefinitionScreenDTO]) -> [AnalyticsDefinition] {
screens.map { screen in
let definition: AnalyticsDefinition = Self.getTagDefinition(
id: screen.id,
name: screen.name,
type: .screen,
tags: screen.tags,
comments: screen.comments,
parameters: screen.parameters
)
if target.contains(TrackerType.matomo.value) {
// Path
guard let path = screen.path else {
let error = AnalyticsError.missingElement("screen path")
print(error.description)
Analytics.exit(withError: error)
}
definition.path = path
}
return definition
}
}
private static func getTagDefinitionEvent(from events: [AnalyticsDefinitionEventDTO]) -> [AnalyticsDefinition] {
events.map { event in
let definition: AnalyticsDefinition = Self.getTagDefinition(
id: event.id,
name: event.name,
type: .event,
tags: event.tags,
comments: event.comments,
parameters: event.parameters
)
if target.contains(TrackerType.matomo.value) {
// Category
guard let category = event.category else {
let error = AnalyticsError.missingElement("event category")
print(error.description)
Analytics.exit(withError: error)
}
definition.category = category
// Action
guard let action = event.action else {
let error = AnalyticsError.missingElement("event action")
print(error.description)
Analytics.exit(withError: error)
}
definition.action = action
}
return definition
}
}
static func parse(_ inputFile: String, target: String) -> [AnalyticsCategory] {
self.inputFile = inputFile
self.target = target
let tagFile = Self.parseYaml()
return tagFile
.categories
.map { categorie in
let section: AnalyticsCategory = AnalyticsCategory(id: categorie.id)
if let screens = categorie.screens {
section
.definitions
.append(
contentsOf: Self.getTagDefinitionScreen(from: screens)
)
}
if let events = categorie.events {
section
.definitions
.append(
contentsOf: Self.getTagDefinitionEvent(from: events)
)
}
return section
}
}
}

View File

@ -1,125 +0,0 @@
//
// main.swift
//
//
// Created by Thibaut Schmitt on 20/12/2021.
//
import ToolCore
import Foundation
import ArgumentParser
struct Colors: ParsableCommand {
// MARK: - CommandConfiguration
static var configuration = CommandConfiguration(
abstract: "A utility for generate colors assets and their getters.",
version: ResgenSwiftVersion
)
// MARK: - Static
static let toolName = "Color"
static let defaultExtensionName = "Color"
static let defaultExtensionNameUIKit = "UIColor"
static let assetsColorsFolderName = "Colors"
// MARK: - Command options
@OptionGroup var options: ColorsToolOptions
// MARK: - Run
public func run() throws {
print("[\(Self.toolName)] Starting colors generation")
// Check requirements
guard checkRequirements() else { return }
print("[\(Self.toolName)] Will generate colors")
// Delete current colors
deleteCurrentColors()
// Get colors to generate
let parsedColors = ColorFileParser.parse(options.inputFile,
colorStyle: options.style)
// -> 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: options.staticMembers,
extensionName: options.extensionName,
extensionFilePath: options.extensionFilePath,
isSwiftUI: true)
// Generate extension
ColorExtensionGenerator.writeExtensionFile(colors: parsedColors,
staticVar: options.staticMembers,
extensionName: options.extensionNameUIKit,
extensionFilePath: options.extensionFilePathUIKit,
isSwiftUI: false)
print("[\(Self.toolName)] Colors generated")
}
// MARK: - Requirements
private func checkRequirements() -> Bool {
let fileManager = FileManager()
// Check if input file exists
guard fileManager.fileExists(atPath: options.inputFile) else {
let error = ColorsToolError.fileNotExists(options.inputFile)
print(error.description)
Colors.exit(withError: error)
}
// Check if xcassets file exists
guard fileManager.fileExists(atPath: options.xcassetsPath) else {
let error = ColorsToolError.fileNotExists(options.xcassetsPath)
print(error.description)
Colors.exit(withError: error)
}
// Extension for UIKit and SwiftUI should have different name
guard options.extensionName != options.extensionNameUIKit else {
let error = ColorsToolError.extensionNamesCollision(options.extensionName)
print(error.description)
Colors.exit(withError: error)
}
// Check if needed to regenerate
guard GeneratorChecker.shouldGenerate(force: options.forceGeneration,
inputFilePath: options.inputFile,
extensionFilePath: options.extensionFilePath) else {
print("[\(Self.toolName)] Colors are already up to date :) ")
return false
}
return true
}
// MARK: - Helpers
private func deleteCurrentColors() {
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.description)
Colors.exit(withError: error)
}
}
}
}

View File

@ -1,47 +0,0 @@
//
// ColorsToolError.swift
//
//
// Created by Thibaut Schmitt on 20/12/2021.
//
import Foundation
enum ColorsToolError: Error {
case extensionNamesCollision(String)
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 .extensionNamesCollision(let extensionName):
return "error: [\(Fonts.toolName)] Error on extension names, extension name and SwiftUI extension name should be different (\(extensionName) is used on both)"
case .badFormat(let info):
return "error: [\(Colors.toolName)] Bad line format: \(info). Accepted format are: colorName=\"#RGB/#ARGB\"; colorName \"#RGB/#ARGB\"; colorName \"#RGB/#ARGB\" \"#RGB/#ARGB\""
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,69 +0,0 @@
//
// ColorsToolOptions.swift
//
//
// Created by Thibaut Schmitt on 17/01/2022.
//
import Foundation
import ArgumentParser
struct ColorsToolOptions: ParsableArguments {
@Flag(name: [.customShort("f"), .customShort("F")], help: "Should force generation")
var forceGeneration = false
@Argument(help: "Input files where colors ared defined.", transform: { $0.replaceTiltWithHomeDirectoryPath() })
var inputFile: String
@Option(help: "Color style to generate: light for light colors only, or all for dark and light colors")
var style: ColorStyle
@Option(help: "Path of xcassets where to generate colors", transform: { $0.replaceTiltWithHomeDirectoryPath() })
var xcassetsPath: String
@Option(help: "Path where to generate the extension.", transform: { $0.replaceTiltWithHomeDirectoryPath() })
var extensionOutputPath: String
@Option(help: "Tell if it will generate static properties or not")
var staticMembers: Bool = false
@Option(help: "Extension name. If not specified, it will generate an Color extension.")
var extensionName: String = Colors.defaultExtensionName
@Option(help: "SwiftUI Extension name. If not specified, it will generate an UIColor extension.")
var extensionNameUIKit: String = Colors.defaultExtensionNameUIKit
@Option(help: "Extension suffix. Ex: MyApp, it will generate {extensionName}+ColorsMyApp.swift")
var extensionSuffix: String?
}
// MARK: - Computed var
extension ColorsToolOptions {
// MARK: - SwiftUI
var extensionFileName: String {
if let extensionSuffix = extensionSuffix {
return "\(extensionName)+\(extensionSuffix).swift"
}
return "\(extensionName).swift"
}
var extensionFilePath: String {
"\(extensionOutputPath)/\(extensionFileName)"
}
// MARK: - UIKit
var extensionFileNameUIKit: String {
if let extensionSuffix = extensionSuffix {
return "\(extensionNameUIKit)+\(extensionSuffix).swift"
}
return "\(extensionNameUIKit).swift"
}
var extensionFilePathUIKit: String {
"\(extensionOutputPath)/\(extensionFileNameUIKit)"
}
}

View File

@ -1,77 +0,0 @@
//
// ColorExtensionGenerator.swift
//
//
// Created by Thibaut Schmitt on 20/12/2021.
//
import Foundation
import ToolCore
struct ColorExtensionGenerator {
let colors: [ParsedColor]
let extensionClassname: String
// MARK: - UIKit
static func writeExtensionFile(colors: [ParsedColor],
staticVar: Bool,
extensionName: String,
extensionFilePath: String,
isSwiftUI: Bool) {
// Create extension content
let extensionContent = Self.getExtensionContent(colors: colors,
staticVar: staticVar,
extensionName: extensionName,
isSwiftUI: isSwiftUI)
// 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.description)
Colors.exit(withError: error)
}
}
static func getExtensionContent(colors: [ParsedColor],
staticVar: Bool,
extensionName: String,
isSwiftUI: Bool) -> String {
[
Self.getHeader(extensionClassname: extensionName, isSwiftUI: isSwiftUI),
Self.getProperties(for: colors, withStaticVar: staticVar, isSwiftUI: isSwiftUI),
Self.getFooter()
]
.joined(separator: "\n")
}
private static func getHeader(extensionClassname: String, isSwiftUI: Bool) -> String {
"""
// Generated by ResgenSwift.\(Colors.toolName) \(ResgenSwiftVersion)
import \(isSwiftUI ? "SwiftUI" : "UIKit")
extension \(extensionClassname) {\n
"""
}
private static func getFooter() -> String {
"""
}
"""
}
private static func getProperties(for colors: [ParsedColor],
withStaticVar staticVar: Bool,
isSwiftUI: Bool) -> String {
colors.map {
$0.getColorProperty(isStatic: staticVar, isSwiftUI: isSwiftUI)
}
.joined(separator: "\n\n")
}
}

View File

@ -1,21 +0,0 @@
//
// ColorStyle.swift
//
//
// Created by Thibaut Schmitt on 29/08/2022.
//
import Foundation
import ArgumentParser
enum ColorStyle: String, Decodable, ExpressibleByArgument {
case light
case all
static var allValueStrings: [String] {
[
Self.light.rawValue,
Self.all.rawValue
]
}
}

View File

@ -1,56 +0,0 @@
//
// 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.description)
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,74 +0,0 @@
//
// FontsOptions.swift
//
//
// Created by Thibaut Schmitt on 17/01/2022.
//
import Foundation
import ArgumentParser
struct FontsOptions: ParsableArguments {
@Flag(name: [.customShort("f"), .customShort("F")], help: "Should force generation")
var forceGeneration = false
@Argument(help: "Input files where fonts ared defined.", transform: { $0.replaceTiltWithHomeDirectoryPath() })
var inputFile: String
@Option(help: "Path where to generate the extension.", transform: { $0.replaceTiltWithHomeDirectoryPath() })
var extensionOutputPath: String
@Option(help: "Tell if it will generate static properties or methods")
var staticMembers: Bool = false
@Option(help: "Extension name. If not specified, it will generate an Font extension.")
var extensionName: String = Fonts.defaultExtensionName
@Option(help: "Extension name. If not specified, it will generate an UIFont extension.")
var extensionNameUIKit: String = Fonts.defaultExtensionNameUIKit
@Option(help: "Extension suffix. Ex: MyApp, it will generate {extensionName}+FontsMyApp.swift")
var extensionSuffix: String = ""
@Option(name: .customLong("info-plist-paths"), help: "Info.plist paths (array). Will be used to update UIAppFonts content")
fileprivate var infoPlistPathsRaw: String = ""
}
// MARK: - Computed var
extension FontsOptions {
// MARK: - SwiftUI
var extensionFileName: String {
if extensionSuffix.isEmpty == false {
return "\(extensionName)+\(extensionSuffix).swift"
}
return "\(extensionName).swift"
}
var extensionFilePath: String {
"\(extensionOutputPath)/\(extensionFileName)"
}
// MARK: - UIKit
var extensionFileNameUIKit: String {
if extensionSuffix.isEmpty == false {
return "\(extensionNameUIKit)+\(extensionSuffix).swift"
}
return "\(extensionNameUIKit).swift"
}
var extensionFilePathUIKit: String {
"\(extensionOutputPath)/\(extensionFileNameUIKit)"
}
// MARK: -
var infoPlistPaths: [String] {
infoPlistPathsRaw
.split(separator: " ")
.map { String($0) }
}
}

View File

@ -1,35 +0,0 @@
//
// FontsToolError.swift
//
//
// Created by Thibaut Schmitt on 13/12/2021.
//
import Foundation
enum FontsToolError: Error {
case extensionNamesCollision(String)
case fcScan(String, Int32, String?)
case inputFolderNotFound(String)
case fileNotExists(String)
case writeExtension(String, String)
var description: String {
switch self {
case .extensionNamesCollision(let extensionName):
return "error: [\(Fonts.toolName)] Error on extension names, extension name and SwiftUI extension name should be different (\(extensionName) is used on both)"
case .fcScan(let path, let code, let output):
return "error: [\(Fonts.toolName)] Error while getting fontName (fc-scan --format %{postscriptname} \(path). fc-scan exit with \(code) and output is: \(output ?? "no output")"
case .inputFolderNotFound(let inputFolder):
return "error: [\(Fonts.toolName)] Input folder not found: \(inputFolder)"
case .fileNotExists(let filename):
return "error: [\(Fonts.toolName)] File \(filename) does not exists"
case .writeExtension(let filename, let info):
return "error: [\(Fonts.toolName)] An error occured while writing extension in \(filename): \(info)"
}
}
}

View File

@ -1,44 +0,0 @@
//
// FontPlistGenerator.swift
//
//
// Created by Thibaut Schmitt on 29/08/2022.
//
import Foundation
import ToolCore
class FontPlistGenerator {
static func generatePlistUIAppsFontContent(for fonts: [FontName], infoPlistPaths: [String]) -> String {
let fontsToAddToPlist = fonts
.compactMap { $0 }
// Update each plist
infoPlistPaths.forEach { infoPlist in
// Remove UIAppFonts value
Shell.shell(launchPath: "/usr/libexec/PlistBuddy",
["-c", "delete :UIAppFonts", infoPlist])
// Add UIAppFonts empty array
debugPrint("Will PlistBuddy -c add :UIAppFonts array \(infoPlist)")
Shell.shell(launchPath: "/usr/libexec/PlistBuddy",
["-c", "add :UIAppFonts array", infoPlist])
// Fill array with fonts
fontsToAddToPlist
.forEach {
Shell.shell(launchPath: "/usr/libexec/PlistBuddy",
["-c", "add :UIAppFonts: string \($0)", infoPlist])
}
}
var plistData = "<key>UIAppFonts</key>\n\t<array>\n"
fontsToAddToPlist
.forEach {
plistData += "\t\t<string>\($0)</string>\n"
}
plistData += "\t</array>"
return plistData
}
}

View File

@ -1,88 +0,0 @@
//
// FontToolContentGenerator.swift
//
//
// Created by Thibaut Schmitt on 13/12/2021.
//
import Foundation
import ToolCore
class FontExtensionGenerator {
private static func getFontNameEnum(fontsNames: [String]) -> String {
var enumDefinition = " enum FontName: String {\n"
fontsNames.forEach {
enumDefinition += " case \($0.fontNameSanitize) = \"\($0)\"\n"
}
enumDefinition += " }\n"
return enumDefinition
}
static func writeExtensionFile(fontsNames: [String],
staticVar: Bool,
extensionName: String,
extensionFilePath: String,
isSwiftUI: Bool) {
// Create extension content
let extensionContent = Self.getExtensionContent(fontsNames: fontsNames,
staticVar: staticVar,
extensionName: extensionName,
isSwiftUI: isSwiftUI)
// Write content
let extensionFilePathURL = URL(fileURLWithPath: extensionFilePath)
do {
try extensionContent.write(to: extensionFilePathURL, atomically: false, encoding: .utf8)
} catch let error {
let error = FontsToolError.writeExtension(extensionFilePath, error.localizedDescription)
print(error.description)
Fonts.exit(withError: error)
}
}
static func getExtensionContent(fontsNames: [String],
staticVar: Bool,
extensionName: String,
isSwiftUI: Bool) -> String {
[
Self.getHeader(extensionClassname: extensionName, isSwiftUI: isSwiftUI),
Self.getFontNameEnum(fontsNames: fontsNames),
Self.getFontMethods(fontsNames: fontsNames, staticVar: staticVar, isSwiftUI: isSwiftUI),
Self.getFooter()
]
.joined(separator: "\n")
}
private static func getHeader(extensionClassname: String, isSwiftUI: Bool) -> String {
"""
// Generated by ResgenSwift.\(Fonts.toolName) \(ResgenSwiftVersion)
import \(isSwiftUI ? "SwiftUI" : "UIKit")
extension \(extensionClassname) {\n
"""
}
private static func getFontMethods(fontsNames: [FontName], staticVar: Bool, isSwiftUI: Bool) -> String {
let pragma = " // MARK: - Getter"
var propertiesOrMethods: [String] = fontsNames
.unique()
.map {
$0.getProperty(isStatic: staticVar, isSwiftUI: isSwiftUI)
}
propertiesOrMethods.insert(pragma, at: 0)
return propertiesOrMethods.joined(separator: "\n\n")
}
private static func getFooter() -> String {
"""
}
"""
}
}

View File

@ -1,46 +0,0 @@
//
// FontName.swift
//
//
// Created by Thibaut Schmitt on 29/08/2022.
//
import Foundation
typealias FontName = String
extension FontName {
var fontNameSanitize: String {
self.removeCharacters(from: "[]+-_")
}
func getProperty(isStatic: Bool, isSwiftUI: Bool) -> String {
if isSwiftUI {
if isStatic {
return """
static let \(fontNameSanitize): ((_ size: CGFloat) -> Font) = { size in
Font.custom(FontName.\(fontNameSanitize).rawValue, size: size)
}
"""
}
return """
func \(fontNameSanitize)(withSize size: CGFloat) -> Font {
Font.custom(FontName.\(fontNameSanitize).rawValue, size: size)
}
"""
}
// UIKit
if isStatic {
return """
static let \(fontNameSanitize): ((_ size: CGFloat) -> UIFont) = { size in
UIFont(name: FontName.\(fontNameSanitize).rawValue, size: size)!
}
"""
}
return """
func \(fontNameSanitize)(withSize size: CGFloat) -> UIFont {
UIFont(name: FontName.\(fontNameSanitize).rawValue, size: size)!
}
"""
}
}

View File

@ -1,19 +0,0 @@
//
// StringExtensions.swift
//
//
// Created by Thibaut Schmitt on 31/08/2022.
//
import Foundation
extension String {
func prependIfRelativePath(_ prependPath: String) -> String {
// If path starts with "/", it's an absolute path
if self.hasPrefix("/") {
return self
}
return prependPath + self
}
}

View File

@ -1,61 +0,0 @@
//
// 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.analytics.count) analytics configuration(s)")
print(" - \(configuration.colors.count) colors configuration(s)")
print(" - \(configuration.fonts.count) fonts configuration(s)")
print(" - \(configuration.images.count) images configuration(s)")
print(" - \(configuration.strings.count) strings configuration(s)")
print(" - \(configuration.tags.count) tags configuration(s)")
print()
if let architecture = configuration.architecture {
ArchitectureGenerator.writeArchitecture(architecture,
projectDirectory: options.projectDirectory)
}
// 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

@ -1,34 +0,0 @@
//
// ResgenSwiftError.swift
//
//
// Created by Thibaut Schmitt on 30/08/2022.
//
import Foundation
enum GenerateError: Error {
case fileNotExists(String)
case invalidConfigurationFile(String, String)
case commandError([String], String)
case writeFile(String, String)
var description: String {
switch self {
case .fileNotExists(let filename):
return "error: [\(Generate.toolName)] File \(filename) does not exists"
case .invalidConfigurationFile(let filename, let underneathErrorDescription):
return "error: [\(Generate.toolName)] File \(filename) is not a valid configuration file. Underneath error: \(underneathErrorDescription)"
case .commandError(let command, let terminationStatus):
let readableCommand = command
.map { $0 }
.joined(separator: " ")
return "error: [\(Generate.toolName)] An error occured while running command '\(readableCommand)'. Command terminate with status code: \(terminationStatus)"
case .writeFile(let filename, let info):
return "error: [\(Generate.toolName)] An error occured while writing file in \(filename): \(info)"
}
}
}

View File

@ -1,26 +0,0 @@
//
// GenerateOptions.swift
//
//
// Created by Thibaut Schmitt on 30/08/2022.
//
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

@ -1,39 +0,0 @@
//
// ArchitectureGenerator.swift
//
//
// Created by Thibaut Schmitt on 18/11/2022.
//
import ToolCore
import Foundation
struct ArchitectureGenerator {
static func writeArchitecture(_ architecture: ConfigurationArchitecture, projectDirectory: String) {
// Create extension content
var architectureContent = [
"// Generated by ResgenSwift.\(Generate.toolName) \(ResgenSwiftVersion)",
architecture.getClass()
]
.joined(separator: "\n\n")
architectureContent += "\n"
let filename = "\(architecture.classname).swift"
guard let filePath = architecture.path?.prependIfRelativePath(projectDirectory) else {
let error = GenerateError.writeFile(filename, "Path of file is not defined.")
print(error.description)
Generate.exit(withError: error)
}
// Write content
let architectureFilePathURL = URL(fileURLWithPath: "\(filePath)/\(filename)")
do {
try architectureContent.write(to: architectureFilePathURL, atomically: false, encoding: .utf8)
} catch let error {
let error = GenerateError.writeFile(filename, error.localizedDescription)
print(error.description)
Generate.exit(withError: error)
}
}
}

View File

@ -1,361 +0,0 @@
//
// ConfigurationFile.swift
//
//
// Created by Thibaut Schmitt on 30/08/2022.
//
import Foundation
struct ConfigurationFile: Codable, CustomDebugStringConvertible {
var architecture: ConfigurationArchitecture?
var analytics: [AnalyticsConfiguration]
var colors: [ColorsConfiguration]
var fonts: [FontsConfiguration]
var images: [ImagesConfiguration]
var strings: [StringsConfiguration]
var tags: [TagsConfiguration]
var runnableConfigurations: [Runnable] {
let runnables: [[Runnable]] = [analytics, colors, fonts, images, strings, tags]
return Array(runnables.joined())
}
var debugDescription: String {
"""
\(analytics)
-----------
-----------
\(colors)
-----------
-----------
\(fonts)
-----------
-----------
\(images)
-----------
-----------
\(strings)
-----------
-----------
\(tags)
"""
}
}
struct ConfigurationArchitecture: Codable {
let property: String
let classname: String
let path: String?
let children: [ConfigurationArchitecture]?
func getProperty(isStatic: Bool) -> String {
" \(isStatic ? "static " : "")let \(property) = \(classname)()"
}
func getClass(generateStaticProperty: Bool = true) -> String {
guard children?.isEmpty == false else {
return "final class \(classname): Sendable {}"
}
let classDefinition = [
"class \(classname) {",
children?.map { $0.getProperty(isStatic: generateStaticProperty) }.joined(separator: "\n"),
"}"
]
.compactMap { $0 }
.joined(separator: "\n")
return [classDefinition, "", getSubclass()]
.compactMap { $0 }
.joined(separator: "\n")
}
func getSubclass() -> String? {
guard let children else { return nil }
return children.compactMap { arch in
arch.getClass(generateStaticProperty: false)
}
.joined(separator: "\n\n")
}
}
struct AnalyticsConfiguration: Codable, CustomDebugStringConvertible {
let inputFile: String
let target: String
let extensionOutputPath: String
let extensionName: String?
let extensionSuffix: String?
private let staticMembers: Bool?
var staticMembersOptions: Bool {
if let staticMembers = staticMembers {
return staticMembers
}
return false
}
internal init(inputFile: String,
target: String,
extensionOutputPath: String,
extensionName: String?,
extensionSuffix: String?,
staticMembers: Bool?) {
self.inputFile = inputFile
self.target = target
self.extensionOutputPath = extensionOutputPath
self.extensionName = extensionName
self.extensionSuffix = extensionSuffix
self.staticMembers = staticMembers
}
var debugDescription: String {
"""
Analytics configuration:
- Input file: \(inputFile)
- Target: \(target)
- Extension output path: \(extensionOutputPath)
- Extension name: \(extensionName ?? "-")
- Extension suffix: \(extensionSuffix ?? "-")
"""
}
}
struct ColorsConfiguration: Codable, CustomDebugStringConvertible {
let inputFile: String
let style: String
let xcassetsPath: String
let extensionOutputPath: String
let extensionName: String?
let extensionNameUIKit: String?
let extensionSuffix: String?
private let staticMembers: Bool?
var staticMembersOptions: Bool {
if let staticMembers = staticMembers {
return staticMembers
}
return false
}
internal init(inputFile: String,
style: String,
xcassetsPath: String,
extensionOutputPath: String,
extensionName: String?,
extensionNameUIKit: String?,
extensionSuffix: String?,
staticMembers: Bool?) {
self.inputFile = inputFile
self.style = style
self.xcassetsPath = xcassetsPath
self.extensionOutputPath = extensionOutputPath
self.extensionName = extensionName
self.extensionNameUIKit = extensionNameUIKit
self.extensionSuffix = extensionSuffix
self.staticMembers = staticMembers
}
var debugDescription: String {
"""
Colors configuration:
- Input file: \(inputFile)
- Style: \(style)
- Xcassets path: \(xcassetsPath)
- Extension output path: \(extensionOutputPath)
- Extension name: \(extensionName ?? "-")
- Extension name UIKit: \(extensionNameUIKit ?? "-")
- Extension suffix: \(extensionSuffix ?? "-")
"""
}
}
struct FontsConfiguration: Codable, CustomDebugStringConvertible {
let inputFile: String
let extensionOutputPath: String
let extensionName: String?
let extensionNameUIKit: String?
let extensionSuffix: String?
let infoPlistPaths: String?
private let staticMembers: Bool?
var staticMembersOptions: Bool {
if let staticMembers = staticMembers {
return staticMembers
}
return false
}
internal init(inputFile: String,
extensionOutputPath: String,
extensionName: String?,
extensionNameUIKit: String?,
extensionSuffix: String?,
infoPlistPaths: String?,
staticMembers: Bool?) {
self.inputFile = inputFile
self.extensionOutputPath = extensionOutputPath
self.extensionName = extensionName
self.extensionNameUIKit = extensionNameUIKit
self.extensionSuffix = extensionSuffix
self.infoPlistPaths = infoPlistPaths
self.staticMembers = staticMembers
}
var debugDescription: String {
"""
Fonts configuration:
- Input file: \(inputFile)
- Extension output path: \(extensionOutputPath)
- Extension name: \(extensionName ?? "-")
- Extension name UIKit: \(extensionNameUIKit ?? "-")
- Extension suffix: \(extensionSuffix ?? "-")
- InfoPlistPaths: \(infoPlistPaths ?? "-")
"""
}
}
struct ImagesConfiguration: Codable, CustomDebugStringConvertible {
let inputFile: String
let xcassetsPath: String
let extensionOutputPath: String
let extensionName: String?
let extensionNameUIKit: String?
let extensionSuffix: String?
private let staticMembers: Bool?
var staticMembersOptions: Bool {
if let staticMembers = staticMembers {
return staticMembers
}
return false
}
internal init(inputFile: String,
xcassetsPath: String,
extensionOutputPath: String,
extensionName: String?,
extensionNameUIKit: String?,
extensionSuffix: String?,
staticMembers: Bool?) {
self.inputFile = inputFile
self.xcassetsPath = xcassetsPath
self.extensionOutputPath = extensionOutputPath
self.extensionName = extensionName
self.extensionNameUIKit = extensionNameUIKit
self.extensionSuffix = extensionSuffix
self.staticMembers = staticMembers
}
var debugDescription: String {
"""
Images configuration:
- Input file: \(inputFile)
- Xcassets path: \(xcassetsPath)
- Extension output path: \(extensionOutputPath)
- Extension name: \(extensionName ?? "-")
- Extension name UIKit: \(extensionNameUIKit ?? "-")
- 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?
private let xcStrings: Bool?
var staticMembersOptions: Bool {
if let staticMembers = staticMembers {
return staticMembers
}
return false
}
var xcStringsOptions: Bool {
if let xcStrings = xcStrings {
return xcStrings
}
return false
}
internal init(inputFile: String,
outputPath: String,
langs: String,
defaultLang: String,
extensionOutputPath: String,
extensionName: String?,
extensionSuffix: String?,
staticMembers: Bool?,
xcStrings: Bool?) {
self.inputFile = inputFile
self.outputPath = outputPath
self.langs = langs
self.defaultLang = defaultLang
self.extensionOutputPath = extensionOutputPath
self.extensionName = extensionName
self.extensionSuffix = extensionSuffix
self.staticMembers = staticMembers
self.xcStrings = xcStrings
}
var debugDescription: String {
"""
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
}
internal init(inputFile: String,
lang: String,
extensionOutputPath: String,
extensionName: String?,
extensionSuffix: String?,
staticMembers: Bool?) {
self.inputFile = inputFile
self.lang = lang
self.extensionOutputPath = extensionOutputPath
self.extensionName = extensionName
self.extensionSuffix = extensionSuffix
self.staticMembers = staticMembers
}
var debugDescription: String {
"""
Tags configuration:
- Input file: \(inputFile)
- Lang: \(lang)
- Extension output path: \(extensionOutputPath)
- Extension name: \(extensionName ?? "-")
- Extension suffix: \(extensionSuffix ?? "-")
"""
}
}

Some files were not shown because too many files have changed in this diff Show More