init project
This commit is contained in:
43
StringGenerator/Colors.swift
Normal file
43
StringGenerator/Colors.swift
Normal file
@ -0,0 +1,43 @@
|
||||
//
|
||||
// Colors.swift
|
||||
// StringGenerator
|
||||
//
|
||||
// Created by Ekaterina Lapkovskaja on 04/07/2019.
|
||||
// Copyright © 2019 Waveaccess. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public protocol ModeCode {
|
||||
var value: UInt8 { get }
|
||||
}
|
||||
|
||||
public enum Color: UInt8, ModeCode {
|
||||
case black = 30
|
||||
case red
|
||||
case green
|
||||
case yellow
|
||||
case blue
|
||||
case magenta
|
||||
case cyan
|
||||
case white
|
||||
case `default` = 39
|
||||
case lightBlack = 90
|
||||
case lightRed
|
||||
case lightGreen
|
||||
case lightYellow
|
||||
case lightBlue
|
||||
case lightMagenta
|
||||
case lightCyan
|
||||
case lightWhite
|
||||
|
||||
public var value: UInt8 {
|
||||
return rawValue
|
||||
}
|
||||
}
|
||||
extension String {
|
||||
func color(_ color: Color) -> String {
|
||||
return "\u{001B}[0;\(color.rawValue)m\(self)\u{001B}[0;\(Color.default.rawValue)m"
|
||||
}
|
||||
}
|
||||
|
||||
77
StringGenerator/Commands/AddStringCommand.swift
Normal file
77
StringGenerator/Commands/AddStringCommand.swift
Normal file
@ -0,0 +1,77 @@
|
||||
//
|
||||
// AddStringCommand.swift
|
||||
// StringGenerator
|
||||
//
|
||||
// Created by Ekaterina Lapkovskaja on 12/07/2019.
|
||||
// Copyright © 2019 Waveaccess. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class AddStringCommand: CommandProtocol {
|
||||
|
||||
private let fileManager = FileManager.default
|
||||
|
||||
var name: String = "add"
|
||||
|
||||
var description: String = """
|
||||
Adds/Updates localizable strings in plist-files
|
||||
type 'stringen add [Key.Key.Key...] [project resources folder path]'
|
||||
if [Key.Key.Key...] already exists, updates the value
|
||||
"""
|
||||
|
||||
func perform(arguments: [String]) {
|
||||
if arguments.count == 0 {
|
||||
print(error: "no arguments provided")
|
||||
exit(1)
|
||||
}
|
||||
else if arguments.count == 1 {
|
||||
print(error: "too few arguments")
|
||||
exit(1)
|
||||
}
|
||||
else if arguments.count == 2 {
|
||||
let key = arguments[0]
|
||||
let resourcesPath = URL(fileURLWithPath: arguments[1])
|
||||
guard let lprojFoldersPaths = try? fileManager.contentsOfDirectory(atPath: resourcesPath.path)
|
||||
.filter({ $0.contains(Constants.lproj) })
|
||||
.compactMap({ resourcesPath.appendingPathComponent($0) }),
|
||||
!lprojFoldersPaths.isEmpty else {
|
||||
print(error: "*.lproj folders not found.")
|
||||
exit(1)
|
||||
}
|
||||
let stringFiles = lprojFoldersPaths.map { $0.appendingPathComponent(Constants.stringFileName) }
|
||||
.filter({ fileManager.fileExists(atPath: $0.path) })
|
||||
guard stringFiles.count == lprojFoldersPaths.count else {
|
||||
print(error: "\(Constants.stringFileName)'s count is not equal to all languages.")
|
||||
exit(1)
|
||||
}
|
||||
stringFiles.forEach({ addKey(key, fileURL: $0) })
|
||||
}
|
||||
else {
|
||||
print(error: "too many arguments")
|
||||
exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
private func addKey(_ key: String, fileURL: URL) {
|
||||
guard var plistDictionary = NSDictionary(contentsOf: fileURL) as? [String: Any] else {
|
||||
print(error: "Can't parse file \(getTwoLastComponents(path: fileURL))")
|
||||
exit(1)
|
||||
}
|
||||
|
||||
print("Enter localized string for \("\(getTwoLastComponents(path: fileURL))".color(.lightCyan))\("".color(.default)).")
|
||||
guard let input = readLine()?.trimmingCharacters(in: .whitespacesAndNewlines) else {
|
||||
addKey(key, fileURL: fileURL)
|
||||
exit(1)
|
||||
}
|
||||
|
||||
plistDictionary[keyPath: key] = input
|
||||
|
||||
if !NSDictionary(dictionary: plistDictionary).write(toFile: fileURL.path, atomically: true) {
|
||||
print(error: fileURL.path.color(.lightCyan) + " create error.")
|
||||
exit(1)
|
||||
}
|
||||
print(success: "\(fileURL.path) successfully created.")
|
||||
}
|
||||
|
||||
}
|
||||
15
StringGenerator/Commands/BaseCommand.swift
Normal file
15
StringGenerator/Commands/BaseCommand.swift
Normal file
@ -0,0 +1,15 @@
|
||||
//
|
||||
// BaseCommand.swift
|
||||
// StringGenerator
|
||||
//
|
||||
// Created by Ekaterina Lapkovskaja on 04/07/2019.
|
||||
// Copyright © 2019 Waveaccess. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
protocol CommandProtocol {
|
||||
var name: String { get }
|
||||
var description: String { get }
|
||||
func perform(arguments: [String])
|
||||
}
|
||||
119
StringGenerator/Commands/ConvertCommand.swift
Normal file
119
StringGenerator/Commands/ConvertCommand.swift
Normal file
@ -0,0 +1,119 @@
|
||||
//
|
||||
// ConvertCommand.swift
|
||||
// StringGenerator
|
||||
//
|
||||
// Created by Mike Price on 2019-07-11.
|
||||
// Copyright © 2019 Waveaccess. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class ConvertCommand: CommandProtocol {
|
||||
|
||||
private let fileManager = FileManager.default
|
||||
private lazy var rowRegex: NSRegularExpression = {
|
||||
guard let regex = try? NSRegularExpression(pattern: Constants.localizationRowPattern) else {
|
||||
print(error: "Regexp error.")
|
||||
exit(1)
|
||||
}
|
||||
return regex
|
||||
}()
|
||||
|
||||
var name: String = "convert"
|
||||
var description: String = """
|
||||
Converting all '\(Constants.generatedStringsFileName)' from resources folder to plists for all language (to selected output folder)
|
||||
"""
|
||||
|
||||
func perform(arguments: [String]) {
|
||||
guard arguments.count == 2 else {
|
||||
print(error: "Please, provide resources folder & Localization output folder.")
|
||||
exit(1)
|
||||
}
|
||||
|
||||
let resourcesPath = URL(fileURLWithPath: arguments[0])
|
||||
let outputPath = URL(fileURLWithPath: arguments[1])
|
||||
|
||||
guard let lprojFoldersPaths = try? fileManager
|
||||
.contentsOfDirectory(atPath: resourcesPath.path)
|
||||
.filter({ $0.contains(Constants.lproj) })
|
||||
.compactMap({ resourcesPath.appendingPathComponent($0) }),
|
||||
!lprojFoldersPaths.isEmpty else {
|
||||
print(error: "*.lproj folders not found.")
|
||||
exit(1)
|
||||
}
|
||||
|
||||
let langFiles = lprojFoldersPaths.map { $0.appendingPathComponent(Constants.generatedStringsFileName) }
|
||||
.filter({ fileManager.fileExists(atPath: $0.path) })
|
||||
|
||||
guard langFiles.count == lprojFoldersPaths.count else {
|
||||
print(error: "\(Constants.generatedStringsFileName)'s count is not equal to all languages.")
|
||||
exit(1)
|
||||
}
|
||||
|
||||
if !fileManager.fileExists(atPath: outputPath.path) {
|
||||
try? fileManager.createDirectory(at: outputPath, withIntermediateDirectories: true)
|
||||
}
|
||||
|
||||
langFiles.forEach { (url: URL) in
|
||||
guard let fileData = try? String(contentsOfFile: url.path)
|
||||
else {
|
||||
print(error: url.path.color(.lightCyan) + " parsing text error.")
|
||||
exit(1)
|
||||
}
|
||||
|
||||
let nameWithoutExt: String = url.pathComponents[url.pathComponents.count - 2]
|
||||
var firstName = nameWithoutExt.split(separator: ".").dropLast()
|
||||
firstName.append("plist")
|
||||
let resultFileName = firstName.joined(separator: ".")
|
||||
let stringPlistFilePath = outputPath.appendingPathComponent(resultFileName)
|
||||
|
||||
let dataStrings = fileData.split(separator: "\n")
|
||||
.filter({ rowRegex.matches(String($0)) })
|
||||
.map({ String($0) })
|
||||
|
||||
validate(name: stringPlistFilePath.path, dataStrings: dataStrings)
|
||||
|
||||
var plistData = [String: Any]()
|
||||
parsing(dataStrings, in: &plistData)
|
||||
if !NSDictionary(dictionary: plistData).write(toFile: stringPlistFilePath.path, atomically: true) {
|
||||
print(error: stringPlistFilePath.path.color(.lightCyan) + " create error.")
|
||||
exit(1)
|
||||
}
|
||||
print(success: "\(stringPlistFilePath.path) successfully created.")
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
private func validate(name: String, dataStrings: [String]) {
|
||||
if !dataStrings.filter({ !rowRegex.matches($0) }).isEmpty {
|
||||
print(error: "\(name) is invalid.")
|
||||
exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
private func parsing(_ dataStrings: [String], in result: inout [String: Any]) {
|
||||
dataStrings.forEach { row in
|
||||
let path = getGroup(Constants.rowPathComponent, from: row)
|
||||
let value = getGroup(Constants.rowValueComponent, from: row)
|
||||
result[keyPath: path] = value
|
||||
}
|
||||
}
|
||||
|
||||
private func getGroup(_ key: String, from row: String) -> String {
|
||||
let fullRange = NSRange(row.startIndex..<row.endIndex, in: row)
|
||||
guard let match = rowRegex.firstMatch(in: row, range: fullRange) else {
|
||||
print(error: "Error get full range from '\(row)'")
|
||||
exit(1)
|
||||
}
|
||||
|
||||
let valueRange = match.range(withName: key)
|
||||
if valueRange.location != NSNotFound, let valueRangeInRow = Range(valueRange, in: row) {
|
||||
return String(row[valueRangeInRow])
|
||||
}
|
||||
else {
|
||||
print(error: "Invalid value in '\(row)'")
|
||||
exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
290
StringGenerator/Commands/GenerateCommand.swift
Normal file
290
StringGenerator/Commands/GenerateCommand.swift
Normal file
@ -0,0 +1,290 @@
|
||||
//
|
||||
// GenerateCommand.swift
|
||||
// StringGenerator
|
||||
//
|
||||
// Created by Mike Price on 2019-07-11.
|
||||
// Copyright © 2019 Waveaccess. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class GenerateCommand: CommandProtocol {
|
||||
|
||||
private let fileManager = FileManager.default
|
||||
|
||||
var name: String = "generate"
|
||||
var description: String = """
|
||||
Parameters: [project resources folder path] [dir for generated enum path]
|
||||
Generation localizable resources on steps:
|
||||
- check on valid and compare all \(Constants.stringFileName) (for all languages) in resources folder
|
||||
- generate \(Constants.generatedFileName) enum for selected directory
|
||||
- generate \(Constants.generatedStringsFileName)'s for all languages
|
||||
"""
|
||||
|
||||
func perform(arguments: [String]) {
|
||||
guard arguments.count == 2 else {
|
||||
print(error: "Please, provide resources folder & Localization output folder.")
|
||||
return
|
||||
}
|
||||
let resourcesPath = URL(fileURLWithPath: arguments[0])
|
||||
let localizationPath = URL(fileURLWithPath: arguments[1])
|
||||
guard let lprojFoldersPaths = try? fileManager.contentsOfDirectory(atPath: resourcesPath.path)
|
||||
.filter({ $0.contains(Constants.lproj) })
|
||||
.compactMap({ resourcesPath.appendingPathComponent($0) }),
|
||||
!lprojFoldersPaths.isEmpty else {
|
||||
print(error: "*.lproj folders not found.")
|
||||
exit(1)
|
||||
}
|
||||
let stringFiles = lprojFoldersPaths.map { $0.appendingPathComponent(Constants.stringFileName) }
|
||||
.filter({ fileManager.fileExists(atPath: $0.path) })
|
||||
|
||||
guard stringFiles.count == lprojFoldersPaths.count else {
|
||||
print(error: "\(Constants.stringFileName)'s count is not equal to all languages.")
|
||||
exit(1)
|
||||
}
|
||||
|
||||
validate(stringFiles: stringFiles)
|
||||
compare(stringFiles: stringFiles)
|
||||
makeLocalizationFile(stringFiles[0], at: localizationPath)
|
||||
generationLocalizationStrings(stringFiles: stringFiles)
|
||||
}
|
||||
|
||||
// MARK: Validation block
|
||||
|
||||
private func validate(stringFiles: [URL]) {
|
||||
var isFailure = false
|
||||
for stringFile in stringFiles {
|
||||
guard let plistDictionary = NSDictionary(contentsOf: stringFile) as? [String: Any] else {
|
||||
print(error: "Can't parse the file. Maybe, it's not a plist?")
|
||||
exit(1)
|
||||
}
|
||||
let errorKeys = isValid(dict: plistDictionary)
|
||||
if !errorKeys.isEmpty {
|
||||
print("")
|
||||
print(error: "Plist '\(getTwoLastComponents(path: stringFile).color(.lightCyan))' have next invalid keys:")
|
||||
errorKeys.forEach { print(error: $0) }
|
||||
isFailure = true
|
||||
}
|
||||
}
|
||||
if isFailure {
|
||||
exit(1)
|
||||
}
|
||||
print(success: "Key values in Plists is valid.")
|
||||
}
|
||||
|
||||
private func isValid(dict: [String: Any], keyPath: String = "") -> [String] {
|
||||
var errors = [String]()
|
||||
for item in dict {
|
||||
if let child = item.value as? [String: Any] {
|
||||
if child.isEmpty {
|
||||
errors.append("value for key \("\(keyPath)\(item.key)".color(.lightRed)) is empty.")
|
||||
}
|
||||
else {
|
||||
errors.append(contentsOf: isValid(dict: child, keyPath: "\(keyPath)\(item.key)."))
|
||||
}
|
||||
}
|
||||
else if (item.value as? String)?.isEmpty == true {
|
||||
errors.append("value for key \("\(keyPath)\(item.key)".color(.lightRed)) is empty.")
|
||||
}
|
||||
else if item.value as? [String: Any] == nil && item.value as? String == nil {
|
||||
errors.append("value for key \("\(keyPath)\(item.key)".color(.lightRed)) is not a String.")
|
||||
}
|
||||
}
|
||||
return errors
|
||||
}
|
||||
|
||||
// MARK: Compare block
|
||||
|
||||
private func compare(stringFiles: [URL]) {
|
||||
var dictionaries: [(String, [String: Any])] = stringFiles.map { fileUrl in
|
||||
guard let plistDictionary = NSDictionary(contentsOf: fileUrl) as? [String: Any] else {
|
||||
print(error: "Can't parse \(getTwoLastComponents(path: fileUrl).color(.lightCyan)). Maybe, it's not a plist?")
|
||||
exit(1)
|
||||
}
|
||||
return (fileUrl.path, plistDictionary)
|
||||
}
|
||||
var equalityResults: [Bool] = []
|
||||
for index in 0...dictionaries.count - 1 {
|
||||
let (firstPlistName, firstPlist) = dictionaries[index]
|
||||
let (secondPlistName, secondPlist) = dictionaries[index != dictionaries.count - 1 ? index + 1 : 0]
|
||||
equalityResults.append(comparePlists(firstName: firstPlistName, firstPlist: firstPlist, secondName: secondPlistName, secondPlist: secondPlist))
|
||||
}
|
||||
if equalityResults.contains(false) {
|
||||
exit(1)
|
||||
}
|
||||
print(success: "Plists keys are equal to each other.")
|
||||
}
|
||||
|
||||
private func comparePlists(firstName: String, firstPlist: [String: Any], secondName: String, secondPlist: [String: Any], keyPath: String = "") -> Bool {
|
||||
var areEqual: Bool = true
|
||||
for item in firstPlist {
|
||||
if let child = item.value as? [String: Any] {
|
||||
if let secondChild = secondPlist[item.key] as? [String: Any] {
|
||||
areEqual = areEqual && comparePlists(firstName: firstName, firstPlist: child, secondName: secondName, secondPlist: secondChild, keyPath: "\(keyPath)\(item.key).")
|
||||
}
|
||||
else {
|
||||
print(error: """
|
||||
plist \("\(getTwoLastComponents(path: secondName))".color(.lightCyan))
|
||||
does not contain\(" \(keyPath)\(item.key) ".color(.lightRed))key.
|
||||
But exists in \("\(getTwoLastComponents(path: firstName))".color(.lightCyan)).
|
||||
""")
|
||||
areEqual = areEqual && comparePlists(firstName: firstName, firstPlist: child, secondName: secondName, secondPlist: secondPlist, keyPath: "\(keyPath)\(item.key).")
|
||||
}
|
||||
}
|
||||
else if item.value as? String != nil {
|
||||
if secondPlist[item.key] as? String == nil {
|
||||
areEqual = false
|
||||
print(error: """
|
||||
plist \("\(getTwoLastComponents(path: secondName))".color(.lightCyan))
|
||||
does not contain\(" \(keyPath)\(item.key) ".color(.lightRed))key.
|
||||
But exists in \("\(getTwoLastComponents(path: firstName))".color(.lightCyan)).
|
||||
""")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return areEqual
|
||||
}
|
||||
|
||||
// MARK: LocalizedStringsKeys.swift generation block
|
||||
|
||||
private func makeLocalizationFile(_ path: URL, at localizationPath: URL) {
|
||||
guard let plistDictionary = NSDictionary(contentsOf: path) as? [String: Any] else {
|
||||
print(error: "Can't parse \(getTwoLastComponents(path: path).color(.lightCyan)). Maybe, it's not a plist?")
|
||||
exit(1)
|
||||
}
|
||||
|
||||
let startTime = Date()
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateFormat = "MM/dd/yy h:mm a Z"
|
||||
|
||||
var swiftFileString: String = ""
|
||||
swiftFileString += Constants.comment
|
||||
swiftFileString += "///Generated: \(dateFormatter.string(from: startTime))\n\n"
|
||||
swiftFileString += enumStart(name: Constants.enumName)
|
||||
swiftFileString += parseBody(dict: plistDictionary).0
|
||||
swiftFileString += enumEnd()
|
||||
|
||||
let savePath = localizationPath.appendingPathComponent(Constants.generatedFileName).path
|
||||
if !fileManager.fileExists(atPath: savePath) {
|
||||
fileManager.createFile(atPath: savePath, contents: swiftFileString.data(using: .utf8), attributes: nil)
|
||||
}
|
||||
else {
|
||||
do {
|
||||
try swiftFileString.write(toFile: savePath, atomically: true, encoding: .utf8)
|
||||
print(success: "\(Constants.generatedFileName) enum in '\(localizationPath.path)' was created successfully.")
|
||||
}
|
||||
catch {
|
||||
print(error: error.localizedDescription)
|
||||
exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func parseBody(dict: [String: Any], level: Int = 1, keyPath: String = "") -> (String, Bool) {
|
||||
var res = ""
|
||||
var levelHasKey = false
|
||||
for item in dict {
|
||||
if let child = item.value as? [String: Any] {
|
||||
let tmp = parseBody(dict: child, level: level + 1, keyPath: "\(keyPath)\(item.key).")
|
||||
res += tab(level)
|
||||
res += enumStart(name: item.key, isLocalized: tmp.1)
|
||||
res += tmp.0
|
||||
res += tab(level)
|
||||
res += enumEnd()
|
||||
}
|
||||
if item.value as? String != nil {
|
||||
levelHasKey = true
|
||||
res += tab(level)
|
||||
res += caseFor(name: item.key, value: "\(keyPath)\(item.key)")
|
||||
}
|
||||
}
|
||||
if levelHasKey {
|
||||
res += localized(key: keyPath, level: level)
|
||||
}
|
||||
return (res, levelHasKey)
|
||||
}
|
||||
|
||||
private func enumStart(name: String, isLocalized: Bool = false) -> String {
|
||||
return "enum \(name)\(isLocalized ? ": String, LocalizedProtocol" : "" ) {\n"
|
||||
}
|
||||
|
||||
private func enumEnd() -> String {
|
||||
return "}\n"
|
||||
}
|
||||
|
||||
private func caseFor(name: String, value: String) -> String {
|
||||
return "case \(name) = \"\(value)\"\n"
|
||||
}
|
||||
|
||||
private func tab(_ level: Int) -> String {
|
||||
var res = ""
|
||||
for _ in 0 ... level - 1 {
|
||||
res += "\t"
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
private func localized(key: String, level: Int) -> String {
|
||||
var res = "\n"
|
||||
res += tab(level)
|
||||
res += "var localized: String {\n"
|
||||
res += tab(level + 1)
|
||||
res += "return rawValue.localized\n"
|
||||
res += tab(level)
|
||||
res += "}\n"
|
||||
return res
|
||||
}
|
||||
|
||||
// MARK: Localized.strings generation block
|
||||
|
||||
private func generationLocalizationStrings(stringFiles: [URL]) {
|
||||
stringFiles.forEach { url in
|
||||
guard let plistDictionary = NSDictionary(contentsOf: url) as? [String: Any] else {
|
||||
print(error: "Can't parse \(getTwoLastComponents(path: url).color(.lightCyan)). Maybe, it's not a plist?")
|
||||
exit(1)
|
||||
}
|
||||
var content = [String]()
|
||||
|
||||
content.append(Constants.comment + "/// from: \(getTwoLastComponents(path: url))")
|
||||
|
||||
calculationStrings(plistDictionary, content: &content)
|
||||
|
||||
let body = content.enumerated().map({ (index: Int, item: String) -> String in
|
||||
if index == content.count - 1 { return item }
|
||||
return item.split(separator: ".").first == content[index+1].split(separator: ".").first
|
||||
? item
|
||||
: item + "\n"
|
||||
}).joined(separator: "\n")
|
||||
|
||||
let savePath = url.deletingLastPathComponent().appendingPathComponent(Constants.generatedStringsFileName).path
|
||||
|
||||
if !fileManager.fileExists(atPath: savePath) {
|
||||
fileManager.createFile(atPath: savePath, contents: body.data(using: .utf8), attributes: nil)
|
||||
print(success: "\(savePath) was created successfully.")
|
||||
}
|
||||
else {
|
||||
do {
|
||||
try body.write(toFile: savePath, atomically: true, encoding: .utf8)
|
||||
print(success: "\(savePath) was created successfully.")
|
||||
}
|
||||
catch {
|
||||
print(error: error.localizedDescription)
|
||||
exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func calculationStrings(_ dict: [String: Any], content: inout [String], path: String = "") {
|
||||
dict.forEach { key, value in
|
||||
if let value = value as? Dictionary<String, Any> {
|
||||
calculationStrings(value, content: &content, path: "\(path + key).")
|
||||
}
|
||||
else if let value = value as? String {
|
||||
content.append("\"\(path + key)\" = \"\(value.replacingOccurrences(of: "\"", with: "\\\""))\";")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
22
StringGenerator/Constants.swift
Normal file
22
StringGenerator/Constants.swift
Normal file
@ -0,0 +1,22 @@
|
||||
//
|
||||
// Constants.swift
|
||||
// StringGenerator
|
||||
//
|
||||
// Created by Ekaterina Lapkovskaja on 04/07/2019.
|
||||
// Copyright © 2019 Waveaccess. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class Constants {
|
||||
static let stringFileName = "strings.plist"
|
||||
static let lproj = ".lproj"
|
||||
static let enumName: String = "Localized"
|
||||
static let generatedFileName = "LocalizedStringsKeys.swift"
|
||||
static let generatedStringsFileName = "Localizable.strings"
|
||||
static let comment = "///This file is automatically generated. Yout must not edit it manually\n"
|
||||
|
||||
static let localizationRowPattern = #"\"(?<path>(.+))\" = \"(?<value>(.+))\";"#
|
||||
static let rowPathComponent = "path"
|
||||
static let rowValueComponent = "value"
|
||||
}
|
||||
88
StringGenerator/Helpers.swift
Normal file
88
StringGenerator/Helpers.swift
Normal file
@ -0,0 +1,88 @@
|
||||
//
|
||||
// Helpers.swift
|
||||
// StringGenerator
|
||||
//
|
||||
// Created by Ekaterina Lapkovskaja on 04/07/2019.
|
||||
// Copyright © 2019 Waveaccess. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
func print(error: String) {
|
||||
print("\("Error".color(.red)): \(error)")
|
||||
}
|
||||
|
||||
func print(success: String) {
|
||||
print(success.color(.green))
|
||||
}
|
||||
|
||||
func getTwoLastComponents(path: String) -> String {
|
||||
return getTwoLastComponents(path: URL(fileURLWithPath: path))
|
||||
}
|
||||
|
||||
func getTwoLastComponents(path: URL) -> String {
|
||||
let excludeCount = path.pathComponents.count - 2
|
||||
return path.pathComponents.dropFirst(excludeCount).joined(separator: "/")
|
||||
}
|
||||
|
||||
extension NSRegularExpression {
|
||||
func matches(_ string: String) -> Bool {
|
||||
let range = NSRange(location: 0, length: string.utf16.count)
|
||||
return firstMatch(in: string, options: [], range: range) != nil
|
||||
}
|
||||
}
|
||||
|
||||
extension Dictionary {
|
||||
subscript(keyPath keyPath: String) -> Any? {
|
||||
get {
|
||||
guard let keyPath = Dictionary.keyPathKeys(forKeyPath: keyPath)
|
||||
else { return nil }
|
||||
return getValue(forKeyPath: keyPath)
|
||||
}
|
||||
set {
|
||||
guard let keyPath = Dictionary.keyPathKeys(forKeyPath: keyPath),
|
||||
let newValue = newValue
|
||||
else { return }
|
||||
self.setValue(newValue, forKeyPath: keyPath)
|
||||
}
|
||||
}
|
||||
|
||||
static private func keyPathKeys(forKeyPath: String) -> [Key]? {
|
||||
let keys = forKeyPath.components(separatedBy: ".").reversed().compactMap({ $0 as? Key })
|
||||
return keys.isEmpty ? nil : keys
|
||||
}
|
||||
|
||||
private func getValue(forKeyPath keyPath: [Key]) -> Any? {
|
||||
guard let lastKey = keyPath.last, let value = self[lastKey]
|
||||
else { return nil }
|
||||
return keyPath.count == 1 ? value : (value as? [Key: Any])
|
||||
.flatMap { $0.getValue(forKeyPath: Array(keyPath.dropLast())) }
|
||||
}
|
||||
|
||||
private mutating func setValue(_ value: Any, forKeyPath keyPath: [Key]) {
|
||||
var keyPath = keyPath
|
||||
guard let firstKey = keyPath.popLast() else { return }
|
||||
if keyPath.isEmpty {
|
||||
self[firstKey] = value as? Value
|
||||
}
|
||||
else {
|
||||
var dict: [Key : Any] = self[firstKey] as? [Key : Any] ?? [:]
|
||||
deepSet(value, keyPath: keyPath, in: &dict)
|
||||
self[firstKey] = dict as? Value
|
||||
}
|
||||
}
|
||||
|
||||
private func deepSet(_ value: Any, keyPath: [Key], in dict: inout [Key : Any]) {
|
||||
var keyPath = keyPath
|
||||
guard let key: Key = keyPath.popLast() else {
|
||||
return
|
||||
}
|
||||
if keyPath.isEmpty {
|
||||
dict[key] = value
|
||||
return
|
||||
}
|
||||
var bufferDict: [Key: Any] = dict[key] as? [Key : Any] ?? [:]
|
||||
deepSet(value, keyPath: keyPath, in: &bufferDict)
|
||||
dict[key] = bufferDict
|
||||
}
|
||||
}
|
||||
49
StringGenerator/main.swift
Normal file
49
StringGenerator/main.swift
Normal file
@ -0,0 +1,49 @@
|
||||
//
|
||||
// LocalizedStringKeyGen.swift
|
||||
// TaxFree
|
||||
//
|
||||
// Created by Ekaterina Lapkovskaja on 05/03/2019.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class Run {
|
||||
var commands: [CommandProtocol] = []
|
||||
func perform(arguments: [String]) {
|
||||
guard arguments.count > 0, let commandName = arguments.first, let command = commands.first(where: { (row) -> Bool in
|
||||
return row.name == commandName
|
||||
}) else {
|
||||
showHelp()
|
||||
return
|
||||
}
|
||||
var args = arguments
|
||||
args.removeFirst()
|
||||
command.perform(arguments: args)
|
||||
}
|
||||
|
||||
private func showHelp() {
|
||||
for command in commands {
|
||||
var desc = ""
|
||||
for (index, key) in command.description.split(separator: "\n").enumerated() {
|
||||
desc.append(String(format: index == 0 ? "%s%@\n" : "%-16s%@\n", ("" as NSString).utf8String!, String(key)))
|
||||
}
|
||||
let str = String(format: "%-22s\("".color(.default)) - %@", (command.name.color(.green) as NSString).utf8String!, desc)
|
||||
print(str)
|
||||
}
|
||||
}
|
||||
|
||||
func command<T>(class: T.Type) -> T? {
|
||||
return commands.first(where: { command in
|
||||
return command is T
|
||||
}) as? T
|
||||
}
|
||||
}
|
||||
|
||||
let run = Run()
|
||||
run.commands.append(GenerateCommand())
|
||||
run.commands.append(AddStringCommand())
|
||||
run.commands.append(ConvertCommand())
|
||||
var arguments = CommandLine.arguments
|
||||
arguments.removeFirst()
|
||||
run.perform(arguments: arguments)
|
||||
|
||||
Reference in New Issue
Block a user