35 Commits

Author SHA1 Message Date
09556ba6e0 [DEVTOOLS-186] Exporter les images de resgen en svg
Some checks failed
gitea-openium/resgen.swift/pipeline/head There was a failure building this commit
Reviewed-on: #12
Reviewed-by: Thibaut Schmitt <t.schmitt@openium.fr>
2024-07-17 15:18:13 +02:00
dea57dc1e2 Maj du readme
Some checks failed
gitea-openium/resgen.swift/pipeline/pr-master There was a failure building this commit
gitea-openium/resgen.swift/pipeline/head There was a failure building this commit
2024-07-11 10:29:32 +02:00
07575bd2bf DEVTOOLS-195 Ne pas générer les svg en template par défaut
Some checks failed
gitea-openium/resgen.swift/pipeline/pr-master There was a failure building this commit
2024-07-11 10:08:47 +02:00
8686ae974c Suppression des anciens assets si svg
Some checks failed
gitea-openium/resgen.swift/pipeline/pr-master There was a failure building this commit
2024-06-21 14:54:20 +02:00
be4c561ea8 DEVTOOLS-192 Resgen iOS vector
Some checks failed
gitea-openium/resgen.swift/pipeline/pr-master There was a failure building this commit
2024-06-21 09:18:51 +02:00
2357a40fff DEVTOOLS-186 Exporter les images de resgen en svg
Some checks failed
gitea-openium/resgen.swift/pipeline/head There was a failure building this commit
gitea-openium/resgen.swift/pipeline/pr-master There was a failure building this commit
2024-04-22 12:05:24 +02:00
d4afa9c9e9 Maj du Readme suite aux devs sur le fichier string catalog
Some checks failed
gitea-openium/resgen.swift/pipeline/head There was a failure building this commit
2024-04-22 09:48:21 +02:00
76ef0a2d59 Merge pull request 'DEVTOOLS-185 Remplacer le json en dur des images resgen' (#11) from DEVTOOLS-186/SVG_resgen into master
Some checks failed
gitea-openium/resgen.swift/pipeline/head There was a failure building this commit
Reviewed-on: #11
Reviewed-by: Thibaut Schmitt <t.schmitt@openium.fr>
2024-04-19 17:02:26 +02:00
129eb135f1 DEVTOOLS-185 Remplacer le json en dur des images resgen
Some checks are pending
gitea-openium/resgen.swift/pipeline/pr-master Build started...
2024-04-19 17:02:00 +02:00
4ad15fcded Merge pull request 'xcstrings' (#10) from xcstrings into master
Some checks failed
gitea-openium/resgen.swift/pipeline/head There was a failure building this commit
Reviewed-on: #10
Reviewed-by: Thibaut Schmitt <t.schmitt@openium.fr>
2024-04-19 16:59:03 +02:00
fb2ddb2227 DEVTOOLS-181 Gérer le tag noTranslation pour les xcstrings 2024-04-17 09:44:09 +02:00
27f86f5c4d Correction de l'option pour le fichier xcstrings 2024-04-15 16:51:18 +02:00
209ba49e3f Gestion des commentaires 2024-04-12 17:15:15 +02:00
ba07005b13 Fix equatable properties for arrays 2024-04-12 16:45:09 +02:00
6c3f3a8982 Correction du test testGenerateXcStringsRootObject 2024-04-12 16:25:10 +02:00
0d651b810f Première implémentation des xcstrings 2024-04-12 16:09:54 +02:00
1d7fc76340 Changed the condition for moe visibility 2024-04-11 15:49:07 +02:00
5d4e461933 Affichage du commentaire même si nil ou empty 2024-04-11 14:20:54 +02:00
55264d61ad Modification de l'affichage des commentaires
Some checks failed
gitea-openium/resgen.swift/pipeline/head There was a failure building this commit
gitea-openium/resgen.swift/pipeline/pr-master There was a failure building this commit
2024-04-11 11:18:57 +02:00
d21ad9d1ea Update JenkinsFile Xcode version
Some checks failed
gitea-openium/resgen.swift/pipeline/head There was a failure building this commit
2024-04-11 10:19:10 +02:00
0bd6c3c2d4 Correction des commentaires des strings
Some checks failed
gitea-openium/resgen.swift/pipeline/head There was a failure building this commit
2024-04-11 10:16:28 +02:00
eed20367b9 fix: Edit for NSObject in Firebase
Some checks failed
gitea-openium/resgen.swift/pipeline/pr-master There was a failure building this commit
gitea-openium/resgen.swift/pipeline/head There was a failure building this commit
PixeeBox/resgen.swift/pipeline/head There was a failure building this commit
2023-12-13 11:24:21 +01:00
43b5111d79 Actualiser Sources/ResgenSwift/Analytics/Generator/AnalyticsGenerator.swift
Some checks failed
gitea-openium/resgen.swift/pipeline/head There was a failure building this commit
2023-12-13 10:39:12 +01:00
2983093a9c Add Swiftlint 2023-12-13 10:39:12 +01:00
b4bbaa3bfd Fix Image 2023-12-13 10:39:12 +01:00
498c8fa4ae Fix Font 2023-12-13 10:39:12 +01:00
2957da6233 Fix Color 2023-12-13 10:39:12 +01:00
d79af06c38 docs: Add Analytics section in README
Some checks failed
gitea-openium/resgen.swift/pipeline/head There was a failure building this commit
2023-12-13 10:27:35 +01:00
d8937f2de6 docs: Add Analytics section in README 2023-12-13 10:23:31 +01:00
9b27f24197 docs: Add Analytics section in README 2023-12-13 10:20:53 +01:00
1d58fd5510 Edit sample file
Some checks failed
gitea-openium/resgen.swift/pipeline/pr-master There was a failure building this commit
2023-12-12 16:58:25 +01:00
f6c49bf626 Add parse error
Some checks failed
gitea-openium/resgen.swift/pipeline/head There was a failure building this commit
2023-12-11 11:19:19 +01:00
f1b62d83c4 Add missing print error
Some checks failed
gitea-openium/resgen.swift/pipeline/head There was a failure building this commit
2023-12-11 10:29:19 +01:00
ee5055efa5 Retours sur la structure du code
Some checks failed
gitea-openium/resgen.swift/pipeline/head There was a failure building this commit
2023-12-11 10:24:42 +01:00
6f8e3b6664 Add error handling
Some checks failed
gitea-openium/resgen.swift/pipeline/head There was a failure building this commit
2023-12-11 10:09:24 +01:00
39 changed files with 2714 additions and 447 deletions

2
Jenkinsfile vendored
View File

@ -1,6 +1,6 @@
library "openiumpipeline" library "openiumpipeline"
env.DEVELOPER_DIR="/Applications/Xcode-15.0.1.app/Contents/Developer" env.DEVELOPER_DIR="/Applications/Xcode-15.4.0.app/Contents/Developer"
//env.SIMULATOR_DEVICE_TYPES="iPad--7th-generation-" //env.SIMULATOR_DEVICE_TYPES="iPad--7th-generation-"
env.IS_PACKAGE_SWIFT=1 env.IS_PACKAGE_SWIFT=1
env.TARGETS_MACOS=1 env.TARGETS_MACOS=1

View File

@ -14,8 +14,8 @@
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",
"location" : "https://github.com/krzyzanowskim/CryptoSwift.git", "location" : "https://github.com/krzyzanowskim/CryptoSwift.git",
"state" : { "state" : {
"revision" : "db51c407d3be4a051484a141bf0bff36c43d3b1e", "revision" : "c9c3df6ab812de32bae61fc0cd1bf6d45170ebf0",
"version" : "1.8.0" "version" : "1.8.2"
} }
}, },
{ {

View File

@ -81,7 +81,7 @@ swift run -c release ResgenSwift strings twine $FORCE_FLAG "./Twine/strings.txt"
2. Input translations file (must be Twine formatted) 2. Input translations file (must be Twine formatted)
3. `--langs`: langs to generate (string with space between each lang) 3. `--langs`: langs to generate (string with space between each lang)
4. `--default-lang`: default lang that will be in `Base.lproj`. It must be in `langs` as well 4. `--default-lang`: default lang that will be in `Base.lproj`. It must be in `langs` as well
4. `--extension-output-path`: path where to generate generated extension 5. `--extension-output-path`: path where to generate generated extension
### Stringium (recommended) ### Stringium (recommended)
@ -93,6 +93,7 @@ swift run -c release ResgenSwift strings stringium $FORCE_FLAG "./Strings/string
--extension-output-path "./Strings/Generated" \ --extension-output-path "./Strings/Generated" \
--extension-name "AppString" \ --extension-name "AppString" \
--extension-suffix "GreatApp" \ --extension-suffix "GreatApp" \
--xcStrings true
--static-members true --static-members true
``` ```
@ -105,6 +106,7 @@ swift run -c release ResgenSwift strings stringium $FORCE_FLAG "./Strings/string
4. `--extension-output-path`: path where to generate generated extension 4. `--extension-output-path`: path where to generate generated extension
5. `--extension-name` *(optional)* : name of class to add the extension 5. `--extension-name` *(optional)* : name of class to add the extension
6. `--extension-suffix` *(optional)* : additional text which is added to filename (ex: `AppString+GreatApp.swift`) 6. `--extension-suffix` *(optional)* : additional text which is added to filename (ex: `AppString+GreatApp.swift`)
6. `--xcStrings`*(optional)* : generate string catalog
7. `--static-members` *(optional)*: generate static properties or not 7. `--static-members` *(optional)*: generate static properties or not
@ -133,9 +135,74 @@ swift run -c release ResgenSwift strings tags $FORCE_FLAG "./Tags/tags.txt" \
> ⚠️ If extension name is not set or is `Tags`, it will generate the following typaloas `typealias Tags = String`. > ⚠️ If extension name is not set or is `Tags`, it will generate the following typaloas `typealias Tags = String`.
## Analytics
Analytics will generate all you need to analyze UX with Matomo or Firebase Analytics. Input files are formatted in YAML. This command will generate a manager for each target and an AnalyticsManager. This is this one you will need to use. And it will generate a method for all tags you have declared in the YAML file. Next, you will need to use the `configure()` method of AnalyticsManager and if you want to use matomo to set up the `siteId` and the `url` of the site.
```sh
swift run -c release ResgenSwift strings tags $FORCE_FLAG "./Tags/tags.txt" \
--target "matomo firebase" \
--extension-output-path "./Analytics/Generated" \
--extension-name "AppAnalytics" \
--extension-suffix "GreatApp" \
--static-members true
```
**Parameters**
1. `-f`: force generation
2. Input tags file (must be YAML formatted)
3. `--target`: target with you will log UX
4. `--extension-output-path`: path where to generate generated extension
5. `--extension-name` *(optional)* : name of class to add the extension
6. `--extension-suffix` *(optional)* : additional text which is added to filename (ex: `AppAnalytics+GreatApp.swift`)
7. `--static-members` *(optional)*: generate static properties or not
> ⚠️ If extension name is not set or is `Analytics`, it will generate the following typaloas `typealias Analytics = String`.
### YAML
```
- id: s1_def_one
name: s1 def one _TITLE_
path: s1_def_one/_TITLE_
action: Tap
category: User
tags: ios,droid
comments:
parameters:
- name: title
type: String
replaceIn: name,path
```
1. `id`: name of the method (method name will be composed of `log` + `Event|Screen` + `id`)
2. `name`: name of the tag
3. `path` *(optional with firebase)* : needed for matomo but not with firebase (log screen)
4. `action` *(optional with firebase)* : needed for matomo but not with firebase (log event)
5. `category` *(optional with firebase)* : needed for matomo but not with firebase (log event)
6. `tags`: which platform target
7. `comments` *(optional)*
8. `parameters` *(optional)*
**Parameters**
You can use parameters in generate methods.
1. `name`: name of the parameter
2. `type`: type of the parameter (Int, String, Bool, Double)
3. `replaceIn` *(optional)*
**Replace in**
This is section is equivalent of `%s | %d | %f | %@`. You can put the content of the parameter in *name*, *path*, *action*, *category*.
You need to put `_` + `NAME OF THE PARAMETER` + `_` in the target and which target you want in the value of `replaceIn`. (name need to be in uppercase)
## Images ## Images
Images generator will generate images assets along with extensions to access those images easily. Images generator will generate images assets along with extensions to access those images easily.
```sh ```sh
swift run -c release ResgenSwift images $FORCE_FLAG "./Images/images.txt" \ swift run -c release ResgenSwift images $FORCE_FLAG "./Images/images.txt" \
@ -158,6 +225,7 @@ swift run -c release ResgenSwift images $FORCE_FLAG "./Images/images.txt" \
6. `--extension-suffix` *(optional)* : additional text which is added to filename (ex: `AppImage+GreatApp.swift`) 6. `--extension-suffix` *(optional)* : additional text which is added to filename (ex: `AppImage+GreatApp.swift`)
7. `--static-members` *(optional)*: generate static properties or not 7. `--static-members` *(optional)*: generate static properties or not
> ⚠️ Svg images will be copied in the assets and rendered as "Original", however if those images are not rendered correctly you can force the png generation by adding the key word "png" like this: id arrow_back 15 ? png
## All at once ## All at once

View File

@ -1,7 +1,7 @@
// Generated by ResgenSwift.Analytics 1.2 // Generated by ResgenSwift.Analytics 1.2
import MatomoTracker import MatomoTracker
import Firebase import FirebaseAnalytics
// MARK: - Protocol // MARK: - Protocol
@ -81,7 +81,7 @@ class MatomoAnalyticsManager: AnalyticsManagerProtocol {
class FirebaseAnalyticsManager: AnalyticsManagerProtocol { class FirebaseAnalyticsManager: AnalyticsManagerProtocol {
func logScreen(name: String, path: String) { func logScreen(name: String, path: String) {
var parameters = [ var parameters = [
AnalyticsParameterScreenName: name AnalyticsParameterScreenName: name as NSObject
] ]
Analytics.logEvent( Analytics.logEvent(
@ -96,19 +96,25 @@ class FirebaseAnalyticsManager: AnalyticsManagerProtocol {
category: String, category: String,
params: [String: Any]? params: [String: Any]?
) { ) {
var parameters: [String:Any] = [ var parameters: [String:NSObject] = [
"action": action, "action": action as NSObject,
"category": category, "category": category as NSObject,
] ]
if let supplementaryParameters = params { if let supplementaryParameters = params {
parameters.merge(supplementaryParameters) { (origin, new) -> Any in for (newKey, newValue) in supplementaryParameters {
return origin if parameters.contains(where: { (key: String, value: NSObject) in
key == newKey
}) {
continue
}
parameters[newKey] = newValue as? NSObject
} }
} }
Analytics.logEvent( Analytics.logEvent(
name, name.replacingOccurrences(of: [" "], with: "_"),
parameters: parameters parameters: parameters
) )
} }
@ -169,19 +175,22 @@ class AnalyticsManager {
// MARK: - section_one // MARK: - section_one
func logScreenS1DefOne() { func logScreenS1DefOne(title: String) {
logScreen( logScreen(
name: "s1 def one", name: "s1 def one \(title)",
path: "s1_def_one/" path: "s1_def_one/\(title)"
) )
} }
func logEventS1DefTwo() { func logEventS1DefTwo(title: String, count: String) {
logEvent( logEvent(
name: "s1 def two", name: "s1 def two",
action: "test", action: "test",
category: "test", category: "test",
params: [] params: [
"title": title,
"count": count
]
) )
} }

View File

@ -3,9 +3,13 @@ categories:
- id: section_one - id: section_one
screens: screens:
- id: s1_def_one - id: s1_def_one
name: s1 def one name: s1 def one _TITLE_
path: s1_def_one/ path: s1_def_one/_TITLE_
tags: ios tags: ios,droid
parameters:
- name: title
type: String
replaceIn: name,path
events: events:
- id: s1_def_two - id: s1_def_two
@ -13,6 +17,11 @@ categories:
action: test action: test
category: test category: test
tags: ios tags: ios
parameters:
- name: title
type: String
- name: count
type: String
- id: section_two - id: section_two
screens: screens:

View File

@ -52,19 +52,19 @@ FORCE_FLAG="$1"
#echo "\n-------------------------\n" #echo "\n-------------------------\n"
## Analytics # Analytics
#swift run -c release ResgenSwift analytics $FORCE_FLAG "./Tags/sampleTags.yml" \ swift run -c release ResgenSwift analytics $FORCE_FLAG "./Tags/sampleTags.yml" \
# --target "matomo firebase" \ --target "matomo firebase" \
# --extension-output-path "./Tags/Generated" \ --extension-output-path "./Tags/Generated" \
# --extension-name "Analytics" \ --extension-name "Analytics" \
# --extension-suffix "GenAllScript" --extension-suffix "GenAllScript"
#echo "\n-------------------------\n" #echo "\n-------------------------\n"
# #
# Images ## Images
swift run -c release ResgenSwift images $FORCE_FLAG "./Images/sampleImages.txt" \ #swift run -c release ResgenSwift images $FORCE_FLAG "./Images/sampleImages.txt" \
--xcassets-path "./Images/imagium.xcassets" \ # --xcassets-path "./Images/imagium.xcassets" \
--extension-output-path "./Images/Generated" \ # --extension-output-path "./Images/Generated" \
--extension-name "ImageYolo" \ # --extension-name "ImageYolo" \
--extension-name-ui-kit "UIImageYolo" \ # --extension-name-ui-kit "UIImageYolo" \
--extension-suffix "GenAllScript" # --extension-suffix "GenAllScript"

View File

@ -32,8 +32,15 @@ struct Analytics: ParsableCommand {
mutating func run() { mutating func run() {
print("[\(Self.toolName)] Starting analytics generation") print("[\(Self.toolName)] Starting analytics generation")
print("[\(Self.toolName)] Will use inputFile \(options.inputFile) to generate analytics for target: \(options.target)") 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") print("[\(Self.toolName)] Will generate analytics")
// Check requirements
guard checkRequirements() else { return }
// Parse input file // Parse input file
let sections = AnalyticsFileParser.parse(options.inputFile, target: options.target) let sections = AnalyticsFileParser.parse(options.inputFile, target: options.target)
@ -47,20 +54,33 @@ struct Analytics: ParsableCommand {
print("[\(Self.toolName)] Analytics generated") print("[\(Self.toolName)] Analytics generated")
} }
}
// MARK: - Requirements
extension Analytics {
enum TargetType: CaseIterable { private func checkRequirements() -> Bool {
case matomo let fileManager = FileManager()
case firebase
var value: String { // Input file
switch self { guard fileManager.fileExists(atPath: options.inputFile) else {
case .matomo: let error = AnalyticsError.fileNotExists(options.inputFile)
"matomo" print(error.description)
case .firebase: Analytics.exit(withError: error)
"firebase"
}
} }
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

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

View File

@ -10,13 +10,13 @@ import ToolCore
import CoreVideo import CoreVideo
class AnalyticsGenerator { class AnalyticsGenerator {
static var targets: [Analytics.TargetType] = [] static var targets: [TrackerType] = []
static func writeExtensionFiles(sections: [AnalyticsCategory], target: String, tags: [String], staticVar: Bool, extensionName: String, extensionFilePath: String) { static func writeExtensionFiles(sections: [AnalyticsCategory], target: String, tags: [String], staticVar: Bool, extensionName: String, extensionFilePath: String) {
// Get target type from enum // Get target type from enum
let targetsString: [String] = target.components(separatedBy: " ") let targetsString: [String] = target.components(separatedBy: " ")
Analytics.TargetType.allCases.forEach { enumTarget in TrackerType.allCases.forEach { enumTarget in
if targetsString.contains(enumTarget.value) { if targetsString.contains(enumTarget.value) {
targets.append(enumTarget) targets.append(enumTarget)
} }
@ -33,9 +33,9 @@ class AnalyticsGenerator {
do { do {
try extensionFileContent.write(to: extensionFilePathURL, atomically: false, encoding: .utf8) try extensionFileContent.write(to: extensionFilePathURL, atomically: false, encoding: .utf8)
} catch let error { } catch let error {
let error = StringiumError.writeFile(extensionFilePath, error.localizedDescription) let error = AnalyticsError.writeFile(extensionFilePath, error.localizedDescription)
print(error.description) print(error.description)
Stringium.exit(withError: error) Analytics.exit(withError: error)
} }
} }
@ -58,7 +58,7 @@ class AnalyticsGenerator {
\(Self.getImport()) \(Self.getImport())
\(Self.getAnalytics()) \(Self.getAnalyticsProtocol())
// MARK: - Manager // MARK: - Manager
class AnalyticsManager { class AnalyticsManager {
@ -91,10 +91,10 @@ class AnalyticsGenerator {
private static func getImport() -> String { private static func getImport() -> String {
var result: [String] = [] var result: [String] = []
if targets.contains(Analytics.TargetType.matomo) { if targets.contains(TrackerType.matomo) {
result.append("import MatomoTracker") result.append("import MatomoTracker")
} }
if targets.contains(Analytics.TargetType.firebase) { if targets.contains(TrackerType.firebase) {
result.append("import FirebaseAnalytics") result.append("import FirebaseAnalytics")
} }
@ -136,13 +136,13 @@ class AnalyticsGenerator {
var content: [String] = [] var content: [String] = []
let footer = " }" let footer = " }"
if targets.contains(Analytics.TargetType.matomo) { if targets.contains(TrackerType.matomo) {
header = "func configure(siteId: String, url: String) {" header = "func configure(siteId: String, url: String) {"
} else if targets.contains(Analytics.TargetType.firebase) { } else if targets.contains(TrackerType.firebase) {
header = "func configure() {" header = "func configure() {"
} }
if targets.contains(Analytics.TargetType.matomo) { if targets.contains(TrackerType.matomo) {
content.append(""" content.append("""
managers.append( managers.append(
MatomoAnalyticsManager( MatomoAnalyticsManager(
@ -152,7 +152,7 @@ class AnalyticsGenerator {
) )
""") """)
} }
if targets.contains(Analytics.TargetType.firebase) { if targets.contains(TrackerType.firebase) {
content.append(" managers.append(FirebaseAnalyticsManager())") content.append(" managers.append(FirebaseAnalyticsManager())")
} }
@ -164,7 +164,7 @@ class AnalyticsGenerator {
.joined(separator: "\n") .joined(separator: "\n")
} }
private static func getAnalytics() -> String { private static func getAnalyticsProtocol() -> String {
let proto = """ let proto = """
// MARK: - Protocol // MARK: - Protocol
@ -182,12 +182,12 @@ class AnalyticsGenerator {
var result: [String] = [proto] var result: [String] = [proto]
if targets.contains(Analytics.TargetType.matomo) { if targets.contains(TrackerType.matomo) {
result.append(MatomoGenerator.service.content) result.append(MatomoGenerator.service)
} }
if targets.contains(Analytics.TargetType.firebase) { if targets.contains(TrackerType.firebase) {
result.append(FirebaseGenerator.service.content) result.append(FirebaseGenerator.service)
} }
return result.joined(separator: "\n") return result.joined(separator: "\n")

View File

@ -8,19 +8,20 @@
import Foundation import Foundation
enum FirebaseGenerator { enum FirebaseGenerator {
case service
static var service: String {
var content: String {
[ [
FirebaseGenerator.service.header, FirebaseGenerator.header,
FirebaseGenerator.service.logScreen, FirebaseGenerator.logScreen,
FirebaseGenerator.service.logEvent, FirebaseGenerator.logEvent,
FirebaseGenerator.service.footer FirebaseGenerator.footer
] ]
.joined(separator: "\n") .joined(separator: "\n")
} }
private var header: String { // MARK: - Private vars
private static var header: String {
""" """
// MARK: - Firebase // MARK: - Firebase
@ -28,11 +29,11 @@ enum FirebaseGenerator {
""" """
} }
private var logScreen: String { private static var logScreen: String {
""" """
func logScreen(name: String, path: String) { func logScreen(name: String, path: String) {
var parameters = [ var parameters = [
AnalyticsParameterScreenName: name AnalyticsParameterScreenName: name as NSObject
] ]
Analytics.logEvent( Analytics.logEvent(
@ -44,7 +45,7 @@ enum FirebaseGenerator {
""" """
} }
private var logEvent: String { private static var logEvent: String {
""" """
func logEvent( func logEvent(
name: String, name: String,
@ -52,26 +53,32 @@ enum FirebaseGenerator {
category: String, category: String,
params: [String: Any]? params: [String: Any]?
) { ) {
var parameters: [String:Any] = [ var parameters: [String:NSObject] = [
"action": action, "action": action as NSObject,
"category": category, "category": category as NSObject,
] ]
if let supplementaryParameters = params { if let supplementaryParameters = params {
parameters.merge(supplementaryParameters) { (origin, new) -> Any in for (newKey, newValue) in supplementaryParameters {
return origin if parameters.contains(where: { (key: String, value: NSObject) in
key == newKey
}) {
continue
}
parameters[newKey] = newValue as? NSObject
} }
} }
Analytics.logEvent( Analytics.logEvent(
name, name.replacingOccurrences(of: [" "], with: "_"),
parameters: parameters parameters: parameters
) )
} }
""" """
} }
private var footer: String { private static var footer: String {
""" """
} }

View File

@ -8,20 +8,21 @@
import Foundation import Foundation
enum MatomoGenerator { enum MatomoGenerator {
case service
static var service: String {
var content: String {
[ [
MatomoGenerator.service.header, MatomoGenerator.header,
MatomoGenerator.service.setup, MatomoGenerator.setup,
MatomoGenerator.service.logScreen, MatomoGenerator.logScreen,
MatomoGenerator.service.logEvent, MatomoGenerator.logEvent,
MatomoGenerator.service.footer MatomoGenerator.footer
] ]
.joined(separator: "\n") .joined(separator: "\n")
} }
private var header: String { // MARK: - Private vars
private static var header: String {
""" """
// MARK: - Matomo // MARK: - Matomo
@ -34,7 +35,7 @@ enum MatomoGenerator {
""" """
} }
private var setup: String { private static var setup: String {
""" """
// MARK: - Init // MARK: - Init
@ -63,7 +64,7 @@ enum MatomoGenerator {
""" """
} }
private var logScreen: String { private static var logScreen: String {
""" """
func logScreen(name: String, path: String) { func logScreen(name: String, path: String) {
guard !tracker.isOptedOut else { return } guard !tracker.isOptedOut else { return }
@ -79,7 +80,7 @@ enum MatomoGenerator {
""" """
} }
private var logEvent: String { private static var logEvent: String {
""" """
func logEvent( func logEvent(
name: String, name: String,
@ -100,7 +101,7 @@ enum MatomoGenerator {
""" """
} }
private var footer: String { private static var footer: String {
""" """
} }

View File

@ -11,10 +11,14 @@ class AnalyticsCategory {
let id: String // OnBoarding let id: String // OnBoarding
var definitions = [AnalyticsDefinition]() var definitions = [AnalyticsDefinition]()
// MARK: - Init
init(id: String) { init(id: String) {
self.id = id self.id = id
} }
// MARK: - Methods
func hasOneOrMoreMatchingTags(tags: [String]) -> Bool { func hasOneOrMoreMatchingTags(tags: [String]) -> Bool {
let allTags = definitions.flatMap { $0.tags } let allTags = definitions.flatMap { $0.tags }
let allTagsSet = Set(allTags) let allTagsSet = Set(allTags)

View File

@ -6,6 +6,7 @@
// //
import Foundation import Foundation
import ToolCore
class AnalyticsDefinition { class AnalyticsDefinition {
let id: String let id: String
@ -18,12 +19,16 @@ class AnalyticsDefinition {
var parameters: [AnalyticsParameter] = [] var parameters: [AnalyticsParameter] = []
var type: TagType var type: TagType
// MARK: - Init
init(id: String, name: String, type: TagType) { init(id: String, name: String, type: TagType) {
self.id = id self.id = id
self.name = name self.name = name
self.type = type self.type = type
} }
// MARK: - Methods
func hasOneOrMoreMatchingTags(inputTags: [String]) -> Bool { func hasOneOrMoreMatchingTags(inputTags: [String]) -> Bool {
if Set(inputTags).isDisjoint(with: tags) { if Set(inputTags).isDisjoint(with: tags) {
return false return false
@ -31,8 +36,8 @@ class AnalyticsDefinition {
return true return true
} }
// MARK: - Methods // MARK: - Private Methods
private func getFuncName() -> String { private func getFuncName() -> String {
var pascalCaseTitle: String = "" var pascalCaseTitle: String = ""
id.components(separatedBy: "_").forEach { word in id.components(separatedBy: "_").forEach { word in
@ -96,7 +101,7 @@ class AnalyticsDefinition {
supplementaryParams.forEach { param in supplementaryParams.forEach { param in
params.append("\"\(param.name)\": \(param.name)") params.append("\"\(param.name)\": \(param.name)")
} }
if params.count > 1 { if params.count > 1 {
result = """ result = """
[ [
@ -150,27 +155,3 @@ class AnalyticsDefinition {
""" """
} }
} }
extension AnalyticsDefinition {
enum TagType {
case screen
case event
}
}
extension String {
func replacingFirstOccurrence(of: String, with: String) -> Self {
if let range = self.range(of: of) {
let tmp = self.replacingOccurrences(
of: of,
with: with,
options: .literal,
range: range
)
return tmp
}
return self
}
}

View File

@ -17,9 +17,7 @@ struct AnalyticsCategoryDTO: Codable {
var events: [AnalyticsDefinitionEventDTO]? var events: [AnalyticsDefinitionEventDTO]?
} }
protocol AnalyticsDefinitionDTO: Codable {} struct AnalyticsDefinitionScreenDTO: Codable {
struct AnalyticsDefinitionScreenDTO: AnalyticsDefinitionDTO {
var id: String var id: String
var name: String var name: String
var tags: String var tags: String
@ -29,7 +27,7 @@ struct AnalyticsDefinitionScreenDTO: AnalyticsDefinitionDTO {
var path: String? var path: String?
} }
struct AnalyticsDefinitionEventDTO: AnalyticsDefinitionDTO { struct AnalyticsDefinitionEventDTO: Codable {
var id: String var id: String
var name: String var name: String
var tags: String var tags: String

View File

@ -12,6 +12,8 @@ class AnalyticsParameter {
var type: String var type: String
var replaceIn: [String] = [] var replaceIn: [String] = []
// MARK: - Init
init(name: String, type: String) { init(name: String, type: String) {
self.name = name self.name = name
self.type = type self.type = type

View File

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

View File

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

View File

@ -14,46 +14,49 @@ class AnalyticsFileParser {
private static func parseYaml() -> AnalyticsFile { private static func parseYaml() -> AnalyticsFile {
guard let data = FileManager().contents(atPath: inputFile) else { guard let data = FileManager().contents(atPath: inputFile) else {
let error = GenerateError.fileNotExists(inputFile) let error = AnalyticsError.fileNotExists(inputFile)
Generate.exit(withError: error) print(error.description)
Analytics.exit(withError: error)
} }
do { do {
let tagFile = try YAMLDecoder().decode(AnalyticsFile.self, from: data) let tagFile = try YAMLDecoder().decode(AnalyticsFile.self, from: data)
return tagFile return tagFile
} catch let error { } catch {
Generate.exit(withError: error) let error = AnalyticsError.parseFailed(error.localizedDescription)
print(error.description)
Analytics.exit(withError: error)
} }
} }
private static func getParameters(fromData data: [AnalyticsParameterDTO]) -> [AnalyticsParameter] { private static func getParameters(from parameters: [AnalyticsParameterDTO]) -> [AnalyticsParameter] {
var parameters: [AnalyticsParameter] = [] parameters.map { dtoParameter in
data.forEach { value in
// Type // Type
let type = value.type.uppercasedFirst() let type = dtoParameter.type.uppercasedFirst()
guard guard
type == "String" || type == "String" ||
type == "Int" || type == "Int" ||
type == "Double" || type == "Double" ||
type == "Bool" type == "Bool"
else { else {
let error = GenerateError.invalidParameter("type of \(value.name)") let error = AnalyticsError.invalidParameter("type of \(dtoParameter.name)")
Generate.exit(withError: error) print(error.description)
Analytics.exit(withError: error)
} }
let parameter: AnalyticsParameter = AnalyticsParameter(name: value.name, type: type) let parameter = AnalyticsParameter(
name: dtoParameter.name,
if let replaceIn = value.replaceIn { type: type
)
if let replaceIn = dtoParameter.replaceIn {
parameter.replaceIn = replaceIn.components(separatedBy: ",") parameter.replaceIn = replaceIn.components(separatedBy: ",")
} }
parameters.append(parameter) return parameter
} }
return parameters
} }
private static func getTagDefinition( private static func getTagDefinition(
@ -64,24 +67,24 @@ class AnalyticsFileParser {
comments: String?, comments: String?,
parameters: [AnalyticsParameterDTO]? parameters: [AnalyticsParameterDTO]?
) -> AnalyticsDefinition { ) -> AnalyticsDefinition {
let definition: AnalyticsDefinition = AnalyticsDefinition(id: id, name: name, type: type) let definition = AnalyticsDefinition(id: id, name: name, type: type)
definition.tags = tags.components(separatedBy: ",") definition.tags = tags
.components(separatedBy: ",")
.map { $0.removeLeadingTrailingWhitespace() }
if let comments = comments { if let comments = comments {
definition.comments = comments definition.comments = comments
} }
if let parameters = parameters { if let parameters = parameters {
definition.parameters = Self.getParameters(fromData: parameters) definition.parameters = Self.getParameters(from: parameters)
} }
return definition return definition
} }
private static func getTagDefinitionScreen(fromData screens: [AnalyticsDefinitionScreenDTO]) -> [AnalyticsDefinition] { private static func getTagDefinitionScreen(from screens: [AnalyticsDefinitionScreenDTO]) -> [AnalyticsDefinition] {
var definitions: [AnalyticsDefinition] = [] screens.map { screen in
for screen in screens {
let definition: AnalyticsDefinition = Self.getTagDefinition( let definition: AnalyticsDefinition = Self.getTagDefinition(
id: screen.id, id: screen.id,
name: screen.name, name: screen.name,
@ -90,28 +93,25 @@ class AnalyticsFileParser {
comments: screen.comments, comments: screen.comments,
parameters: screen.parameters parameters: screen.parameters
) )
if target.contains(Analytics.TargetType.matomo.value) { if target.contains(TrackerType.matomo.value) {
// Path // Path
guard let path = screen.path else { guard let path = screen.path else {
let error = GenerateError.missingElement("screen path") let error = AnalyticsError.missingElement("screen path")
Generate.exit(withError: error) print(error.description)
Analytics.exit(withError: error)
} }
definition.path = path definition.path = path
} }
definitions.append(definition) return definition
} }
return definitions
} }
private static func getTagDefinitionEvent(fromData events: [AnalyticsDefinitionEventDTO]) -> [AnalyticsDefinition] { private static func getTagDefinitionEvent(from events: [AnalyticsDefinitionEventDTO]) -> [AnalyticsDefinition] {
var definitions: [AnalyticsDefinition] = [] events.map { event in
for event in events {
let definition: AnalyticsDefinition = Self.getTagDefinition( let definition: AnalyticsDefinition = Self.getTagDefinition(
id: event.id, id: event.id,
name: event.name, name: event.name,
@ -120,54 +120,59 @@ class AnalyticsFileParser {
comments: event.comments, comments: event.comments,
parameters: event.parameters parameters: event.parameters
) )
if target.contains(Analytics.TargetType.matomo.value) { if target.contains(TrackerType.matomo.value) {
// Category // Category
guard let category = event.category else { guard let category = event.category else {
let error = GenerateError.missingElement("event category") let error = AnalyticsError.missingElement("event category")
Generate.exit(withError: error) print(error.description)
Analytics.exit(withError: error)
} }
definition.category = category definition.category = category
// Action // Action
guard let action = event.action else { guard let action = event.action else {
let error = GenerateError.missingElement("event action") let error = AnalyticsError.missingElement("event action")
Generate.exit(withError: error) print(error.description)
Analytics.exit(withError: error)
} }
definition.action = action definition.action = action
} }
definitions.append(definition) return definition
} }
return definitions
} }
static func parse(_ inputFile: String, target: String) -> [AnalyticsCategory] { static func parse(_ inputFile: String, target: String) -> [AnalyticsCategory] {
self.inputFile = inputFile self.inputFile = inputFile
self.target = target self.target = target
let tagFile: AnalyticsFile = Self.parseYaml() let tagFile = Self.parseYaml()
var sections: [AnalyticsCategory] = []
tagFile.categories.forEach { categorie in return tagFile
let section: AnalyticsCategory = AnalyticsCategory(id: categorie.id) .categories
.map { categorie in
if let screens = categorie.screens { let section: AnalyticsCategory = AnalyticsCategory(id: categorie.id)
section.definitions.append(contentsOf: Self.getTagDefinitionScreen(fromData: screens))
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
} }
if let events = categorie.events {
section.definitions.append(contentsOf: Self.getTagDefinitionEvent(fromData: events))
}
sections.append(section)
}
return sections
} }
} }

View File

@ -9,22 +9,17 @@ import Foundation
enum GenerateError: Error { enum GenerateError: Error {
case fileNotExists(String) case fileNotExists(String)
case invalidConfigurationFile(String) case invalidConfigurationFile(String, String)
case commandError([String], String) case commandError([String], String)
case writeFile(String, String) case writeFile(String, String)
// Analytics
case missingElement(String)
case invalidParameter(String)
var description: String { var description: String {
switch self { switch self {
case .fileNotExists(let filename): case .fileNotExists(let filename):
return "error: [\(Generate.toolName)] File \(filename) does not exists" return "error: [\(Generate.toolName)] File \(filename) does not exists"
case .invalidConfigurationFile(let filename): case .invalidConfigurationFile(let filename, let underneathErrorDescription):
return "error: [\(Generate.toolName)] File \(filename) is not a valid configuration file" return "error: [\(Generate.toolName)] File \(filename) is not a valid configuration file. Underneath error: \(underneathErrorDescription)"
case .commandError(let command, let terminationStatus): case .commandError(let command, let terminationStatus):
let readableCommand = command let readableCommand = command
@ -34,12 +29,6 @@ enum GenerateError: Error {
case .writeFile(let filename, let info): case .writeFile(let filename, let info):
return "error: [\(Generate.toolName)] An error occured while writing file in \(filename): \(info)" return "error: [\(Generate.toolName)] An error occured while writing file in \(filename): \(info)"
case .missingElement(let element):
return "error: [\(Generate.toolName)] Missing \(element) for Matomo"
case .invalidParameter(let reason):
return "error: [\(Generate.toolName)] Invalid parameter \(reason)"
} }
} }
} }

View File

@ -269,14 +269,22 @@ struct StringsConfiguration: Codable, CustomDebugStringConvertible {
let extensionName: String? let extensionName: String?
let extensionSuffix: String? let extensionSuffix: String?
private let staticMembers: Bool? private let staticMembers: Bool?
private let xcStrings: Bool?
var staticMembersOptions: Bool { var staticMembersOptions: Bool {
if let staticMembers = staticMembers { if let staticMembers = staticMembers {
return staticMembers return staticMembers
} }
return false return false
} }
var xcStringsOptions: Bool {
if let xcStrings = xcStrings {
return xcStrings
}
return false
}
internal init(inputFile: String, internal init(inputFile: String,
outputPath: String, outputPath: String,
langs: String, langs: String,
@ -284,7 +292,8 @@ struct StringsConfiguration: Codable, CustomDebugStringConvertible {
extensionOutputPath: String, extensionOutputPath: String,
extensionName: String?, extensionName: String?,
extensionSuffix: String?, extensionSuffix: String?,
staticMembers: Bool?) { staticMembers: Bool?,
xcStrings: Bool?) {
self.inputFile = inputFile self.inputFile = inputFile
self.outputPath = outputPath self.outputPath = outputPath
self.langs = langs self.langs = langs
@ -293,6 +302,7 @@ struct StringsConfiguration: Codable, CustomDebugStringConvertible {
self.extensionName = extensionName self.extensionName = extensionName
self.extensionSuffix = extensionSuffix self.extensionSuffix = extensionSuffix
self.staticMembers = staticMembers self.staticMembers = staticMembers
self.xcStrings = xcStrings
} }
var debugDescription: String { var debugDescription: String {

View File

@ -16,12 +16,15 @@ class ConfigurationFileParser {
Generate.exit(withError: error) Generate.exit(withError: error)
} }
guard let configuration = try? YAMLDecoder().decode(ConfigurationFile.self, from: data) else { do {
let error = GenerateError.invalidConfigurationFile(configurationFile) return try YAMLDecoder().decode(ConfigurationFile.self, from: data)
} catch {
let error = GenerateError.invalidConfigurationFile(
configurationFile,
error.localizedDescription.description
)
print(error.description) print(error.description)
Generate.exit(withError: error) Generate.exit(withError: error)
} }
return configuration
} }
} }

View File

@ -14,7 +14,7 @@ extension StringsConfiguration: Runnable {
if force { if force {
args += ["-f"] args += ["-f"]
} }
args += [ args += [
inputFile.prependIfRelativePath(projectDirectory), inputFile.prependIfRelativePath(projectDirectory),
"--output-path", "--output-path",
@ -26,7 +26,9 @@ extension StringsConfiguration: Runnable {
"--extension-output-path", "--extension-output-path",
extensionOutputPath.prependIfRelativePath(projectDirectory), extensionOutputPath.prependIfRelativePath(projectDirectory),
"--static-members", "--static-members",
"\(staticMembersOptions)" "\(staticMembersOptions)",
"--xc-strings",
"\(xcStringsOptions)"
] ]
if let extensionName = extensionName { if let extensionName = extensionName {

View File

@ -8,10 +8,13 @@
import Foundation import Foundation
import ToolCore import ToolCore
enum OutputImageExtension: String {
case png
case svg
}
class XcassetsGenerator { class XcassetsGenerator {
static let outputImageExtension = "png"
let forceGeneration: Bool let forceGeneration: Bool
// MARK: - Init // MARK: - Init
@ -60,13 +63,20 @@ class XcassetsGenerator {
generatedAssetsPaths.append(imagesetName) generatedAssetsPaths.append(imagesetName)
// Generate output images path // Generate output images path
let output1x = "\(imagesetPath)/\(parsedImage.name).\(XcassetsGenerator.outputImageExtension)" let output1x = "\(imagesetPath)/\(parsedImage.name).\(OutputImageExtension.png.rawValue)"
let output2x = "\(imagesetPath)/\(parsedImage.name)@2x.\(XcassetsGenerator.outputImageExtension)" let output2x = "\(imagesetPath)/\(parsedImage.name)@2x.\(OutputImageExtension.png.rawValue)"
let output3x = "\(imagesetPath)/\(parsedImage.name)@3x.\(XcassetsGenerator.outputImageExtension)" let output3x = "\(imagesetPath)/\(parsedImage.name)@3x.\(OutputImageExtension.png.rawValue)"
// Check if we need to convert image // Check if we need to convert image
guard self.shouldGenerate(inputImagePath: imageData.path, xcassetImagePath: output1x) else {
//print("\(parsedImage.name) -> Not regenerating") var needToGenerateForSvg = false
if imageData.ext == "svg" && !parsedImage.imageExtensions.contains(.png) {
needToGenerateForSvg = true
}
guard self.shouldGenerate(inputImagePath: imageData.path, xcassetImagePath: output1x, needToGenerateForSvg: needToGenerateForSvg) else {
print("\(parsedImage.name) -> Not regenerating")
return return
} }
@ -80,29 +90,55 @@ class XcassetsGenerator {
print(error.description) print(error.description)
Images.exit(withError: error) Images.exit(withError: error)
} }
} else {
do {
let documentsDirectory = try fileManager.contentsOfDirectory(atPath: imagesetPath)
for filePath in documentsDirectory {
try fileManager.removeItem(atPath: "\(imagesetPath)/\(filePath)")
}
} catch {
print("Error deleting previous assets")
}
} }
// Convert image
let convertArguments = parsedImage.convertArguments let convertArguments = parsedImage.convertArguments
if imageData.ext == "svg" { if imageData.ext == "svg" {
// /usr/local/bin/rsvg-convert path/to/image.png -w 200 -h 300 -o path/to/output.png if parsedImage.imageExtensions.contains(.png) {
// /usr/local/bin/rsvg-convert path/to/image.png -w 200 -o path/to/output.png
// /usr/local/bin/rsvg-convert path/to/image.png -h 300 -o path/to/output.png // /usr/local/bin/rsvg-convert path/to/image.png -w 200 -h 300 -o path/to/output.png
var command1x = ["\(svgConverter)", "\(imageData.path)"] // /usr/local/bin/rsvg-convert path/to/image.png -w 200 -o path/to/output.png
var command2x = ["\(svgConverter)", "\(imageData.path)"] // /usr/local/bin/rsvg-convert path/to/image.png -h 300 -o path/to/output.png
var command3x = ["\(svgConverter)", "\(imageData.path)"] var command1x = ["\(svgConverter)", "\(imageData.path)"]
var command2x = ["\(svgConverter)", "\(imageData.path)"]
self.addConvertArgument(command: &command1x, convertArgument: convertArguments.x1) var command3x = ["\(svgConverter)", "\(imageData.path)"]
self.addConvertArgument(command: &command2x, convertArgument: convertArguments.x2)
self.addConvertArgument(command: &command3x, convertArgument: convertArguments.x3) self.addConvertArgument(command: &command1x, convertArgument: convertArguments.x1)
self.addConvertArgument(command: &command2x, convertArgument: convertArguments.x2)
command1x.append(contentsOf: ["-o", output1x]) self.addConvertArgument(command: &command3x, convertArgument: convertArguments.x3)
command2x.append(contentsOf: ["-o", output2x])
command3x.append(contentsOf: ["-o", output3x]) command1x.append(contentsOf: ["-o", output1x])
command2x.append(contentsOf: ["-o", output2x])
Shell.shell(command1x) command3x.append(contentsOf: ["-o", output3x])
Shell.shell(command2x)
Shell.shell(command3x) Shell.shell(command1x)
Shell.shell(command2x)
Shell.shell(command3x)
} else {
let output = "\(imagesetPath)/\(parsedImage.name).\(OutputImageExtension.svg.rawValue)"
let tempURL = URL(fileURLWithPath: output)
do {
if FileManager.default.fileExists(atPath: tempURL.path) {
try FileManager.default.removeItem(atPath: tempURL.path)
}
try FileManager.default.copyItem(atPath: imageData.path, toPath: tempURL.path)
} catch {
print(error.localizedDescription)
}
}
} else { } else {
// convert path/to/image.png -resize 200x300 path/to/output.png // convert path/to/image.png -resize 200x300 path/to/output.png
// convert path/to/image.png -resize 200x path/to/output.png // convert path/to/image.png -resize 200x path/to/output.png
@ -119,7 +155,7 @@ class XcassetsGenerator {
} }
// Write Content.json // Write Content.json
let imagesetContentJson = parsedImage.contentJson guard let imagesetContentJson = parsedImage.generateContentJson(isVector: imageData.ext == "svg") else { return }
let contentJsonFilePath = "\(imagesetPath)/Contents.json" let contentJsonFilePath = "\(imagesetPath)/Contents.json"
let contentJsonFilePathURL = URL(fileURLWithPath: contentJsonFilePath) let contentJsonFilePathURL = URL(fileURLWithPath: contentJsonFilePath)
@ -161,8 +197,8 @@ class XcassetsGenerator {
// MARK: - Helpers: bypass generation // MARK: - Helpers: bypass generation
private func shouldGenerate(inputImagePath: String, xcassetImagePath: String) -> Bool { private func shouldGenerate(inputImagePath: String, xcassetImagePath: String, needToGenerateForSvg: Bool) -> Bool {
if forceGeneration { if forceGeneration || needToGenerateForSvg {
return true return true
} }

View File

@ -0,0 +1,76 @@
//
// ImageContent.swift
//
//
// Created by Quentin Bandera on 19/04/2024.
//
import Foundation
enum TemplateRenderingIntent: String, Codable {
case template
case original
}
struct AssetContent: Codable, Equatable {
let images: [AssetImageDescription]
let info: AssetInfo
let properties: AssetProperties?
init(
images: [AssetImageDescription],
info: AssetInfo,
properties: AssetProperties? = nil
) {
self.images = images
self.info = info
self.properties = properties
}
static func == (lhs: AssetContent, rhs: AssetContent) -> Bool {
guard lhs.images.count == rhs.images.count else { return false }
let lhsImages = lhs.images.sorted(by: { $0.filename < $1.filename })
let rhsImages = rhs.images.sorted(by: { $0.filename < $1.filename })
return lhsImages == rhsImages
}
}
struct AssetImageDescription: Codable, Equatable {
let idiom: String
let scale: String?
let filename: String
init(
idiom: String,
scale: String? = nil,
filename: String
) {
self.idiom = idiom
self.scale = scale
self.filename = filename
}
}
struct AssetInfo: Codable, Equatable {
let version: Int
let author: String
}
struct AssetProperties: Codable, Equatable {
let preservesVectorRepresentation: Bool
let templateRenderingIntent: TemplateRenderingIntent?
init(
preservesVectorRepresentation: Bool,
templateRenderingIntent: TemplateRenderingIntent? = nil
) {
self.preservesVectorRepresentation = preservesVectorRepresentation
self.templateRenderingIntent = templateRenderingIntent
}
enum CodingKeys: String, CodingKey {
case preservesVectorRepresentation = "preserves-vector-representation"
case templateRenderingIntent = "template-rendering-intent"
}
}

View File

@ -7,12 +7,31 @@
import Foundation import Foundation
enum ImageExtension: String {
case png
}
struct ParsedImage { struct ParsedImage {
let name: String let name: String
let tags: String let tags: String
let width: Int let width: Int
let height: Int let height: Int
let imageExtensions: [ImageExtension]
init(
name: String,
tags: String,
width: Int,
height: Int,
imageExtensions: [ImageExtension] = []
) {
self.name = name
self.tags = tags
self.width = width
self.height = height
self.imageExtensions = imageExtensions
}
// MARK: - Convert // MARK: - Convert
var convertArguments: (x1: ConvertArgument, x2: ConvertArgument, x3: ConvertArgument) { var convertArguments: (x1: ConvertArgument, x2: ConvertArgument, x3: ConvertArgument) {
@ -42,34 +61,69 @@ struct ParsedImage {
// MARK: - Assets // MARK: - Assets
var contentJson: String { func generateContentJson(isVector: Bool) -> String? {
""" let encoder = JSONEncoder()
{ encoder.outputFormatting = .prettyPrinted
"images" : [
{ let imageContent = generateImageContent(isVector: isVector)
"idiom" : "universal",
"scale" : "1x", guard let data = try? encoder.encode(imageContent) else {
"filename" : "\(name).\(XcassetsGenerator.outputImageExtension)" let error = ImagesError.writeFile("Contents.json", "Error encoding json file")
}, print(error.description)
{ Images.exit(withError: error)
"idiom" : "universal",
"scale" : "2x",
"filename" : "\(name)@2x.\(XcassetsGenerator.outputImageExtension)"
},
{
"idiom" : "universal",
"scale" : "3x",
"filename" : "\(name)@3x.\(XcassetsGenerator.outputImageExtension)"
}
],
"info" : {
"version" : 1,
"author" : "ResgenSwift-Imagium"
}
} }
"""
return String(data: data, encoding: .utf8)
} }
func generateImageContent(isVector: Bool) -> AssetContent {
if !imageExtensions.contains(.png) && isVector {
//Generate svg
return AssetContent(
images: [
AssetImageDescription(
idiom: "universal",
filename: "\(name).\(OutputImageExtension.svg.rawValue)"
)
],
info: AssetInfo(
version: 1,
author: "ResgenSwift-Imagium"
),
properties: AssetProperties(
preservesVectorRepresentation: true,
templateRenderingIntent: .original
)
)
} else {
//Generate png
return AssetContent(
images: [
AssetImageDescription(
idiom: "universal",
scale: "1x",
filename: "\(name).\(OutputImageExtension.png.rawValue)"
),
AssetImageDescription(
idiom: "universal",
scale: "2x",
filename: "\(name)@2x.\(OutputImageExtension.png.rawValue)"
),
AssetImageDescription(
idiom: "universal",
scale: "3x",
filename: "\(name)@3x.\(OutputImageExtension.png.rawValue)"
)
],
info: AssetInfo(
version: 1,
author: "ResgenSwift-Imagium"
)
)
}
}
// MARK: - Extension property // MARK: - Extension property
func getImageProperty(isStatic: Bool, isSwiftUI: Bool) -> String { func getImageProperty(isStatic: Bool, isSwiftUI: Bool) -> String {

View File

@ -38,11 +38,21 @@ class ImageFileParser {
} }
return Int(splittedLine[3])! return Int(splittedLine[3])!
}() }()
let image = ParsedImage(name: String(splittedLine[1]), tags: String(splittedLine[0]), width: width, height: height) var imageExtensions: [ImageExtension] = []
splittedLine.forEach { stringExtension in
if let imageExtension = ImageExtension(rawValue: String(stringExtension)) {
imageExtensions.append(imageExtension)
}
}
let image = ParsedImage(name: String(splittedLine[1]), tags: String(splittedLine[0]), width: width, height: height, imageExtensions: imageExtensions)
imagesToGenerate.append(image) imagesToGenerate.append(image)
} }
print(imagesToGenerate)
return imagesToGenerate.filter { return imagesToGenerate.filter {
$0.tags.contains(platform.rawValue) $0.tags.contains(platform.rawValue)
} }

View File

@ -1,6 +1,6 @@
// //
// StringsFileGenerator.swift // StringsFileGenerator.swift
// //
// //
// Created by Thibaut Schmitt on 04/01/2022. // Created by Thibaut Schmitt on 04/01/2022.
// //
@ -9,15 +9,16 @@ import Foundation
import ToolCore import ToolCore
class StringsFileGenerator { class StringsFileGenerator {
// MARK: - Strings Files // MARK: - Strings Files
static func writeStringsFiles(sections: [Section], static func writeStringsFiles(sections: [Section],
langs: [String], langs: [String],
defaultLang: String, defaultLang: String,
tags: [String], tags: [String],
outputPath: String, outputPath: String,
inputFilenameWithoutExt: String) { inputFilenameWithoutExt: String) {
var stringsFilesContent = [String: String]() var stringsFilesContent = [String: String]()
for lang in langs { for lang in langs {
stringsFilesContent[lang] = Self.generateStringsFileContent(lang: lang, stringsFilesContent[lang] = Self.generateStringsFileContent(lang: lang,
@ -25,11 +26,11 @@ class StringsFileGenerator {
tags: tags, tags: tags,
sections: sections) sections: sections)
} }
// Write strings file content // Write strings file content
langs.forEach { lang in langs.forEach { lang in
guard let fileContent = stringsFilesContent[lang] else { return } guard let fileContent = stringsFilesContent[lang] else { return }
let stringsFilePath = "\(outputPath)/\(lang).lproj/\(inputFilenameWithoutExt).strings" let stringsFilePath = "\(outputPath)/\(lang).lproj/\(inputFilenameWithoutExt).strings"
let stringsFilePathURL = URL(fileURLWithPath: stringsFilePath) let stringsFilePathURL = URL(fileURLWithPath: stringsFilePath)
do { do {
@ -41,7 +42,32 @@ class StringsFileGenerator {
} }
} }
} }
static func writeXcStringsFiles(sections: [Section],
langs: [String],
defaultLang: String,
tags: [String],
outputPath: String,
inputFilenameWithoutExt: String) {
let fileContent: String = Self.generateXcStringsFileContent(
langs: langs,
defaultLang: defaultLang,
tags: tags,
sections: sections
)
let stringsFilePath = "\(outputPath)/\(inputFilenameWithoutExt).xcstrings"
let stringsFilePathURL = URL(fileURLWithPath: stringsFilePath)
do {
try fileContent.write(to: stringsFilePathURL, atomically: false, encoding: .utf8)
} catch let error {
let error = StringiumError.writeFile(error.localizedDescription, stringsFilePath)
print(error.description)
Stringium.exit(withError: error)
}
}
static func generateStringsFileContent(lang: String, static func generateStringsFileContent(lang: String,
defaultLang: String, defaultLang: String,
tags inputTags: [String], tags inputTags: [String],
@ -53,13 +79,13 @@ class StringsFileGenerator {
* Language: \(lang) * Language: \(lang)
*/\n */\n
""" """
sections.forEach { section in sections.forEach { section in
// Check that at least one string will be generated // Check that at least one string will be generated
guard section.hasOneOrMoreMatchingTags(tags: inputTags) else { guard section.hasOneOrMoreMatchingTags(tags: inputTags) else {
return // Go to next section return // Go to next section
} }
stringsFileContent += "\n/********** \(section.name) **********/\n\n" stringsFileContent += "\n/********** \(section.name) **********/\n\n"
section.definitions.forEach { definition in section.definitions.forEach { definition in
var skipDefinition = false // Set to true if not matching tag var skipDefinition = false // Set to true if not matching tag
@ -69,16 +95,16 @@ class StringsFileGenerator {
skipDefinition = true skipDefinition = true
return nil return nil
} }
// If tags contains `noTranslationTag` => get default lang // If tags contains `noTranslationTag` => get default lang
if definition.tags.contains(Stringium.noTranslationTag) { if definition.tags.contains(Stringium.noTranslationTag) {
return definition.translations[defaultLang] return definition.translations[defaultLang]
} }
// Else: get specific lang // Else: get specific lang
return definition.translations[lang] return definition.translations[lang]
}() }()
if let translation = translationOpt { if let translation = translationOpt {
stringsFileContent += "\"\(definition.name)\" = \"\(translation)\";\n\n" stringsFileContent += "\"\(definition.name)\" = \"\(translation)\";\n\n"
} else if skipDefinition == false { } else if skipDefinition == false {
@ -88,12 +114,123 @@ class StringsFileGenerator {
} }
} }
} }
return stringsFileContent return stringsFileContent
} }
// MARK: - XcStrings Generation
static func generateXcStringsFileContent(langs: [String],
defaultLang: String,
tags inputTags: [String],
sections: [Section]) -> String {
let rootObject = generateRootObject(langs: langs, defaultLang: defaultLang, tags: inputTags, sections: sections)
let file = generateXcStringsFileContentFromRootObject(rootObject: rootObject)
return file
}
static func generateXcStringsFileContentFromRootObject(rootObject: Root) -> String {
do {
let encoder = JSONEncoder()
encoder.outputFormatting = [.prettyPrinted]
let json = try encoder.encode(rootObject)
if let jsonString = String(data: json, encoding: .utf8) {
return jsonString
}
} catch {
debugPrint("Failed to encode: \(error)")
}
return ""
}
static func generateRootObject(langs: [String],
defaultLang: String,
tags inputTags: [String],
sections: [Section]) -> Root {
var xcStringDefinitionTab: [XCStringDefinition] = []
sections.forEach { section in
// Check that at least one string will be generated
guard section.hasOneOrMoreMatchingTags(tags: inputTags) else {
return // Go to next section
}
section.definitions.forEach { definition in
var skipDefinition = false
var isNoTranslation = false
var localizationTab: [XCStringLocalization] = []
if definition.hasOneOrMoreMatchingTags(inputTags: inputTags) == false {
skipDefinition = true
}
if definition.tags.contains(Stringium.noTranslationTag) {
isNoTranslation = true
}
if !skipDefinition {
if isNoTranslation {
// Search for langs in yaml
for lang in langs {
if let value = definition.translations[defaultLang], !value.isEmpty {
let localization = XCStringLocalization(
lang: lang,
content: XCStringLocalizationLangContent(
stringUnit: DefaultStringUnit(state: "translated", value: value)
)
)
localizationTab.append(localization)
}
}
} else {
// Search for langs in twine
for (lang, value) in definition.translations where !value.isEmpty {
let localization = XCStringLocalization(
lang: lang,
content: XCStringLocalizationLangContent(
stringUnit: DefaultStringUnit(state: "translated", value: value)
)
)
localizationTab.append(localization)
}
}
let xcStringDefinition = XCStringDefinition(
title: definition.name,
content: XCStringDefinitionContent(
comment: definition.comment,
extractionState: "manual",
localizations: XCStringLocalizationContainer(
localizations: localizationTab
)
)
)
xcStringDefinitionTab.append(xcStringDefinition)
}
}
}
let xcStringContainer = XCStringDefinitionContainer(strings: xcStringDefinitionTab)
return Root(
sourceLanguage: defaultLang,
strings: xcStringContainer,
version: "1.0"
)
}
// MARK: - Extension file // MARK: - Extension file
static func writeExtensionFiles(sections: [Section], static func writeExtensionFiles(sections: [Section],
defaultLang lang: String, defaultLang lang: String,
tags: [String], tags: [String],
@ -110,7 +247,7 @@ class StringsFileGenerator {
inputFilename: inputFilename, inputFilename: inputFilename,
extensionName: extensionName, extensionName: extensionName,
extensionSuffix: extensionSuffix) extensionSuffix: extensionSuffix)
// Write content // Write content
let extensionFilePathURL = URL(fileURLWithPath: extensionFilePath) let extensionFilePathURL = URL(fileURLWithPath: extensionFilePath)
do { do {
@ -121,9 +258,9 @@ class StringsFileGenerator {
Stringium.exit(withError: error) Stringium.exit(withError: error)
} }
} }
// MARK: - Extension content // MARK: - Extension content
static func getExtensionContent(sections: [Section], static func getExtensionContent(sections: [Section],
defaultLang lang: String, defaultLang lang: String,
tags: [String], tags: [String],
@ -139,31 +276,31 @@ class StringsFileGenerator {
] ]
.joined(separator: "\n") .joined(separator: "\n")
} }
// MARK: - Extension part // MARK: - Extension part
private static func getHeader(stringsFilename: String, extensionClassname: String) -> String { private static func getHeader(stringsFilename: String, extensionClassname: String) -> String {
""" """
// Generated by ResgenSwift.Strings.\(Stringium.toolName) \(ResgenSwiftVersion) // Generated by ResgenSwift.Strings.\(Stringium.toolName) \(ResgenSwiftVersion)
import UIKit import UIKit
fileprivate let kStringsFileName = "\(stringsFilename)" fileprivate let kStringsFileName = "\(stringsFilename)"
extension \(extensionClassname) { extension \(extensionClassname) {
""" """
} }
private static func getEnumKey(sections: [Section], tags: [String], extensionClassname: String, extensionSuffix: String) -> String { private static func getEnumKey(sections: [Section], tags: [String], extensionClassname: String, extensionSuffix: String) -> String {
var enumDefinition = "\n enum Key\(extensionSuffix.uppercasedFirst()): String {\n" var enumDefinition = "\n enum Key\(extensionSuffix.uppercasedFirst()): String {\n"
// Enum // Enum
sections.forEach { section in sections.forEach { section in
// Check that at least one string will be generated // Check that at least one string will be generated
guard section.hasOneOrMoreMatchingTags(tags: tags) else { guard section.hasOneOrMoreMatchingTags(tags: tags) else {
return // Go to next section return // Go to next section
} }
section.definitions.forEach { definition in section.definitions.forEach { definition in
guard definition.hasOneOrMoreMatchingTags(inputTags: tags) == true else { guard definition.hasOneOrMoreMatchingTags(inputTags: tags) == true else {
return // Go to next definition return // Go to next definition
@ -172,7 +309,7 @@ class StringsFileGenerator {
enumDefinition += " case \(definition.name) = \"\(definition.name)\"\n" enumDefinition += " case \(definition.name) = \"\(definition.name)\"\n"
} }
} }
// KeyPath accessors // KeyPath accessors
enumDefinition += "\n" enumDefinition += "\n"
enumDefinition += " var keyPath: KeyPath<\(extensionClassname), String> {\n" enumDefinition += " var keyPath: KeyPath<\(extensionClassname), String> {\n"
@ -182,7 +319,7 @@ class StringsFileGenerator {
guard section.hasOneOrMoreMatchingTags(tags: tags) else { guard section.hasOneOrMoreMatchingTags(tags: tags) else {
return // Go to next section return // Go to next section
} }
section.definitions.forEach { definition in section.definitions.forEach { definition in
guard definition.hasOneOrMoreMatchingTags(inputTags: tags) == true else { guard definition.hasOneOrMoreMatchingTags(inputTags: tags) == true else {
return // Go to next definition return // Go to next definition
@ -194,23 +331,23 @@ class StringsFileGenerator {
enumDefinition += " }\n" // Switch enumDefinition += " }\n" // Switch
enumDefinition += " }\n" // var keyPath enumDefinition += " }\n" // var keyPath
enumDefinition += " }" // Enum enumDefinition += " }" // Enum
return enumDefinition return enumDefinition
} }
private static func getProperties(sections: [Section], defaultLang lang: String, tags: [String], staticVar: Bool) -> String { private static func getProperties(sections: [Section], defaultLang lang: String, tags: [String], staticVar: Bool) -> String {
sections.compactMap { section in sections.compactMap { section in
// Check that at least one string will be generated // Check that at least one string will be generated
guard section.hasOneOrMoreMatchingTags(tags: tags) else { guard section.hasOneOrMoreMatchingTags(tags: tags) else {
return nil // Go to next section return nil // Go to next section
} }
var res = "\n // MARK: - \(section.name)\n" var res = "\n // MARK: - \(section.name)\n"
res += section.definitions.compactMap { definition in res += section.definitions.compactMap { definition in
guard definition.hasOneOrMoreMatchingTags(inputTags: tags) == true else { guard definition.hasOneOrMoreMatchingTags(inputTags: tags) == true else {
return nil // Go to next definition return nil // Go to next definition
} }
if staticVar { if staticVar {
return "\n\(definition.getNSLocalizedStringStaticProperty(forLang: lang))" return "\n\(definition.getNSLocalizedStringStaticProperty(forLang: lang))"
} }
@ -221,11 +358,11 @@ class StringsFileGenerator {
} }
.joined(separator: "\n") .joined(separator: "\n")
} }
private static func getFooter() -> String { private static func getFooter() -> String {
""" """
} }
""" """
} }
} }

View File

@ -84,22 +84,29 @@ class Definition {
return (inputParameters: inputParameters, translationArguments: translationArguments) return (inputParameters: inputParameters, translationArguments: translationArguments)
} }
private func getBaseProperty(lang: String, translation: String, isStatic: Bool) -> String { private func getBaseProperty(lang: String, translation: String, isStatic: Bool, comment: String?) -> String {
""" """
/// Translation in \(lang) : /// Translation in \(lang) :
/// \(translation) /// \(translation)
///
/// Comment :
/// \(comment?.isEmpty == false ? comment! : "No comment")
\(isStatic ? "static ": "")var \(name): String { \(isStatic ? "static ": "")var \(name): String {
NSLocalizedString("\(name)", tableName: kStringsFileName, bundle: Bundle.main, value: "\(translation)", comment: "") NSLocalizedString("\(name)", tableName: kStringsFileName, bundle: Bundle.main, value: "\(translation)", comment: "\(comment ?? "")")
} }
""" """
} }
private func getBaseMethod(lang: String, translation: String, isStatic: Bool, inputParameters: [String], translationArguments: [String]) -> String { private func getBaseMethod(lang: String, translation: String, isStatic: Bool, inputParameters: [String], translationArguments: [String], comment: String?) -> String {
""" """
/// Translation in \(lang) : /// Translation in \(lang) :
/// \(translation) /// \(translation)
///
/// Comment :
/// \(comment?.isEmpty == false ? comment! : "No comment")
\(isStatic ? "static ": "")func \(name)(\(inputParameters.joined(separator: ", "))) -> String { \(isStatic ? "static ": "")func \(name)(\(inputParameters.joined(separator: ", "))) -> String {
String(format: \(isStatic ? "Self" : "self").\(name), \(translationArguments.joined(separator: ", "))) String(format: \(isStatic ? "Self" : "self").\(name), \(translationArguments.joined(separator: ", ")))
} }
@ -114,8 +121,13 @@ class Definition {
} }
// Generate property // Generate property
let property = getBaseProperty(lang: lang, translation: translation, isStatic: false) let property = getBaseProperty(
lang: lang,
translation: translation,
isStatic: false,
comment: self.comment
)
// Generate method // Generate method
var method = "" var method = ""
if let parameters = self.getStringParameters(input: translation) { if let parameters = self.getStringParameters(input: translation) {
@ -123,7 +135,8 @@ class Definition {
translation: translation, translation: translation,
isStatic: false, isStatic: false,
inputParameters: parameters.inputParameters, inputParameters: parameters.inputParameters,
translationArguments: parameters.translationArguments) translationArguments: parameters.translationArguments,
comment: self.comment)
} }
return property + method return property + method
@ -137,7 +150,12 @@ class Definition {
} }
// Generate property // Generate property
let property = getBaseProperty(lang: lang, translation: translation, isStatic: true) let property = getBaseProperty(
lang: lang,
translation: translation,
isStatic: true,
comment: self.comment
)
// Generate method // Generate method
var method = "" var method = ""
@ -146,7 +164,8 @@ class Definition {
translation: translation, translation: translation,
isStatic: true, isStatic: true,
inputParameters: parameters.inputParameters, inputParameters: parameters.inputParameters,
translationArguments: parameters.translationArguments) translationArguments: parameters.translationArguments,
comment: self.comment)
} }
return property + method return property + method
@ -160,10 +179,14 @@ class Definition {
print(error.description) print(error.description)
Stringium.exit(withError: error) Stringium.exit(withError: error)
} }
return """ return """
/// Translation in \(lang) : /// Translation in \(lang) :
/// \(translation) /// \(translation)
///
/// Comment :
/// \(comment?.isEmpty == false ? comment! : "No comment")
var \(name): String { var \(name): String {
"\(translation)" "\(translation)"
} }
@ -180,6 +203,9 @@ class Definition {
return """ return """
/// Translation in \(lang) : /// Translation in \(lang) :
/// \(translation) /// \(translation)
///
/// Comment :
/// \(comment?.isEmpty == false ? comment! : "No comment")
static var \(name): String { static var \(name): String {
"\(translation)" "\(translation)"
} }

View File

@ -0,0 +1,109 @@
//
// XcString.swift
//
//
// Created by Quentin Bandera on 12/04/2024.
//
import SwiftUI
struct DynamicKey: CodingKey {
var intValue: Int?
init?(intValue: Int) {
self.intValue = intValue
self.stringValue = "\(intValue)"
}
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
}
struct Root: Codable, Equatable {
let sourceLanguage: String
let strings: XCStringDefinitionContainer
let version: String
}
struct XCStringDefinitionContainer: Codable, Equatable {
let strings: [XCStringDefinition]
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: DynamicKey.self)
for str in strings {
if let codingKey = DynamicKey(stringValue: str.title) {
try container.encode(str.content, forKey: codingKey)
}
}
}
static func == (lhs: XCStringDefinitionContainer, rhs: XCStringDefinitionContainer) -> Bool {
return lhs.strings.sorted(by: {
$0.title < $1.title
}) == rhs.strings.sorted(by: { $0.title < $1.title })
}
}
struct XCStringDefinition: Codable, Equatable {
let title: String // json key -> custom encoding methods
let content: XCStringDefinitionContent
}
struct XCStringDefinitionContent: Codable, Equatable {
let comment: String?
let extractionState: String
var localizations: XCStringLocalizationContainer
init(comment: String? = nil, extractionState: String, localizations: XCStringLocalizationContainer) {
self.comment = comment
self.extractionState = extractionState
self.localizations = localizations
}
}
struct XCStringLocalizationContainer: Codable, Equatable {
let localizations: [XCStringLocalization]
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: DynamicKey.self)
for loca in localizations {
if let codingKey = DynamicKey(stringValue: loca.lang) {
try container.encode(loca.content, forKey: codingKey)
}
}
}
static func == (lhs: XCStringLocalizationContainer, rhs: XCStringLocalizationContainer) -> Bool {
return lhs.localizations.count == rhs.localizations.count && lhs.localizations.sorted(by: { $0.lang < $1.lang }) == rhs.localizations.sorted(by: { $0.lang < $1.lang })
}
}
struct XCStringLocalization: Codable, Equatable {
let lang: String // json key -> custom encoding method
let content: XCStringLocalizationLangContent
}
struct XCStringLocalizationLangContent: Codable, Equatable {
let stringUnit: DefaultStringUnit
}
//enum VarationOrStringUnit: Encodable {
// case variations([Varation])
// case stringUnit: (DefaultStringUnit)
//
// func encode(to encoder: any Encoder) throws {
// if let varations {
//
// } else if let {
//
// }
// }
//}
struct DefaultStringUnit: Codable, Equatable {
let state: String
let value: String
}

View File

@ -43,13 +43,26 @@ struct Stringium: ParsableCommand {
let sections = TwineFileParser.parse(options.inputFile) let sections = TwineFileParser.parse(options.inputFile)
// Generate strings files // Generate strings files
StringsFileGenerator.writeStringsFiles(sections: sections, print(options.xcStrings)
langs: options.langs, if !options.xcStrings {
defaultLang: options.defaultLang, print("[\(Self.toolName)] Will generate strings")
tags: options.tags,
outputPath: options.stringsFileOutputPath, StringsFileGenerator.writeStringsFiles(sections: sections,
inputFilenameWithoutExt: options.inputFilenameWithoutExt) langs: options.langs,
defaultLang: options.defaultLang,
tags: options.tags,
outputPath: options.stringsFileOutputPath,
inputFilenameWithoutExt: options.inputFilenameWithoutExt)
} else {
print("[\(Self.toolName)] Will generate xcStrings")
StringsFileGenerator.writeXcStringsFiles(sections: sections,
langs: options.langs,
defaultLang: options.defaultLang,
tags: options.tags,
outputPath: options.stringsFileOutputPath,
inputFilenameWithoutExt: options.inputFilenameWithoutExt)
}
// Generate extension // Generate extension
StringsFileGenerator.writeExtensionFiles(sections: sections, StringsFileGenerator.writeExtensionFiles(sections: sections,
defaultLang: options.defaultLang, defaultLang: options.defaultLang,
@ -59,7 +72,7 @@ struct Stringium: ParsableCommand {
extensionName: options.extensionName, extensionName: options.extensionName,
extensionFilePath: options.extensionFilePath, extensionFilePath: options.extensionFilePath,
extensionSuffix: options.extensionSuffix) extensionSuffix: options.extensionSuffix)
print("[\(Self.toolName)] Strings generated") print("[\(Self.toolName)] Strings generated")
} }

View File

@ -11,8 +11,8 @@ import ArgumentParser
struct StringiumOptions: ParsableArguments { struct StringiumOptions: ParsableArguments {
@Flag(name: [.customShort("f"), .customShort("F")], help: "Should force generation") @Flag(name: [.customShort("f"), .customShort("F")], help: "Should force generation")
var forceGeneration = false var forceGeneration = false
@Argument(help: "Input files where strings ared defined.", transform: { $0.replaceTiltWithHomeDirectoryPath() }) @Argument(help: "Input files where strings are defined.", transform: { $0.replaceTiltWithHomeDirectoryPath() })
var inputFile: String var inputFile: String
@Option(name: .customLong("output-path"), help: "Path where to strings file.", transform: { $0.replaceTiltWithHomeDirectoryPath() }) @Option(name: .customLong("output-path"), help: "Path where to strings file.", transform: { $0.replaceTiltWithHomeDirectoryPath() })
@ -32,7 +32,10 @@ struct StringiumOptions: ParsableArguments {
@Option(help: "Tell if it will generate static properties or not") @Option(help: "Tell if it will generate static properties or not")
var staticMembers: Bool = false var staticMembers: Bool = false
@Option(help: "Tell if it will generate xcStrings file or not")
var xcStrings: Bool = false
@Option(help: "Extension name. If not specified, it will generate an String extension.") @Option(help: "Extension name. If not specified, it will generate an String extension.")
var extensionName: String = Stringium.defaultExtensionName var extensionName: String = Stringium.defaultExtensionName

View File

@ -63,7 +63,7 @@ public extension String {
replacingOccurrences(of: "~", with: "\(FileManager.default.homeDirectoryForCurrentUser.relativePath)") replacingOccurrences(of: "~", with: "\(FileManager.default.homeDirectoryForCurrentUser.relativePath)")
} }
func colorComponent() -> (alpha: String, red: String, green: String, blue: String) { func colorComponent() -> (alpha: String, red: String, green: String, blue: String) {
var alpha: String = "FF" var alpha: String = "FF"
var red: String var red: String
var green: String var green: String
@ -89,4 +89,19 @@ public extension String {
func uppercasedFirst() -> String { func uppercasedFirst() -> String {
prefix(1).uppercased() + dropFirst() prefix(1).uppercased() + dropFirst()
} }
func replacingFirstOccurrence(of: String, with: String) -> Self {
if let range = self.range(of: of) {
let tmp = self.replacingOccurrences(
of: of,
with: with,
options: .literal,
range: range
)
return tmp
}
return self
}
} }

View File

@ -51,7 +51,7 @@ final class AnalyticsGeneratorTests: XCTestCase {
] ]
// When // When
AnalyticsGenerator.targets = [Analytics.TargetType.firebase] AnalyticsGenerator.targets = [TrackerType.firebase]
let extensionContent = AnalyticsGenerator.getExtensionContent(sections: [sectionOne, sectionTwo, sectionThree], let extensionContent = AnalyticsGenerator.getExtensionContent(sections: [sectionOne, sectionTwo, sectionThree],
tags: ["ios", "iosonly"], tags: ["ios", "iosonly"],
staticVar: false, staticVar: false,
@ -216,7 +216,7 @@ final class AnalyticsGeneratorTests: XCTestCase {
] ]
// When // When
AnalyticsGenerator.targets = [Analytics.TargetType.matomo] AnalyticsGenerator.targets = [TrackerType.matomo]
let extensionContent = AnalyticsGenerator.getExtensionContent(sections: [sectionOne, sectionTwo, sectionThree], let extensionContent = AnalyticsGenerator.getExtensionContent(sections: [sectionOne, sectionTwo, sectionThree],
tags: ["ios", "iosonly"], tags: ["ios", "iosonly"],
staticVar: false, staticVar: false,
@ -409,7 +409,7 @@ final class AnalyticsGeneratorTests: XCTestCase {
] ]
// When // When
AnalyticsGenerator.targets = [Analytics.TargetType.matomo, Analytics.TargetType.firebase] AnalyticsGenerator.targets = [TrackerType.matomo, TrackerType.firebase]
let extensionContent = AnalyticsGenerator.getExtensionContent(sections: [sectionOne, sectionTwo, sectionThree], let extensionContent = AnalyticsGenerator.getExtensionContent(sections: [sectionOne, sectionTwo, sectionThree],
tags: ["ios", "iosonly"], tags: ["ios", "iosonly"],
staticVar: false, staticVar: false,

View File

@ -1,5 +1,5 @@
// //
// File.swift // DiffString.swift
// //
// //
// Created by Loris Perret on 06/12/2023. // Created by Loris Perret on 06/12/2023.
@ -7,7 +7,6 @@
import Foundation import Foundation
/// Find first differing character between two strings /// Find first differing character between two strings
/// ///
/// :param: s1 First String /// :param: s1 First String

View File

@ -91,7 +91,7 @@ final class ParsedColorTests: XCTestCase {
// When // When
let contentJson = color.contentsJSON() let contentJson = color.contentsJSON()
guard let data = contentJson.data(using: .utf8), guard let data = contentJson.data(using: .utf8),
let parsedJson = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else { let parsedJson = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
XCTFail("Cannot convert `contentJSON` string to Data") XCTFail("Cannot convert `contentJSON` string to Data")
return return
} }

View File

@ -17,8 +17,8 @@ class ImageFileParserTests: XCTestCase {
# #
# SMAAS Support # SMAAS Support
# #
id image_one 25 ? id image_one 25 ? png
di image_two ? 50 di image_two ? 50 webp png
d image_three 25 ? d image_three 25 ?
d image_four 75 ? d image_four 75 ?
""" """
@ -38,13 +38,15 @@ class ImageFileParserTests: XCTestCase {
XCTAssertEqual(firstImage!.tags, "id") XCTAssertEqual(firstImage!.tags, "id")
XCTAssertEqual(firstImage!.width, 25) XCTAssertEqual(firstImage!.width, 25)
XCTAssertEqual(firstImage!.height, -1) XCTAssertEqual(firstImage!.height, -1)
XCTAssertEqual(firstImage!.imageExtensions, [.png])
let secondImage = parsedImages.first { let secondImage = parsedImages.first {
$0.name == "image_two" $0.name == "image_two"
} }
XCTAssertEqual(secondImage!.name, "image_two") XCTAssertEqual(secondImage!.name, "image_two")
XCTAssertEqual(secondImage!.tags, "di") XCTAssertEqual(secondImage!.tags, "di")
XCTAssertEqual(secondImage!.width, -1) XCTAssertEqual(secondImage!.width, -1)
XCTAssertEqual(secondImage!.height, 50) XCTAssertEqual(secondImage!.height, 50)
XCTAssertEqual(firstImage!.imageExtensions, [.png])
} }
} }

View File

@ -127,35 +127,77 @@ final class ParsedImageTests: XCTestCase {
height: 10) height: 10)
// When // When
let property = parsedImage.contentJson let property = parsedImage.generateImageContent(isVector: false)
// Expect // Expect
let expect = """ let expect = AssetContent(
{ images: [
"images" : [ AssetImageDescription(
{ idiom: "universal",
"idiom" : "universal", scale: "1x",
"scale" : "1x", filename: "\(parsedImage.name).\(OutputImageExtension.png.rawValue)"
"filename" : "\(imageName).\(XcassetsGenerator.outputImageExtension)" ),
}, AssetImageDescription(
{ idiom: "universal",
"idiom" : "universal", scale: "2x",
"scale" : "2x", filename: "\(parsedImage.name)@2x.\(OutputImageExtension.png.rawValue)"
"filename" : "\(imageName)@2x.\(XcassetsGenerator.outputImageExtension)" ),
}, AssetImageDescription(
{ idiom: "universal",
"idiom" : "universal", scale: "3x",
"scale" : "3x", filename: "\(parsedImage.name)@3x.\(OutputImageExtension.png.rawValue)"
"filename" : "\(imageName)@3x.\(XcassetsGenerator.outputImageExtension)" )
} ],
], info: AssetInfo(
"info" : { version: 1,
"version" : 1, author: "ResgenSwift-Imagium"
"author" : "ResgenSwift-Imagium" )
} )
}
""" XCTAssertEqual(property, expect)
}
XCTAssertEqual(property.adaptForXCTest(), expect.adaptForXCTest())
func testAssetPng() {
// Given
let imageName = "the_name"
let parsedImage = ParsedImage(name: imageName,
tags: "id",
width: 10,
height: 10,
imageExtensions: [.png])
// When
let property = parsedImage.generateImageContent(isVector: false)
// Expect
let expect = AssetContent(
images: [
AssetImageDescription(
idiom: "universal",
scale: "1x",
filename: "\(parsedImage.name).\(OutputImageExtension.png.rawValue)"
),
AssetImageDescription(
idiom: "universal",
scale: "2x",
filename: "\(parsedImage.name)@2x.\(OutputImageExtension.png.rawValue)"
),
AssetImageDescription(
idiom: "universal",
scale: "3x",
filename: "\(parsedImage.name)@3x.\(OutputImageExtension.png.rawValue)"
)
],
info: AssetInfo(
version: 1,
author: "ResgenSwift-Imagium"
)
)
debugPrint(property)
debugPrint(expect)
XCTAssertEqual(property, expect)
} }
} }

View File

@ -100,24 +100,33 @@ final class DefinitionTests: XCTestCase {
let expectFr = """ let expectFr = """
/// Translation in fr : /// Translation in fr :
/// C'est la traduction francaise /// C'est la traduction francaise
///
/// Comment :
/// This is a comment
var definition_name: String { var definition_name: String {
NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "C'est la traduction francaise", comment: "") NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "C'est la traduction francaise", comment: "This is a comment")
} }
""" """
let expectEn = """ let expectEn = """
/// Translation in en : /// Translation in en :
/// This is the english translation /// This is the english translation
///
/// Comment :
/// This is a comment
var definition_name: String { var definition_name: String {
NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "This is the english translation", comment: "") NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "This is the english translation", comment: "This is a comment")
} }
""" """
let expectEnUs = """ let expectEnUs = """
/// Translation in en-us : /// Translation in en-us :
/// This is the english us translation /// This is the english us translation
///
/// Comment :
/// This is a comment
var definition_name: String { var definition_name: String {
NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "This is the english us translation", comment: "") NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "This is the english us translation", comment: "This is a comment")
} }
""" """
@ -125,7 +134,118 @@ final class DefinitionTests: XCTestCase {
XCTAssertEqual(propertyEn.adaptForXCTest(), expectEn.adaptForXCTest()) XCTAssertEqual(propertyEn.adaptForXCTest(), expectEn.adaptForXCTest())
XCTAssertEqual(propertyEnUs.adaptForXCTest(), expectEnUs.adaptForXCTest()) XCTAssertEqual(propertyEnUs.adaptForXCTest(), expectEnUs.adaptForXCTest())
} }
func testGeneratedNSLocalizedStringPropertyWithEmptyComment() {
// Given
let definition = Definition(name: "definition_name")
definition.tags = ["ios","iosonly","notranslation"]
definition.comment = ""
definition.translations = [
"fr": "C'est la traduction francaise",
"en": "This is the english translation",
"en-us": "This is the english us translation"
]
// When
let propertyFr = definition.getNSLocalizedStringProperty(forLang: "fr")
let propertyEn = definition.getNSLocalizedStringProperty(forLang: "en")
let propertyEnUs = definition.getNSLocalizedStringProperty(forLang: "en-us")
// Expect
let expectFr = """
/// Translation in fr :
/// C'est la traduction francaise
///
/// Comment :
/// No comment
var definition_name: String {
NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "C'est la traduction francaise", comment: "")
}
"""
let expectEn = """
/// Translation in en :
/// This is the english translation
///
/// Comment :
/// No comment
var definition_name: String {
NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "This is the english translation", comment: "")
}
"""
let expectEnUs = """
/// Translation in en-us :
/// This is the english us translation
///
/// Comment :
/// No comment
var definition_name: String {
NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "This is the english us translation", comment: "")
}
"""
XCTAssertEqual(propertyFr.adaptForXCTest(), expectFr.adaptForXCTest())
XCTAssertEqual(propertyEn.adaptForXCTest(), expectEn.adaptForXCTest())
XCTAssertEqual(propertyEnUs.adaptForXCTest(), expectEnUs.adaptForXCTest())
}
func testGeneratedNSLocalizedStringPropertyWithNoComment() {
// Given
let definition = Definition(name: "definition_name")
definition.tags = ["ios","iosonly","notranslation"]
definition.translations = [
"fr": "C'est la traduction francaise",
"en": "This is the english translation",
"en-us": "This is the english us translation"
]
// When
let propertyFr = definition.getNSLocalizedStringProperty(forLang: "fr")
let propertyEn = definition.getNSLocalizedStringProperty(forLang: "en")
let propertyEnUs = definition.getNSLocalizedStringProperty(forLang: "en-us")
// Expect
let expectFr = """
/// Translation in fr :
/// C'est la traduction francaise
///
/// Comment :
/// No comment
var definition_name: String {
NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "C'est la traduction francaise", comment: "")
}
"""
let expectEn = """
/// Translation in en :
/// This is the english translation
///
/// Comment :
/// No comment
var definition_name: String {
NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "This is the english translation", comment: "")
}
"""
let expectEnUs = """
/// Translation in en-us :
/// This is the english us translation
///
/// Comment :
/// No comment
var definition_name: String {
NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "This is the english us translation", comment: "")
}
"""
XCTAssertEqual(propertyFr.adaptForXCTest(), expectFr.adaptForXCTest())
XCTAssertEqual(propertyEn.adaptForXCTest(), expectEn.adaptForXCTest())
XCTAssertEqual(propertyEnUs.adaptForXCTest(), expectEnUs.adaptForXCTest())
}
// MARK: - getNSLocalizedStringStaticProperty
func testGeneratedNSLocalizedStringStaticProperty() { func testGeneratedNSLocalizedStringStaticProperty() {
// Given // Given
let definition = Definition(name: "definition_name") let definition = Definition(name: "definition_name")
@ -146,24 +266,33 @@ final class DefinitionTests: XCTestCase {
let expectFr = """ let expectFr = """
/// Translation in fr : /// Translation in fr :
/// C'est la traduction francaise /// C'est la traduction francaise
///
/// Comment :
/// This is a comment
static var definition_name: String { static var definition_name: String {
NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "C'est la traduction francaise", comment: "") NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "C'est la traduction francaise", comment: "This is a comment")
} }
""" """
let expectEn = """ let expectEn = """
/// Translation in en : /// Translation in en :
/// This is the english translation /// This is the english translation
///
/// Comment :
/// This is a comment
static var definition_name: String { static var definition_name: String {
NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "This is the english translation", comment: "") NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "This is the english translation", comment: "This is a comment")
} }
""" """
let expectEnUs = """ let expectEnUs = """
/// Translation in en-us : /// Translation in en-us :
/// This is the english us translation /// This is the english us translation
///
/// Comment :
/// This is a comment
static var definition_name: String { static var definition_name: String {
NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "This is the english us translation", comment: "") NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "This is the english us translation", comment: "This is a comment")
} }
""" """
@ -171,7 +300,116 @@ final class DefinitionTests: XCTestCase {
XCTAssertEqual(propertyEn.adaptForXCTest(), expectEn.adaptForXCTest()) XCTAssertEqual(propertyEn.adaptForXCTest(), expectEn.adaptForXCTest())
XCTAssertEqual(propertyEnUs.adaptForXCTest(), expectEnUs.adaptForXCTest()) XCTAssertEqual(propertyEnUs.adaptForXCTest(), expectEnUs.adaptForXCTest())
} }
func testGeneratedNSLocalizedStringStaticPropertyWithEmptyComment() {
// Given
let definition = Definition(name: "definition_name")
definition.tags = ["ios","iosonly","notranslation"]
definition.comment = ""
definition.translations = [
"fr": "C'est la traduction francaise",
"en": "This is the english translation",
"en-us": "This is the english us translation"
]
// When
let propertyFr = definition.getNSLocalizedStringStaticProperty(forLang: "fr")
let propertyEn = definition.getNSLocalizedStringStaticProperty(forLang: "en")
let propertyEnUs = definition.getNSLocalizedStringStaticProperty(forLang: "en-us")
// Expect
let expectFr = """
/// Translation in fr :
/// C'est la traduction francaise
///
/// Comment :
/// No comment
static var definition_name: String {
NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "C'est la traduction francaise", comment: "")
}
"""
let expectEn = """
/// Translation in en :
/// This is the english translation
///
/// Comment :
/// No comment
static var definition_name: String {
NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "This is the english translation", comment: "")
}
"""
let expectEnUs = """
/// Translation in en-us :
/// This is the english us translation
///
/// Comment :
/// No comment
static var definition_name: String {
NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "This is the english us translation", comment: "")
}
"""
XCTAssertEqual(propertyFr.adaptForXCTest(), expectFr.adaptForXCTest())
XCTAssertEqual(propertyEn.adaptForXCTest(), expectEn.adaptForXCTest())
XCTAssertEqual(propertyEnUs.adaptForXCTest(), expectEnUs.adaptForXCTest())
}
func testGeneratedNSLocalizedStringStaticPropertyWithNoComment() {
// Given
let definition = Definition(name: "definition_name")
definition.tags = ["ios","iosonly","notranslation"]
definition.translations = [
"fr": "C'est la traduction francaise",
"en": "This is the english translation",
"en-us": "This is the english us translation"
]
// When
let propertyFr = definition.getNSLocalizedStringStaticProperty(forLang: "fr")
let propertyEn = definition.getNSLocalizedStringStaticProperty(forLang: "en")
let propertyEnUs = definition.getNSLocalizedStringStaticProperty(forLang: "en-us")
// Expect
let expectFr = """
/// Translation in fr :
/// C'est la traduction francaise
///
/// Comment :
/// No comment
static var definition_name: String {
NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "C'est la traduction francaise", comment: "")
}
"""
let expectEn = """
/// Translation in en :
/// This is the english translation
///
/// Comment :
/// No comment
static var definition_name: String {
NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "This is the english translation", comment: "")
}
"""
let expectEnUs = """
/// Translation in en-us :
/// This is the english us translation
///
/// Comment :
/// No comment
static var definition_name: String {
NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "This is the english us translation", comment: "")
}
"""
XCTAssertEqual(propertyFr.adaptForXCTest(), expectFr.adaptForXCTest())
XCTAssertEqual(propertyEn.adaptForXCTest(), expectEn.adaptForXCTest())
XCTAssertEqual(propertyEnUs.adaptForXCTest(), expectEnUs.adaptForXCTest())
}
func testGeneratedNSLocalizedStringPropertyWithOneArgument() { func testGeneratedNSLocalizedStringPropertyWithOneArgument() {
// Given // Given
let definition = Definition(name: "definition_name") let definition = Definition(name: "definition_name")
@ -188,17 +426,23 @@ final class DefinitionTests: XCTestCase {
let expectFr = """ let expectFr = """
/// Translation in fr : /// Translation in fr :
/// Welcome "%@" ! /// Welcome "%@" !
///
/// Comment :
/// This is a comment
var definition_name: String { var definition_name: String {
NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "Welcome \"%@\" !", comment: "") NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "Welcome \"%@\" !", comment: "This is a comment")
} }
/// Translation in fr : /// Translation in fr :
/// Welcome "%@" ! /// Welcome "%@" !
///
/// Comment :
/// This is a comment
func definition_name(arg0: String) -> String { func definition_name(arg0: String) -> String {
String(format: self.definition_name, arg0) String(format: self.definition_name, arg0)
} }
""" """
XCTAssertEqual(propertyFr.adaptForXCTest(), expectFr.adaptForXCTest()) XCTAssertEqual(propertyFr.adaptForXCTest(), expectFr.adaptForXCTest())
} }
@ -218,12 +462,18 @@ final class DefinitionTests: XCTestCase {
let expectFr = """ let expectFr = """
/// Translation in fr : /// Translation in fr :
/// Welcome "%@" ! Your age is %d :) Your weight is %f ;-) /// Welcome "%@" ! Your age is %d :) Your weight is %f ;-)
///
/// Comment :
/// This is a comment
var definition_name: String { var definition_name: String {
NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "Welcome \"%@\" ! Your age is %d :) Your weight is %f ;-)", comment: "") NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "Welcome \"%@\" ! Your age is %d :) Your weight is %f ;-)", comment: "This is a comment")
} }
/// Translation in fr : /// Translation in fr :
/// Welcome "%@" ! Your age is %d :) Your weight is %f ;-) /// Welcome "%@" ! Your age is %d :) Your weight is %f ;-)
///
/// Comment :
/// This is a comment
func definition_name(arg0: String, arg1: Int, arg2: Double) -> String { func definition_name(arg0: String, arg1: Int, arg2: Double) -> String {
String(format: self.definition_name, arg0, arg1, arg2) String(format: self.definition_name, arg0, arg1, arg2)
} }
@ -249,12 +499,18 @@ final class DefinitionTests: XCTestCase {
let expectFr = """ let expectFr = """
/// Translation in fr : /// Translation in fr :
/// Vous %%: %1$@ %2$@ Age: %3$d /// Vous %%: %1$@ %2$@ Age: %3$d
///
/// Comment :
/// This is a comment
var definition_name: String { var definition_name: String {
NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "Vous %%: %1$@ %2$@ Age: %3$d", comment: "") NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "Vous %%: %1$@ %2$@ Age: %3$d", comment: "This is a comment")
} }
/// Translation in fr : /// Translation in fr :
/// Vous %%: %1$@ %2$@ Age: %3$d /// Vous %%: %1$@ %2$@ Age: %3$d
///
/// Comment :
/// This is a comment
func definition_name(arg0: String, arg1: String, arg2: Int) -> String { func definition_name(arg0: String, arg1: String, arg2: Int) -> String {
String(format: self.definition_name, arg0, arg1, arg2) String(format: self.definition_name, arg0, arg1, arg2)
} }
@ -263,12 +519,18 @@ final class DefinitionTests: XCTestCase {
let expectEn = """ let expectEn = """
/// Translation in en : /// Translation in en :
/// You %%: %2$@ %1$@ Age: %3$d /// You %%: %2$@ %1$@ Age: %3$d
///
/// Comment :
/// This is a comment
var definition_name: String { var definition_name: String {
NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "You %%: %2$@ %1$@ Age: %3$d", comment: "") NSLocalizedString("definition_name", tableName: kStringsFileName, bundle: Bundle.main, value: "You %%: %2$@ %1$@ Age: %3$d", comment: "This is a comment")
} }
/// Translation in en : /// Translation in en :
/// You %%: %2$@ %1$@ Age: %3$d /// You %%: %2$@ %1$@ Age: %3$d
///
/// Comment :
/// This is a comment
func definition_name(arg0: String, arg1: String, arg2: Int) -> String { func definition_name(arg0: String, arg1: String, arg2: Int) -> String {
String(format: self.definition_name, arg0, arg1, arg2) String(format: self.definition_name, arg0, arg1, arg2)
} }
@ -300,6 +562,9 @@ final class DefinitionTests: XCTestCase {
let expectFr = """ let expectFr = """
/// Translation in fr : /// Translation in fr :
/// C'est la traduction francaise /// C'est la traduction francaise
///
/// Comment :
/// This is a comment
var definition_name: String { var definition_name: String {
"C'est la traduction francaise" "C'est la traduction francaise"
} }
@ -308,6 +573,9 @@ final class DefinitionTests: XCTestCase {
let expectEn = """ let expectEn = """
/// Translation in en : /// Translation in en :
/// This is the english translation /// This is the english translation
///
/// Comment :
/// This is a comment
var definition_name: String { var definition_name: String {
"This is the english translation" "This is the english translation"
} }
@ -316,6 +584,9 @@ final class DefinitionTests: XCTestCase {
let expectEnUs = """ let expectEnUs = """
/// Translation in en-us : /// Translation in en-us :
/// This is the english us translation /// This is the english us translation
///
/// Comment :
/// This is a comment
var definition_name: String { var definition_name: String {
"This is the english us translation" "This is the english us translation"
} }
@ -325,7 +596,118 @@ final class DefinitionTests: XCTestCase {
XCTAssertEqual(propertyEn.adaptForXCTest(), expectEn.adaptForXCTest()) XCTAssertEqual(propertyEn.adaptForXCTest(), expectEn.adaptForXCTest())
XCTAssertEqual(propertyEnUs.adaptForXCTest(), expectEnUs.adaptForXCTest()) XCTAssertEqual(propertyEnUs.adaptForXCTest(), expectEnUs.adaptForXCTest())
} }
func testGeneratedRawPropertyWithEmptyComment() {
// Given
let definition = Definition(name: "definition_name")
definition.tags = ["ios","iosonly","notranslation"]
definition.comment = ""
definition.translations = [
"fr": "C'est la traduction francaise",
"en": "This is the english translation",
"en-us": "This is the english us translation"
]
// When
let propertyFr = definition.getProperty(forLang: "fr")
let propertyEn = definition.getProperty(forLang: "en")
let propertyEnUs = definition.getProperty(forLang: "en-us")
// Expect
let expectFr = """
/// Translation in fr :
/// C'est la traduction francaise
///
/// Comment :
/// No comment
var definition_name: String {
"C'est la traduction francaise"
}
"""
let expectEn = """
/// Translation in en :
/// This is the english translation
///
/// Comment :
/// No comment
var definition_name: String {
"This is the english translation"
}
"""
let expectEnUs = """
/// Translation in en-us :
/// This is the english us translation
///
/// Comment :
/// No comment
var definition_name: String {
"This is the english us translation"
}
"""
XCTAssertEqual(propertyFr.adaptForXCTest(), expectFr.adaptForXCTest())
XCTAssertEqual(propertyEn.adaptForXCTest(), expectEn.adaptForXCTest())
XCTAssertEqual(propertyEnUs.adaptForXCTest(), expectEnUs.adaptForXCTest())
}
func testGeneratedRawPropertyWithNoComment() {
// Given
let definition = Definition(name: "definition_name")
definition.tags = ["ios","iosonly","notranslation"]
definition.translations = [
"fr": "C'est la traduction francaise",
"en": "This is the english translation",
"en-us": "This is the english us translation"
]
// When
let propertyFr = definition.getProperty(forLang: "fr")
let propertyEn = definition.getProperty(forLang: "en")
let propertyEnUs = definition.getProperty(forLang: "en-us")
// Expect
let expectFr = """
/// Translation in fr :
/// C'est la traduction francaise
///
/// Comment :
/// No comment
var definition_name: String {
"C'est la traduction francaise"
}
"""
let expectEn = """
/// Translation in en :
/// This is the english translation
///
/// Comment :
/// No comment
var definition_name: String {
"This is the english translation"
}
"""
let expectEnUs = """
/// Translation in en-us :
/// This is the english us translation
///
/// Comment :
/// No comment
var definition_name: String {
"This is the english us translation"
}
"""
XCTAssertEqual(propertyFr.adaptForXCTest(), expectFr.adaptForXCTest())
XCTAssertEqual(propertyEn.adaptForXCTest(), expectEn.adaptForXCTest())
XCTAssertEqual(propertyEnUs.adaptForXCTest(), expectEnUs.adaptForXCTest())
}
// MARK: - Raw static properties
func testGeneratedRawStaticProperty() { func testGeneratedRawStaticProperty() {
// Given // Given
let definition = Definition(name: "definition_name") let definition = Definition(name: "definition_name")
@ -346,6 +728,9 @@ final class DefinitionTests: XCTestCase {
let expectFr = """ let expectFr = """
/// Translation in fr : /// Translation in fr :
/// C'est la traduction francaise /// C'est la traduction francaise
///
/// Comment :
/// This is a comment
static var definition_name: String { static var definition_name: String {
"C'est la traduction francaise" "C'est la traduction francaise"
} }
@ -354,7 +739,10 @@ final class DefinitionTests: XCTestCase {
let expectEn = """ let expectEn = """
/// Translation in en : /// Translation in en :
/// This is the english translation /// This is the english translation
static var definition_name: String { ///
/// Comment :
/// This is a comment
static var definition_name: String {
"This is the english translation" "This is the english translation"
} }
""" """
@ -362,6 +750,9 @@ final class DefinitionTests: XCTestCase {
let expectEnUs = """ let expectEnUs = """
/// Translation in en-us : /// Translation in en-us :
/// This is the english us translation /// This is the english us translation
///
/// Comment :
/// This is a comment
static var definition_name: String { static var definition_name: String {
"This is the english us translation" "This is the english us translation"
} }
@ -371,4 +762,113 @@ final class DefinitionTests: XCTestCase {
XCTAssertEqual(propertyEn.adaptForXCTest(), expectEn.adaptForXCTest()) XCTAssertEqual(propertyEn.adaptForXCTest(), expectEn.adaptForXCTest())
XCTAssertEqual(propertyEnUs.adaptForXCTest(), expectEnUs.adaptForXCTest()) XCTAssertEqual(propertyEnUs.adaptForXCTest(), expectEnUs.adaptForXCTest())
} }
func testGeneratedRawStaticPropertyWithEmptyComment() {
// Given
let definition = Definition(name: "definition_name")
definition.tags = ["ios","iosonly","notranslation"]
definition.comment = ""
definition.translations = [
"fr": "C'est la traduction francaise",
"en": "This is the english translation",
"en-us": "This is the english us translation"
]
// When
let propertyFr = definition.getStaticProperty(forLang: "fr")
let propertyEn = definition.getStaticProperty(forLang: "en")
let propertyEnUs = definition.getStaticProperty(forLang: "en-us")
// Expect
let expectFr = """
/// Translation in fr :
/// C'est la traduction francaise
///
/// Comment :
/// No comment
static var definition_name: String {
"C'est la traduction francaise"
}
"""
let expectEn = """
/// Translation in en :
/// This is the english translation
///
/// Comment :
/// No comment
static var definition_name: String {
"This is the english translation"
}
"""
let expectEnUs = """
/// Translation in en-us :
/// This is the english us translation
///
/// Comment :
/// No comment
static var definition_name: String {
"This is the english us translation"
}
"""
XCTAssertEqual(propertyFr.adaptForXCTest(), expectFr.adaptForXCTest())
XCTAssertEqual(propertyEn.adaptForXCTest(), expectEn.adaptForXCTest())
XCTAssertEqual(propertyEnUs.adaptForXCTest(), expectEnUs.adaptForXCTest())
}
func testGeneratedRawStaticPropertyWithNoComment() {
// Given
let definition = Definition(name: "definition_name")
definition.tags = ["ios","iosonly","notranslation"]
definition.translations = [
"fr": "C'est la traduction francaise",
"en": "This is the english translation",
"en-us": "This is the english us translation"
]
// When
let propertyFr = definition.getStaticProperty(forLang: "fr")
let propertyEn = definition.getStaticProperty(forLang: "en")
let propertyEnUs = definition.getStaticProperty(forLang: "en-us")
// Expect
let expectFr = """
/// Translation in fr :
/// C'est la traduction francaise
///
/// Comment :
/// No comment
static var definition_name: String {
"C'est la traduction francaise"
}
"""
let expectEn = """
/// Translation in en :
/// This is the english translation
///
/// Comment :
/// No comment
static var definition_name: String {
"This is the english translation"
}
"""
let expectEnUs = """
/// Translation in en-us :
/// This is the english us translation
///
/// Comment :
/// No comment
static var definition_name: String {
"This is the english us translation"
}
"""
XCTAssertEqual(propertyFr.adaptForXCTest(), expectFr.adaptForXCTest())
XCTAssertEqual(propertyEn.adaptForXCTest(), expectEn.adaptForXCTest())
XCTAssertEqual(propertyEnUs.adaptForXCTest(), expectEnUs.adaptForXCTest())
}
} }

File diff suppressed because it is too large Load Diff