From 440eaac2ed3c20dfdacd510d206859b96a031915 Mon Sep 17 00:00:00 2001 From: Kaan Dedeoglu Date: Tue, 15 Dec 2020 01:18:16 +0100 Subject: [PATCH 1/7] Update dependencies, initial support for fonts --- .../xcshareddata/xcschemes/Shark.xcscheme | 10 +++ Package.resolved | 16 ++-- Sources/Shark/ColorEnumBuilder.swift | 21 ++---- Sources/Shark/FontEnumBuilder.swift | 52 +++++++++++++ Sources/Shark/ImageEnumBuilder.swift | 60 +++++---------- Sources/Shark/LocalizationEnumBuilder.swift | 58 +++++--------- Sources/Shark/Node.swift | 34 ++++++++- Sources/Shark/Parser.swift | 58 -------------- Sources/Shark/SanitizableValue.swift | 4 + Sources/Shark/Shark.swift | 75 +++++++++++++++++++ Sources/Shark/SharkEnumBuilder.swift | 7 +- Sources/Shark/XcodeProjectHelper.swift | 36 ++++----- 12 files changed, 245 insertions(+), 186 deletions(-) create mode 100644 Sources/Shark/FontEnumBuilder.swift delete mode 100644 Sources/Shark/Parser.swift create mode 100644 Sources/Shark/SanitizableValue.swift create mode 100644 Sources/Shark/Shark.swift diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/Shark.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/Shark.xcscheme index 0f969e6..bc074f7 100644 --- a/.swiftpm/xcode/xcshareddata/xcschemes/Shark.xcscheme +++ b/.swiftpm/xcode/xcshareddata/xcschemes/Shark.xcscheme @@ -72,6 +72,16 @@ ReferencedContainer = "container:"> + + + + + + String { - switch self { - case .color(let name): - return #"\#(String(indentLevel: indentLevel))public static var \#(name.casenameSanitized): UIColor { return UIColor(named: "\#(name)", in: \#(SharkEnumBuilder.topLevelEnumName).bundle, compatibleWith: nil)! }"# - } + return #"\#(String(indentLevel: indentLevel))public static var \#(name.casenameSanitized): UIColor { return UIColor(named: "\#(name)", in: \#(SharkEnumBuilder.topLevelEnumName).bundle, compatibleWith: nil)! }"# } static func <(lhs: ColorValue, rhs: ColorValue) -> Bool { - switch (lhs, rhs) { - case (.color(let leftName), .color(let rightName)): - return leftName < rightName - } + return lhs.name < rhs.name } } @@ -26,13 +20,10 @@ enum ColorEnumBuilder { static func colorEnumString(forFilesAtPaths paths: [String], topLevelName: String) throws -> String? { let colorAssetPaths = try paths.flatMap { try FileManager.default.subpathsOfDirectory(atPath: $0).filter({ $0.pathExtension == Constants.colorSetExtension }) } guard colorAssetPaths.isEmpty == false else { return nil } - - var result = """ -public enum \(topLevelName) { -""" + var result = "public enum \(topLevelName) {\n" for name in colorAssetPaths.map({ $0.lastPathComponent.deletingPathExtension }).sorted() { - result += ColorValue.color(name: name).declaration(indentLevel: 1) + result += ColorValue(name: name).declaration(indentLevel: 1) result += "\n" } diff --git a/Sources/Shark/FontEnumBuilder.swift b/Sources/Shark/FontEnumBuilder.swift new file mode 100644 index 0000000..e51e622 --- /dev/null +++ b/Sources/Shark/FontEnumBuilder.swift @@ -0,0 +1,52 @@ +import Foundation + +private struct FontValue: Equatable, Comparable { + let methodName: String + let fontName: String + + func declaration(indentLevel: Int) -> String { + #"\#(String(indentLevel: indentLevel))public static func \#(methodName)(ofSize size: CGFloat) -> UIFont { return UIFont(name: "\#(fontName)", size: size)! }"# + } + + static func <(lhs: FontValue, rhs: FontValue) -> Bool { + lhs.methodName < rhs.methodName + } +} + +enum FontEnumBuilder { + static func fontsEnumString(forFilesAtPaths paths: [String], topLevelName: String) throws -> String? { + let fontValues = paths.compactMap { path -> FontValue? in + let url = URL(fileURLWithPath: path) + + guard + let data = try? Data(contentsOf: url), + let provider = CGDataProvider(data: data as CFData), + let font = CGFont(provider), + let fullName = font.fullName as String?, + let postScriptName = font.postScriptName as String? else { return nil } + + var components = fullName.split(separator: " ") + let first = components.removeFirst().lowercased() + let rest = components.map(\.capitalized) + let methodName = ([first] + rest).joined() + + return FontValue(methodName: methodName, + fontName: postScriptName) + } + + guard fontValues.isEmpty == false else { return nil } + + var result = """ + public enum \(topLevelName) { + + """ + + for font in fontValues.sorted() { + result += font.declaration(indentLevel: 1) + result += "\n" + } + + result += "}" + return result + } +} diff --git a/Sources/Shark/ImageEnumBuilder.swift b/Sources/Shark/ImageEnumBuilder.swift index 7f416bb..d21f71a 100644 --- a/Sources/Shark/ImageEnumBuilder.swift +++ b/Sources/Shark/ImageEnumBuilder.swift @@ -1,8 +1,8 @@ import Foundation private enum ImageValue: Equatable, Comparable { - case namespace(name: String) case image(caseName: String, value: String) + case namespace(name: String) func declaration(withBody body: String = "", indentLevel: Int) throws -> String { switch self { @@ -31,6 +31,24 @@ private enum ImageValue: Equatable, Comparable { } } +extension ImageValue: SanitizableValue { + var name: String { + switch self { + case .namespace(let name), .image(let name, _): + return name + } + } + + func underscoringName() -> ImageValue { + switch self { + case .image(let caseName, let value): + return .image(caseName: caseName.underscored, value: value) + case .namespace(let name): + return .namespace(name: name.underscored) + } + } +} + enum ImageEnumBuilder { private enum Constants { static let imageSetExtension = "imageset" @@ -67,43 +85,3 @@ enum ImageEnumBuilder { } } } - -private extension Node where Element == ImageValue { - func sanitize() { - //If two children have the same name, or if a child has the same name with its parent, underscore - var modified = false - repeat { - modified = false - var countedSet = CountedSet() - for child in children { - for _ in 0.. Self { + switch self { + case .localization(let name, let key, let value): + return .localization(name: name.underscored, key: key, value: value) + case .namespace(let name): + return .namespace(name: name.underscored) + } + } +} + enum LocalizationBuilderError: Error { case invalidLocalizableStringsFile(path: String) @@ -160,43 +178,3 @@ extension Array where Element == LocalizationValue.InterpolationType { """# } } - -private extension Node where Element == LocalizationValue { - func sanitize() { - //If two children have the same name, or if a children has the same name with a parent, underscore - var modified = false - repeat { - modified = false - var countedSet = CountedSet() - for child in children { - for _ in 0.. { var value: Element @@ -44,3 +42,35 @@ extension Node: Comparable where Element: Comparable { return lhs.value < rhs.value } } + +extension Node where Element: SanitizableValue { + func sanitize() { + //If two children have the same name, or if a children has the same name with a parent, underscore + var modified = false + repeat { + modified = false + var countedSet = CountedSet() + for child in children { + for _ in 0.. Self +} diff --git a/Sources/Shark/Shark.swift b/Sources/Shark/Shark.swift new file mode 100644 index 0000000..053734b --- /dev/null +++ b/Sources/Shark/Shark.swift @@ -0,0 +1,75 @@ +import Foundation +import ArgumentParser + +struct Shark: ParsableCommand { + static var configuration: CommandConfiguration = .init(abstract:#""" +Paste the following line in a Xcode run phase script that runs after the "Compile Sources" run phase: +shark $PROJECT_FILE_PATH $PROJECT_DIR/$PROJECT_NAME +"""#) + + @OptionGroup() + private var options: Options + + func run() throws { + let enumString = try SharkEnumBuilder.sharkEnumString(forOptions: options) + + try FileBuilder + .fileContents(with: enumString, filename: options.outputPath.lastPathComponent) + .write(to: URL(fileURLWithPath: options.outputPath), atomically: true, encoding: .utf8) + } +} + +struct Options: ParsableArguments { + @Argument(help: "The .xcodeproj file path", transform: Self.transform(forProjectPath:)) + fileprivate(set) var projectPath: String + + @Argument(help: "The output file path", transform: Self.transform(forOutputPath:)) + fileprivate(set) var outputPath: String + + @Option(name: .customLong("name"), + help: "The top level enum name") + private(set) var topLevelEnumName: String = "Shark" + + @Option(name: .customLong("target"), + help: "Target name of the application, useful in case there are multiple application targets") + private(set) var targetName: String? + + @Option(name: .long, + help: "Localization code to use when selecting the Localizable.strings. i.e en, de, es.") + private(set) var locale: String = "en" +} + +extension Options { + private static func transform(forProjectPath path: String) throws -> String { + var isDirectory: ObjCBool = false + if path.pathExtension == "xcodeproj" { + return path + } else if FileManager.default.fileExists(atPath: path, isDirectory: &isDirectory), isDirectory.boolValue { + let projectFiles = try FileManager + .default + .contentsOfDirectory(atPath: path).filter { $0.pathExtension == "xcodeproj" } + + if projectFiles.isEmpty { + throw ValidationError("\(path) should point to a .xcodeproj file") + } else if projectFiles.count == 1 { + return path.appendingPathComponent(projectFiles[0]) + } else { + throw ValidationError("There are multiple .xcodeproj files in directory: \(path). Please provide an exact path") + } + } else { + throw ValidationError("\(path) should point to a .xcodeproj file") + } + } + + private static func transform(forOutputPath path: String) throws -> String { + var path = path + var isDirectory: ObjCBool = false + if FileManager.default.fileExists(atPath: path, isDirectory: &isDirectory), isDirectory.boolValue { + path.append("Shark.swift") + } else if path.pathExtension != "swift" { + throw ValidationError("The output path should either point to an existing folder or end with a .swift extension") + } + + return path + } +} diff --git a/Sources/Shark/SharkEnumBuilder.swift b/Sources/Shark/SharkEnumBuilder.swift index 6c38e07..defac17 100644 --- a/Sources/Shark/SharkEnumBuilder.swift +++ b/Sources/Shark/SharkEnumBuilder.swift @@ -1,5 +1,3 @@ -import Foundation - enum SharkEnumBuilder { static var topLevelEnumName = "Shark" static func sharkEnumString(forOptions options: Options) throws -> String { @@ -9,8 +7,9 @@ enum SharkEnumBuilder { let imagesString = try ImageEnumBuilder.imageEnumString(forFilesAtPaths: resourcePaths.assetsPaths, topLevelName: "I") let colorsString = try ColorEnumBuilder.colorEnumString(forFilesAtPaths: resourcePaths.assetsPaths, topLevelName: "C") let localizationsString = try LocalizationEnumBuilder.localizationsEnumString(forFilesAtPaths: resourcePaths.localizationPaths, topLevelName: "L") - - let declarations = [imagesString, colorsString, localizationsString].compactMap({ $0?.indented(withLevel: 1) }).joined(separator: "\n\n") + let fontsString = try FontEnumBuilder.fontsEnumString(forFilesAtPaths: resourcePaths.fontPaths, topLevelName: "F") + + let declarations = [imagesString, colorsString, localizationsString, fontsString].compactMap({ $0?.indented(withLevel: 1) }).joined(separator: "\n\n") return """ public enum \(topLevelEnumName) { diff --git a/Sources/Shark/XcodeProjectHelper.swift b/Sources/Shark/XcodeProjectHelper.swift index 6e7c26b..9924278 100644 --- a/Sources/Shark/XcodeProjectHelper.swift +++ b/Sources/Shark/XcodeProjectHelper.swift @@ -8,8 +8,9 @@ enum PBXFilePathError: String, Error { struct XcodeProjectHelper { struct ResourcePaths { - let localizationPaths: [String] - let assetsPaths: [String] + fileprivate(set) var localizationPaths: [String] = [] + fileprivate(set) var assetsPaths: [String] = [] + fileprivate(set) var fontPaths: [String] = [] } private let projectPath: Path @@ -49,23 +50,22 @@ struct XcodeProjectHelper { print("Cannot locate the resources build phase in the target") exit(EXIT_FAILURE) } - - var localizationPaths: [String] = [] - var assetPaths: [String] = [] - - let allPaths = try targetResourcesFiles.compactMap { $0.file }.flatMap { try paths(for: $0) } - for path in allPaths { - switch path.pathExtension { - case "xcassets": - assetPaths.append(path) - case "strings" where path.pathComponents.contains("\(locale).lproj"): - localizationPaths.append(path) - default: - break - } - } - return ResourcePaths(localizationPaths: localizationPaths, assetsPaths: assetPaths) + return try targetResourcesFiles + .compactMap(\.file) + .flatMap(paths(for:)) + .reduce(into: ResourcePaths(), { result, path in + switch path.pathExtension { + case "xcassets": + result.assetsPaths.append(path) + case "strings" where path.pathComponents.contains("\(locale).lproj"): + result.localizationPaths.append(path) + case "ttf", "otf", "ttc": + result.fontPaths.append(path) + default: + break + } + }) } private func paths(for fileElement: PBXFileElement) throws -> [String] { From 854ee8aa56850c8bef001a6389488651bf39a15c Mon Sep 17 00:00:00 2001 From: Kaan Dedeoglu Date: Tue, 15 Dec 2020 01:22:04 +0100 Subject: [PATCH 2/7] Don't use keypaths as functions --- .swiftpm/xcode/xcshareddata/xcschemes/Shark.xcscheme | 10 ---------- .travis.yml | 6 ------ Sources/Shark/FontEnumBuilder.swift | 6 ++---- Sources/Shark/XcodeProjectHelper.swift | 2 +- 4 files changed, 3 insertions(+), 21 deletions(-) delete mode 100644 .travis.yml diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/Shark.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/Shark.xcscheme index bc074f7..0f969e6 100644 --- a/.swiftpm/xcode/xcshareddata/xcschemes/Shark.xcscheme +++ b/.swiftpm/xcode/xcshareddata/xcschemes/Shark.xcscheme @@ -72,16 +72,6 @@ ReferencedContainer = "container:"> - - - - - - String? { let fontValues = paths.compactMap { path -> FontValue? in - let url = URL(fileURLWithPath: path) - guard - let data = try? Data(contentsOf: url), + let data = try? Data(contentsOf: URL(fileURLWithPath: path)), let provider = CGDataProvider(data: data as CFData), let font = CGFont(provider), let fullName = font.fullName as String?, @@ -27,7 +25,7 @@ enum FontEnumBuilder { var components = fullName.split(separator: " ") let first = components.removeFirst().lowercased() - let rest = components.map(\.capitalized) + let rest = components.map { $0.capitalized } let methodName = ([first] + rest).joined() return FontValue(methodName: methodName, diff --git a/Sources/Shark/XcodeProjectHelper.swift b/Sources/Shark/XcodeProjectHelper.swift index 9924278..3669887 100644 --- a/Sources/Shark/XcodeProjectHelper.swift +++ b/Sources/Shark/XcodeProjectHelper.swift @@ -52,7 +52,7 @@ struct XcodeProjectHelper { } return try targetResourcesFiles - .compactMap(\.file) + .compactMap { $0.file } .flatMap(paths(for:)) .reduce(into: ResourcePaths(), { result, path in switch path.pathExtension { From 2ba738bb8880184bb993be14831b0b10c1afb8af Mon Sep 17 00:00:00 2001 From: Kaan Dedeoglu Date: Tue, 15 Dec 2020 01:46:48 +0100 Subject: [PATCH 3/7] Better indent ergonomics --- Sources/Shark/ColorEnumBuilder.swift | 2 +- Sources/Shark/FontEnumBuilder.swift | 2 +- Sources/Shark/ImageEnumBuilder.swift | 6 +++--- Sources/Shark/LocalizationEnumBuilder.swift | 12 ++++++------ Sources/Shark/String+Extensions.swift | 8 ++++---- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Sources/Shark/ColorEnumBuilder.swift b/Sources/Shark/ColorEnumBuilder.swift index 9497866..64ca9db 100644 --- a/Sources/Shark/ColorEnumBuilder.swift +++ b/Sources/Shark/ColorEnumBuilder.swift @@ -4,7 +4,7 @@ private struct ColorValue: Equatable, Comparable { let name: String func declaration(indentLevel: Int) -> String { - return #"\#(String(indentLevel: indentLevel))public static var \#(name.casenameSanitized): UIColor { return UIColor(named: "\#(name)", in: \#(SharkEnumBuilder.topLevelEnumName).bundle, compatibleWith: nil)! }"# + return #"\#(String.indent(indentLevel))public static var \#(name.casenameSanitized): UIColor { return UIColor(named: "\#(name)", in: \#(SharkEnumBuilder.topLevelEnumName).bundle, compatibleWith: nil)! }"# } static func <(lhs: ColorValue, rhs: ColorValue) -> Bool { diff --git a/Sources/Shark/FontEnumBuilder.swift b/Sources/Shark/FontEnumBuilder.swift index f7789e1..97ab9ab 100644 --- a/Sources/Shark/FontEnumBuilder.swift +++ b/Sources/Shark/FontEnumBuilder.swift @@ -5,7 +5,7 @@ private struct FontValue: Equatable, Comparable { let fontName: String func declaration(indentLevel: Int) -> String { - #"\#(String(indentLevel: indentLevel))public static func \#(methodName)(ofSize size: CGFloat) -> UIFont { return UIFont(name: "\#(fontName)", size: size)! }"# + #"\#(String.indent(indentLevel))public static func \#(methodName)(ofSize size: CGFloat) -> UIFont { return UIFont(name: "\#(fontName)", size: size)! }"# } static func <(lhs: FontValue, rhs: FontValue) -> Bool { diff --git a/Sources/Shark/ImageEnumBuilder.swift b/Sources/Shark/ImageEnumBuilder.swift index d21f71a..6e41dfd 100644 --- a/Sources/Shark/ImageEnumBuilder.swift +++ b/Sources/Shark/ImageEnumBuilder.swift @@ -7,12 +7,12 @@ private enum ImageValue: Equatable, Comparable { func declaration(withBody body: String = "", indentLevel: Int) throws -> String { switch self { case .image(let name, let value): - return #"\#(String(indentLevel: indentLevel))public static var \#(name): UIImage { return UIImage(named:"\#(value)", in: \#(SharkEnumBuilder.topLevelEnumName).bundle, compatibleWith: nil)! }"# + return #"\#(String.indent(indentLevel))public static var \#(name): UIImage { return UIImage(named:"\#(value)", in: \#(SharkEnumBuilder.topLevelEnumName).bundle, compatibleWith: nil)! }"# case .namespace(let name): return #""" - \#(String(indentLevel: indentLevel))public enum \#(name) { + \#(String.indent(indentLevel))public enum \#(name) { \#(body) - \#(String(indentLevel: indentLevel))} + \#(String.indent(indentLevel))} """# } diff --git a/Sources/Shark/LocalizationEnumBuilder.swift b/Sources/Shark/LocalizationEnumBuilder.swift index 43a2e2f..d5af5fd 100644 --- a/Sources/Shark/LocalizationEnumBuilder.swift +++ b/Sources/Shark/LocalizationEnumBuilder.swift @@ -58,9 +58,9 @@ private enum LocalizationValue: Comparable { switch self { case .namespace(let name): result += #""" - \#(String(indentLevel: indentLevel))public enum \#(name) { + \#(String.indent(indentLevel))public enum \#(name) { \#(body) - \#(String(indentLevel: indentLevel))} + \#(String.indent(indentLevel))} """# case .localization(let name, let key, let value): let translationComment = value.mapLines { "/// \($0)" } @@ -73,7 +73,7 @@ private enum LocalizationValue: Comparable { if interpolatedTypes.isEmpty == false { result += interpolatedTypes.functionDeclaration(withName: name, key: key, indentLevel: indentLevel) } else { - result += #"\#(String(indentLevel: indentLevel))public static var \#(name): String { return NSLocalizedString("\#(key)", bundle: \#(SharkEnumBuilder.topLevelEnumName).bundle, comment: "") }"# + result += #"\#(String.indent(indentLevel))public static var \#(name): String { return NSLocalizedString("\#(key)", bundle: \#(SharkEnumBuilder.topLevelEnumName).bundle, comment: "") }"# } } return result @@ -172,9 +172,9 @@ extension Array where Element == LocalizationValue.InterpolationType { let formatValuesString = (1...count).map { "\(variableName)\($0)"}.joined(separator: ", ") return #""" - \#(String(indentLevel: indentLevel))public static func \#(name)(\#(argumentsString)) -> String { - \#(String(indentLevel:indentLevel + 1))return String(format: NSLocalizedString("\#(key)", bundle: \#(SharkEnumBuilder.topLevelEnumName).bundle, comment: ""), \#(formatValuesString)) - \#(String(indentLevel: indentLevel))} + \#(String.indent(indentLevel))public static func \#(name)(\#(argumentsString)) -> String { + \#(String.indent(indentLevel + 1))return String(format: NSLocalizedString("\#(key)", bundle: \#(SharkEnumBuilder.topLevelEnumName).bundle, comment: ""), \#(formatValuesString)) + \#(String.indent(indentLevel))} """# } } diff --git a/Sources/Shark/String+Extensions.swift b/Sources/Shark/String+Extensions.swift index 9c84763..e2519a1 100644 --- a/Sources/Shark/String+Extensions.swift +++ b/Sources/Shark/String+Extensions.swift @@ -9,9 +9,9 @@ extension String { "#sourceLocation", "#warning", "associativity", "convenience", "dynamic", "didSet", "final", "get", "infix", "indirect", "lazy", "left", "mutating", "none", "nonmutating", "optional", "override", "postfix", "precedence", "prefix", "Protocol", "required", "right", "set", "Type", "unowned", "weak", "willSet", "some", "__COLUMN__", "__FILE__", "__FUNCTION__", "__LINE__"] - - init(indentLevel: Int) { - self.init(repeating: " ", count: indentLevel * 4) + + static func indent(_ level: Int) -> String { + String(repeating: " ", count: level * 4) } var expandingTildeInPath: String { @@ -63,7 +63,7 @@ extension String { } func indented(withLevel level: Int) -> String { - return mapLines { String(indentLevel: level) + $0 } + return mapLines { String.indent(level) + $0 } } func mapLines(_ transform: (String) -> String) -> String { From 02d8bee384ed42747dff676fc7f07256e5a10ca9 Mon Sep 17 00:00:00 2001 From: Kaan Dedeoglu Date: Tue, 15 Dec 2020 11:55:47 +0100 Subject: [PATCH 4/7] Encapsulate bundle access and clean up --- Sources/Shark/ColorEnumBuilder.swift | 2 +- Sources/Shark/ImageEnumBuilder.swift | 2 +- Sources/Shark/LocalizationEnumBuilder.swift | 4 ++-- Sources/Shark/SharkEnumBuilder.swift | 15 +++++++++------ 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/Sources/Shark/ColorEnumBuilder.swift b/Sources/Shark/ColorEnumBuilder.swift index 64ca9db..a8f9283 100644 --- a/Sources/Shark/ColorEnumBuilder.swift +++ b/Sources/Shark/ColorEnumBuilder.swift @@ -4,7 +4,7 @@ private struct ColorValue: Equatable, Comparable { let name: String func declaration(indentLevel: Int) -> String { - return #"\#(String.indent(indentLevel))public static var \#(name.casenameSanitized): UIColor { return UIColor(named: "\#(name)", in: \#(SharkEnumBuilder.topLevelEnumName).bundle, compatibleWith: nil)! }"# + return #"\#(String.indent(indentLevel))public static var \#(name.casenameSanitized): UIColor { return UIColor(named: "\#(name)", in: bundle, compatibleWith: nil)! }"# } static func <(lhs: ColorValue, rhs: ColorValue) -> Bool { diff --git a/Sources/Shark/ImageEnumBuilder.swift b/Sources/Shark/ImageEnumBuilder.swift index 6e41dfd..c1cdcb6 100644 --- a/Sources/Shark/ImageEnumBuilder.swift +++ b/Sources/Shark/ImageEnumBuilder.swift @@ -7,7 +7,7 @@ private enum ImageValue: Equatable, Comparable { func declaration(withBody body: String = "", indentLevel: Int) throws -> String { switch self { case .image(let name, let value): - return #"\#(String.indent(indentLevel))public static var \#(name): UIImage { return UIImage(named:"\#(value)", in: \#(SharkEnumBuilder.topLevelEnumName).bundle, compatibleWith: nil)! }"# + return #"\#(String.indent(indentLevel))public static var \#(name): UIImage { return UIImage(named:"\#(value)", in: bundle, compatibleWith: nil)! }"# case .namespace(let name): return #""" \#(String.indent(indentLevel))public enum \#(name) { diff --git a/Sources/Shark/LocalizationEnumBuilder.swift b/Sources/Shark/LocalizationEnumBuilder.swift index d5af5fd..681f08f 100644 --- a/Sources/Shark/LocalizationEnumBuilder.swift +++ b/Sources/Shark/LocalizationEnumBuilder.swift @@ -73,7 +73,7 @@ private enum LocalizationValue: Comparable { if interpolatedTypes.isEmpty == false { result += interpolatedTypes.functionDeclaration(withName: name, key: key, indentLevel: indentLevel) } else { - result += #"\#(String.indent(indentLevel))public static var \#(name): String { return NSLocalizedString("\#(key)", bundle: \#(SharkEnumBuilder.topLevelEnumName).bundle, comment: "") }"# + result += #"\#(String.indent(indentLevel))public static var \#(name): String { return NSLocalizedString("\#(key)", bundle: bundle, comment: "") }"# } } return result @@ -173,7 +173,7 @@ extension Array where Element == LocalizationValue.InterpolationType { return #""" \#(String.indent(indentLevel))public static func \#(name)(\#(argumentsString)) -> String { - \#(String.indent(indentLevel + 1))return String(format: NSLocalizedString("\#(key)", bundle: \#(SharkEnumBuilder.topLevelEnumName).bundle, comment: ""), \#(formatValuesString)) + \#(String.indent(indentLevel + 1))return String(format: NSLocalizedString("\#(key)", bundle: bundle, comment: ""), \#(formatValuesString)) \#(String.indent(indentLevel))} """# } diff --git a/Sources/Shark/SharkEnumBuilder.swift b/Sources/Shark/SharkEnumBuilder.swift index defac17..1fe027c 100644 --- a/Sources/Shark/SharkEnumBuilder.swift +++ b/Sources/Shark/SharkEnumBuilder.swift @@ -1,7 +1,5 @@ enum SharkEnumBuilder { - static var topLevelEnumName = "Shark" static func sharkEnumString(forOptions options: Options) throws -> String { - SharkEnumBuilder.topLevelEnumName = options.topLevelEnumName let resourcePaths = try XcodeProjectHelper(options: options).resourcePaths() let imagesString = try ImageEnumBuilder.imageEnumString(forFilesAtPaths: resourcePaths.assetsPaths, topLevelName: "I") @@ -9,12 +7,17 @@ enum SharkEnumBuilder { let localizationsString = try LocalizationEnumBuilder.localizationsEnumString(forFilesAtPaths: resourcePaths.localizationPaths, topLevelName: "L") let fontsString = try FontEnumBuilder.fontsEnumString(forFilesAtPaths: resourcePaths.fontPaths, topLevelName: "F") - let declarations = [imagesString, colorsString, localizationsString, fontsString].compactMap({ $0?.indented(withLevel: 1) }).joined(separator: "\n\n") + let declarations = [imagesString, colorsString, localizationsString, fontsString] + .compactMap({ $0?.indented(withLevel: 1) }) + .joined(separator: "\n\n") return """ - public enum \(topLevelEnumName) { - private class Custom {} - static var bundle: Bundle { return Bundle(for: Custom.self) } + public enum \(options.topLevelEnumName) { + private static let bundle: Bundle = { + class Custom {} + return Bundle(for: Custom.self) + }() + \(declarations) } """ From 6932b177c656a25ec135c45b6657da322a98a6af Mon Sep 17 00:00:00 2001 From: Kaan Dedeoglu Date: Wed, 16 Dec 2020 12:03:31 +0100 Subject: [PATCH 5/7] More cleanup --- Sources/Shark/FontEnumBuilder.swift | 5 ++--- Sources/Shark/LocalizationEnumBuilder.swift | 6 +++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Sources/Shark/FontEnumBuilder.swift b/Sources/Shark/FontEnumBuilder.swift index 97ab9ab..34877fb 100644 --- a/Sources/Shark/FontEnumBuilder.swift +++ b/Sources/Shark/FontEnumBuilder.swift @@ -15,11 +15,10 @@ private struct FontValue: Equatable, Comparable { enum FontEnumBuilder { static func fontsEnumString(forFilesAtPaths paths: [String], topLevelName: String) throws -> String? { - let fontValues = paths.compactMap { path -> FontValue? in + let fontValues: [FontValue] = paths.compactMap { path in guard let data = try? Data(contentsOf: URL(fileURLWithPath: path)), - let provider = CGDataProvider(data: data as CFData), - let font = CGFont(provider), + let font = CGDataProvider(data: data as CFData).flatMap(CGFont.init), let fullName = font.fullName as String?, let postScriptName = font.postScriptName as String? else { return nil } diff --git a/Sources/Shark/LocalizationEnumBuilder.swift b/Sources/Shark/LocalizationEnumBuilder.swift index 681f08f..1065c9e 100644 --- a/Sources/Shark/LocalizationEnumBuilder.swift +++ b/Sources/Shark/LocalizationEnumBuilder.swift @@ -105,10 +105,10 @@ extension LocalizationValue: SanitizableValue { } } -enum LocalizationBuilderError: Error { +enum LocalizationBuilderError: LocalizedError { case invalidLocalizableStringsFile(path: String) - - var localizedDescription: String { + + var errorDescription: String? { switch self { case .invalidLocalizableStringsFile(let path): return "Invalid .strings file at \(path)" From 53128bdf4689959ab156b62a26c7a62f58edc025 Mon Sep 17 00:00:00 2001 From: Kaan Dedeoglu Date: Wed, 16 Dec 2020 17:13:10 +0100 Subject: [PATCH 6/7] Reorder to image - color - font - localization. Trim space-only lines. --- Sources/Shark/SharkEnumBuilder.swift | 2 +- Sources/Shark/String+Extensions.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/Shark/SharkEnumBuilder.swift b/Sources/Shark/SharkEnumBuilder.swift index 1fe027c..b5b0de2 100644 --- a/Sources/Shark/SharkEnumBuilder.swift +++ b/Sources/Shark/SharkEnumBuilder.swift @@ -7,7 +7,7 @@ enum SharkEnumBuilder { let localizationsString = try LocalizationEnumBuilder.localizationsEnumString(forFilesAtPaths: resourcePaths.localizationPaths, topLevelName: "L") let fontsString = try FontEnumBuilder.fontsEnumString(forFilesAtPaths: resourcePaths.fontPaths, topLevelName: "F") - let declarations = [imagesString, colorsString, localizationsString, fontsString] + let declarations = [imagesString, colorsString, fontsString, localizationsString] .compactMap({ $0?.indented(withLevel: 1) }) .joined(separator: "\n\n") diff --git a/Sources/Shark/String+Extensions.swift b/Sources/Shark/String+Extensions.swift index e2519a1..086ffc8 100644 --- a/Sources/Shark/String+Extensions.swift +++ b/Sources/Shark/String+Extensions.swift @@ -63,7 +63,7 @@ extension String { } func indented(withLevel level: Int) -> String { - return mapLines { String.indent(level) + $0 } + mapLines { $0.allSatisfy(\.isWhitespace) ? $0 : String.indent(level) + $0 } } func mapLines(_ transform: (String) -> String) -> String { From ff2572e9c2125f2e174b2ac65e96620e42f1a93a Mon Sep 17 00:00:00 2001 From: Kaan Dedeoglu Date: Wed, 16 Dec 2020 17:13:27 +0100 Subject: [PATCH 7/7] Improve README --- README.md | 348 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 217 insertions(+), 131 deletions(-) diff --git a/README.md b/README.md index e080712..ce432d7 100644 --- a/README.md +++ b/README.md @@ -1,47 +1,50 @@ # Shark -[![Build Status](https://travis-ci.org/kaandedeoglu/Shark.svg?branch=master)](https://travis-ci.org/kaandedeoglu/Shark) -> Shark has been rewritten from scratch and now requires Xcode 10.2 / Swift 5 +[![Build Status](https://travis-ci.org/kaandedeoglu/Shark.svg?branch=master)](https://travis-ci.org/kaandedeoglu/Shark) -Shark is a Swift command line tool that generates type safe enums for your image assets, color assets and localizations. +Shark is a Swift command line tool that generates type safe enums for your image assets, color assets, localizations and fonts. Because Shark reads your .xcodeproj to find these assets, the setup is extremely simple. ## Installation -#### Brew: + +### Brew + ```bash brew install kaandedeoglu/formulae/shark ``` -#### Manually: +### Manually + Clone the project, then do: + ```bash -> swift build -c release --disable-sandbox +> swift build -c release > cp ./build/release/Shark /usr/local/bin ``` You can then verify the installation by doing ```bash -shark --help +> shark --help ``` ## Setup -The easiest way to set up Shark is by adding a new Run Script phase to your build phases. This build phase should ideally run before the `Compile Sources` phase. +- Add a new Run Script phase to your target's build phases. This build phase should ideally run before the `Compile Sources` phase. The script body should look like the following: -- Add a new Run Script phase to your target. The script body should look like: ```bash if [ -x "$(command -v shark)" ]; then shark $PROJECT_FILE_PATH $PROJECT_DIR/$PROJECT_NAME fi ``` - the `if/else` block makes sure that Shark is run only if it's installed on the current machine. + the `if/fi` block makes sure that Shark runs only if it's installed on the current machine. - Build your project. You should now see a file named `Shark.swift` in your project folder. -- Add this file to your project. Voila! The file will be updated every time the project is built! +- Add this file to your target. Voila! `Shark.swift` will be updated every time you build the project. - Alternatively you can do the following: + ```bash # Write to a specific file called MyAssets.swift shark $PROJECT_FILE_PATH $PROJECT_DIR/$PROJECT_NAME/MyAssets.swift @@ -52,61 +55,146 @@ The easiest way to set up Shark is by adding a new Run Script phase to your buil shark $PROJECT_FILE_PATH $PROJECT_DIR/$PROJECT_NAME/Utility/MyAssets.swift ``` -## Flags -Shark also accepts some flags to configure behavior +## Options + +Shark also accepts the following command line options to configure behavior + +### --name -#### --name By default, the top level enum everything else lives under is called - you guessed it - `Shark`. You can change this by using the `--name` flag. ```bash shark $PROJECT_FILE_PATH $PROJECT_DIR/$PROJECT_NAME --name Assets ``` -#### --locale +### --locale + By default, Shark will try to find English localizations to generate the localizations enum. If there are no English .strings file in your project, or you'd like Shark to take another localization as base, you can specify the language code with the `--locale` flag. ```bash # Use Spanish localizations for generation shark $PROJECT_FILE_PATH $PROJECT_DIR/$PROJECT_NAME --locale es ``` -#### --target +### --target + In case your Xcode project has multiple application targets, you should specify which one Shark should look at by using the `--target` flag. ```bash shark $PROJECT_FILE_PATH $PROJECT_DIR/$PROJECT_NAME --target MyAppTarget ``` -#### --help +### --help + Prints the overview, example usage and available flags to the console. ## Sample output Below is a sample output generated by Shark. As you can see, the top level `enum Shark` contains three enums inside. `I` (Images), `L` (Localizations) and `C` (Colors). Example usage looks like +```swift +// Generated by Shark +public enum Shark { + private static let bundle: Bundle = { + class Custom {} + return Bundle(for: Custom.self) + }() + + public enum I { + public enum Button { + public static var profile: UIImage { return UIImage(named:"report_user", in: bundle, compatibleWith: nil)! } + public static var cancel: UIImage { return UIImage(named:"battery_swap_maintained", in: bundle, compatibleWith: nil)! } + public static var user_avatar: UIImage { return UIImage(named:"damage_check", in: bundle, compatibleWith: nil)! } + } + } + + public enum C { + public static var blue1: UIColor { return UIColor(named: "blue1", in: bundle, compatibleWith: nil)! } + public static var blue2: UIColor { return UIColor(named: "blue2", in: bundle, compatibleWith: nil)! } + public static var gray1: UIColor { return UIColor(named: "gray1", in: bundle, compatibleWith: nil)! } + public static var gray2: UIColor { return UIColor(named: "gray2", in: bundle, compatibleWith: nil)! } + public static var green1: UIColor { return UIColor(named: "green1", in: bundle, compatibleWith: nil)! } + public static var green2: UIColor { return UIColor(named: "green2", in: bundle, compatibleWith: nil)! } + } + + public enum F { + public static func gothamBold(ofSize size: CGFloat) -> UIFont { return UIFont(name: "Gotham-Bold", size: size)! } + public static func gothamMedium(ofSize size: CGFloat) -> UIFont { return UIFont(name: "Gotham-Medium", size: size)! } + public static func gothamRegular(ofSize size: CGFloat) -> UIFont { return UIFont(name: "Gotham-Regular", size: size)! } + } + + public enum L { + public enum button { + /// Login + public static var login: String { return NSLocalizedString("button.login", bundle: bundle, comment: "") } + + /// Logout + public static var logout: String { return NSLocalizedString("button.logout", bundle: bundle, comment: "") } + } + + public enum login { + /// Please log in to continue + public static var title: String { return NSLocalizedString("login.title", bundle: bundle, comment: "") } + + /// Skip login and continue + public static var skip: String { return NSLocalizedString("login.skip", bundle: bundle, comment: "") } + + public enum error { + /// Login failed + public static var title: String { return NSLocalizedString("login.error.title", bundle: bundle, comment: "") } + + /// Operation failed with error: %@ + public static func message(_ value1: String) -> String { + return String(format: NSLocalizedString("login.error.message", bundle: bundle, comment: ""), value1) + } + } + } + } +} + +// At the call site +imageView.image = Shark.I.Button.profile +label.font = Shark.F.gothamBold(ofSize: 16.0) +label.text = Shark.L.login.title +view.backgroundColor = Shark.C.green1 + +// You can also make it prettier with typealiases +typealias I = Shark.I +typealias C = Shark.C +typealias F = Shark.F +typealias L = Shark.L + +imageView.image = I.Button.profile +label.font = F.gothamBold(ofSize: 16.0) +label.text = L.login.error.message("I disobeyed my masters") +view.backgroundColor = C.green1 +``` + ```swift imageView.image = Shark.I.TaskIcons.task_icon_clean +label.font = Shark.F.gothamBold(ofSize: 16.0) label.text = Shark.L.button.login view.backgroundColor = Shark.C.blue1 ``` There are a few things to notice: + - Image assets are namespaced by folder. For example all the images in your `.xcassets` folder that are contained in a folder called `TaskIcons` will be listed under an enum called `TaskIcons`. -- Localizations are namespaced with separators. Currently Shark uses the dot symbol `.` as the separator. - For example given two strings `"button.login"` and `"button.logout"`, Shark will generate the following: +- Localizations are namespaced with separators. Currently Shark uses the dot symbol `.` as the separator. + As you can see localization keys are recursively namespaced until we get to the last component. ```swift public enum L { public enum button { /// Login - public static var login: String { return NSLocalizedString("button.login", comment: "") } + public static var login: String { return NSLocalizedString("button.login", bundle: bundle, comment: "")) } /// Logout - public static var logout: String { return NSLocalizedString("button.logout", comment: "") } + public static var logout: String { return NSLocalizedString("button.logout", bundle: bundle, comment: "")) } } } ``` -You can see the sample below: +You can see an example `Shark.swift` below: ```swift // Shark.swift @@ -114,149 +202,147 @@ You can see the sample below: import UIKit +// swiftlint:disable all public enum Shark { + private static let bundle: Bundle = { + class Custom {} + return Bundle(for: Custom.self) + }() + public enum I { + public enum MapIcons { + public static var battery_swap_available: UIImage { return UIImage(named:"battery_swap_available", in: bundle, compatibleWith: nil)! } + public static var battery_swap_maintained: UIImage { return UIImage(named:"battery_swap_maintained", in: bundle, compatibleWith: nil)! } + public static var damage_check: UIImage { return UIImage(named:"damage_check", in: bundle, compatibleWith: nil)! } + } + public enum TaskIcons { - public static var task_icon_clean: UIImage { return UIImage(named:"task_icon_clean")! } - public static var task_icon_fallback: UIImage { return UIImage(named:"task_icon_fallback")! } - public static var task_icon_replacebatteries: UIImage { return UIImage(named:"task_icon_replacebatteries")! } - public static var task_icon_replacezipper: UIImage { return UIImage(named:"task_icon_replacezipper")! } + public static var task_icon_clean: UIImage { return UIImage(named:"task_icon_clean", in: bundle, compatibleWith: nil)! } + public static var task_icon_fallback: UIImage { return UIImage(named:"task_icon_fallback", in: bundle, compatibleWith: nil)! } + public static var task_icon_replacebatteries: UIImage { return UIImage(named:"task_icon_replacebatteries", in: bundle, compatibleWith: nil)! } + public static var task_icon_replacezipper: UIImage { return UIImage(named:"task_icon_replacezipper", in: bundle, compatibleWith: nil)! } } - - public static var back_image: UIImage { return UIImage(named:"back_image")! } - public static var icon_banner_retry: UIImage { return UIImage(named:"icon-banner-retry")! } - public static var icon_no_internet: UIImage { return UIImage(named:"icon-no-internet")! } - public static var icon_paused_shift: UIImage { return UIImage(named:"icon-paused-shift")! } - public static var input_icon_camera: UIImage { return UIImage(named:"input-icon-camera")! } - public static var input_icon_invalid: UIImage { return UIImage(named:"input-icon-invalid")! } - public static var input_icon_show_password: UIImage { return UIImage(named:"input-icon-show-password")! } - public static var map_location_on: UIImage { return UIImage(named:"map-location-on")! } - public static var menu_icon: UIImage { return UIImage(named:"menu-icon")! } - public static var modal_checkbox_active: UIImage { return UIImage(named:"modal-checkbox-active")! } - public static var modal_checkbox_inactive: UIImage { return UIImage(named:"modal-checkbox-inactive")! } - public static var modal_icon_cancel: UIImage { return UIImage(named:"modal-icon-cancel")! } - public static var modal_icon_navigate: UIImage { return UIImage(named:"modal-icon-navigate")! } - } + + public static var apple_maps: UIImage { return UIImage(named:"apple-maps", in: bundle, compatibleWith: nil)! } + public static var back_image: UIImage { return UIImage(named:"back_image", in: bundle, compatibleWith: nil)! } + public static var google_maps: UIImage { return UIImage(named:"google-maps", in: bundle, compatibleWith: nil)! } + public static var icon_banner_retry: UIImage { return UIImage(named:"icon-banner-retry", in: bundle, compatibleWith: nil)! } + public static var icon_battery_level: UIImage { return UIImage(named:"icon-battery-level", in: bundle, compatibleWith: nil)! } + public static var icon_cancel_modal_cant_access: UIImage { return UIImage(named:"icon-cancel-modal-cant-access", in: bundle, compatibleWith: nil)! } + public static var icon_cancel_modal_cant_find: UIImage { return UIImage(named:"icon-cancel-modal-cant-find", in: bundle, compatibleWith: nil)! } + } public enum C { - public static var backgroundColor: UIColor { return UIColor(named: "backgroundColor")! } - public static var blue1: UIColor { return UIColor(named: "blue1")! } - public static var blue2: UIColor { return UIColor(named: "blue2")! } - public static var blue3: UIColor { return UIColor(named: "blue3")! } - public static var gray1: UIColor { return UIColor(named: "gray1")! } - public static var gray2: UIColor { return UIColor(named: "gray2")! } - public static var gray3: UIColor { return UIColor(named: "gray3")! } - public static var green1: UIColor { return UIColor(named: "green1")! } - public static var green2: UIColor { return UIColor(named: "green2")! } - public static var green3: UIColor { return UIColor(named: "green3")! } - public static var red1: UIColor { return UIColor(named: "red1")! } - public static var red2: UIColor { return UIColor(named: "red2")! } - public static var red3: UIColor { return UIColor(named: "red3")! } - public static var violet1: UIColor { return UIColor(named: "violet1")! } - public static var violet2: UIColor { return UIColor(named: "violet2")! } - public static var violet3: UIColor { return UIColor(named: "violet3")! } + public static var backgroundColor: UIColor { return UIColor(named: "backgroundColor", in: bundle, compatibleWith: nil)! } + public static var blue1: UIColor { return UIColor(named: "blue1", in: bundle, compatibleWith: nil)! } + public static var blue2: UIColor { return UIColor(named: "blue2", in: bundle, compatibleWith: nil)! } + public static var blue3: UIColor { return UIColor(named: "blue3", in: bundle, compatibleWith: nil)! } + public static var gray1: UIColor { return UIColor(named: "gray1", in: bundle, compatibleWith: nil)! } + public static var gray2: UIColor { return UIColor(named: "gray2", in: bundle, compatibleWith: nil)! } + public static var gray3: UIColor { return UIColor(named: "gray3", in: bundle, compatibleWith: nil)! } + public static var green1: UIColor { return UIColor(named: "green1", in: bundle, compatibleWith: nil)! } + public static var green2: UIColor { return UIColor(named: "green2", in: bundle, compatibleWith: nil)! } + public static var green3: UIColor { return UIColor(named: "green3", in: bundle, compatibleWith: nil)! } + public static var red1: UIColor { return UIColor(named: "red1", in: bundle, compatibleWith: nil)! } + public static var red2: UIColor { return UIColor(named: "red2", in: bundle, compatibleWith: nil)! } + public static var red3: UIColor { return UIColor(named: "red3", in: bundle, compatibleWith: nil)! } + } + + public enum F { + public static func ibmPlexMono(ofSize size: CGFloat) -> UIFont { return UIFont(name: "IBMPlexMono", size: size)! } + public static func ibmPlexMonoMedium(ofSize size: CGFloat) -> UIFont { return UIFont(name: "IBMPlexMono-Medium", size: size)! } + public static func ibmPlexMonoSemibold(ofSize size: CGFloat) -> UIFont { return UIFont(name: "IBMPlexMono-SemiBold", size: size)! } + public static func ibmPlexSans(ofSize size: CGFloat) -> UIFont { return UIFont(name: "IBMPlexSans", size: size)! } + public static func ibmPlexSansBold(ofSize size: CGFloat) -> UIFont { return UIFont(name: "IBMPlexSans-Bold", size: size)! } + public static func ibmPlexSansMedium(ofSize size: CGFloat) -> UIFont { return UIFont(name: "IBMPlexSans-Medium", size: size)! } + public static func ibmPlexSansSemibold(ofSize size: CGFloat) -> UIFont { return UIFont(name: "IBMPlexSans-SemiBold", size: size)! } } public enum L { public enum banner { - public enum bluetooth_controller_failed { - /// Cannot create the ble connection to the scooter, this is most likely a backend issue, please contact your fleet operator. - public static var title: String { return NSLocalizedString("banner.bluetooth_controller_failed.title", comment: "") } - } - - public enum check_out_next_step_fetch_failed { - /// Check out succeeded, but fetching the next step failed, please try again. - public static var message: String { return NSLocalizedString("banner.check_out_next_step_fetch_failed.message", comment: "") } - } - public enum generic { /// Error - public static var title: String { return NSLocalizedString("banner.generic.title", comment: "") } + public static var title: String { return NSLocalizedString("banner.generic.title", bundle: bundle, comment: "") } } - + public enum invalid_credentials { /// Wrong email & password combination - public static var title: String { return NSLocalizedString("banner.invalid_credentials.title", comment: "") } - } - - public enum login { - /// Login request failed, please try again - public static var message: String { return NSLocalizedString("banner.login.message", comment: "") } - } - - public enum no_network_connection { - /// No internet connection - public static var title: String { return NSLocalizedString("banner.no_network_connection.title", comment: "") } + public static var title: String { return NSLocalizedString("banner.invalid_credentials.title", bundle: bundle, comment: "") } } - - public enum request_failed { - /// Request failed, please try again - public static var title: String { return NSLocalizedString("banner.request_failed.title", comment: "") } - } - - public enum retry { - /// Please try again - public static var message: String { return NSLocalizedString("banner.retry.message", comment: "") } + + public enum shift_end_failed { + /// You have no more tickets, but ending your shift failed, please try again. + public static var message: String { return NSLocalizedString("banner.shift_end_failed.message", bundle: bundle, comment: "") } + + /// Could not end your shift + public static var title: String { return NSLocalizedString("banner.shift_end_failed.title", bundle: bundle, comment: "") } } } - + public enum button { - /// Connecting... - public static var connecting: String { return NSLocalizedString("button.connecting", comment: "") } - - /// Login - public static var login: String { return NSLocalizedString("button.login", comment: "") } - - /// Logout - public static var logout: String { return NSLocalizedString("button.logout", comment: "") } - - /// Navigate - public static var navigate: String { return NSLocalizedString("button.navigate", comment: "") } - - /// Okay - public static var okay: String { return NSLocalizedString("button.okay", comment: "") } - - /// Remember my choice - public static var remember_my_choice: String { return NSLocalizedString("button.remember_my_choice", comment: "") } - - /// Retry - public static var retry: String { return NSLocalizedString("button.retry", comment: "") } - - /// Start Task - public static var start_task: String { return NSLocalizedString("button.start_task", comment: "") } - - /// Unlock - public static var unlock: String { return NSLocalizedString("button.unlock", comment: "") } - /// Unlocking... - public static var unlocking: String { return NSLocalizedString("button.unlocking", comment: "") } + public static var busy: String { return NSLocalizedString("button.busy", bundle: bundle, comment: "") } + + /// Cancel + public static var cancel: String { return NSLocalizedString("button.cancel", bundle: bundle, comment: "") } + + /// Create + public static var create: String { return NSLocalizedString("button.create", bundle: bundle, comment: "") } + + /// Done + public static var done: String { return NSLocalizedString("button.done", bundle: bundle, comment: "") } } - + public enum error { public enum generic { /// Error - public static var title: String { return NSLocalizedString("error.generic.title", comment: "") } + public static var title: String { return NSLocalizedString("error.generic.title", bundle: bundle, comment: "") } } - + public enum location_required { /// Turn on location services to allow Runner to determine your location - public static var message: String { return NSLocalizedString("error.location_required.message", comment: "") } - + public static var message: String { return NSLocalizedString("error.location_required.message", bundle: bundle, comment: "") } + /// Location required - public static var title: String { return NSLocalizedString("error.location_required.title", comment: "") } + public static var title: String { return NSLocalizedString("error.location_required.title", bundle: bundle, comment: "") } + } + + public enum unlock { + public enum battery { + /// Unlocking the scooter failed due to a battery error, please retry or open the trunk to proceed to the tasks screen + public static var message: String { return NSLocalizedString("error.unlock.battery.message", bundle: bundle, comment: "") } + } + + public enum battery_levels { + /// Unlock succeeded, but reporting battery levels failed, please try again + public static var message: String { return NSLocalizedString("error.unlock.battery_levels.message", bundle: bundle, comment: "") } + } + + public enum general { + /// Unlocking the scooter failed, please retry or open the trunk to proceed to the tasks screen + public static var message: String { return NSLocalizedString("error.unlock.general.message", bundle: bundle, comment: "") } + } + + public enum generic { + /// Unlock failed, please try again + public static var message: String { return NSLocalizedString("error.unlock.generic.message", bundle: bundle, comment: "") } + + /// Unlock failed + public static var title: String { return NSLocalizedString("error.unlock.generic.title", bundle: bundle, comment: "") } + } } - + /// Not a valid email address - public static var invalid_email: String { return NSLocalizedString("error.invalid_email", comment: "") } - + public static var invalid_email: String { return NSLocalizedString("error.invalid_email", bundle: bundle, comment: "") } + /// Password must be at least 8 characters /// include upper and lowercase letters, /// numbers and special characters - public static var invalid_password: String { return NSLocalizedString("error.invalid_password", comment: "") } - + public static var invalid_password: String { return NSLocalizedString("error.invalid_password", bundle: bundle, comment: "") } + /// Starting Runner failed with error: %@ public static func loading_failed(_ value1: String) -> String { - return String(format: NSLocalizedString("error.loading_failed", comment: ""), value1) + return String(format: NSLocalizedString("error.loading_failed", bundle: bundle, comment: ""), value1) } } } @@ -267,7 +353,7 @@ public enum Shark { The MIT License (MIT) -Copyright (c) 2019 Kaan Dedeoglu +Copyright (c) 2020 Kaan Dedeoglu Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal