├── .gitignore ├── Resources ├── jp.lproj │ ├── UIComponent.strings │ └── Localizable.strings ├── Base.lproj │ ├── UIComponent.strings │ └── Localizable.strings ├── Assets.xcassets │ ├── Contents.json │ ├── Monster.imageset │ │ └── Contents.json │ ├── Player.imageset │ │ └── Contents.json │ └── misc │ │ └── Projectile.imageset │ │ └── Contents.json ├── test_color.csv ├── test_simple_url_date.json ├── test_simple.json ├── test_simple_url_date_array.json ├── test_nested_simple_url_date.json ├── test_color.json ├── test_userdata.json ├── test_simple_json_model.json ├── test_multi_userdata.json └── Storyboards │ └── SingleScene.storyboard ├── Sources ├── StringsHelperToolkit │ ├── Strings.swift │ ├── Configuration.swift │ ├── StringsToken.swift │ ├── StringsParser.swift │ ├── StringsTokenizer.swift │ └── StringsHelperToolkit.swift ├── StoryboardHelperToolkit │ ├── Storyboard.swift │ ├── Configuration.swift │ ├── StoryboardParser.swift │ └── StoryboardHelperToolkit.swift ├── DevHelperToolkit │ ├── main.swift │ ├── DevHelperToolkit.swift │ └── ToolkitOptions.swift ├── ColorHelperToolkit │ ├── URL+Extension.swift │ ├── FileType.swift │ ├── Parser.swift │ ├── JSONParser.swift │ ├── Dictionary+Extension.swift │ ├── ColorDefinition.swift │ ├── Configuration.swift │ ├── ColorHelperToolkit.swift │ ├── Foundation+Color.swift │ └── CSVParser.swift ├── JSONHelperToolkit │ ├── Configuration.swift │ ├── Array+Extension.swift │ ├── Foundation+Types.swift │ ├── JSONHelperToolkit.swift │ ├── Dictionary+Conversion.swift │ └── ModelDefinition.swift ├── FoundationExtensions │ ├── String+Extension.swift │ └── FileManager+Extension.swift └── ImageHelperToolkit │ ├── URL+Extension.swift │ ├── Configuration.swift │ └── ImageHelperToolkit.swift ├── Tests ├── LinuxMain.swift ├── ImageHelperToolkitTests │ └── ImageHelpertToolkitTests.swift ├── StoryboardHelperToolkitTests │ └── StoryboardHelperToolkitTests.swift ├── ColorHelperToolkitTests │ └── ColorHelperToolkitTests.swift ├── StringsHelperToolkitTests │ └── StringsHelperToolkitTests.swift └── JSONHelperToolkitTests │ └── JSONHelperToolkitTests.swift ├── Package.swift └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | -------------------------------------------------------------------------------- /Resources/jp.lproj/UIComponent.strings: -------------------------------------------------------------------------------- 1 | "ok button" = "OK"; 2 | 3 | "cancel button" = "キャンセル"; 4 | -------------------------------------------------------------------------------- /Resources/Base.lproj/UIComponent.strings: -------------------------------------------------------------------------------- 1 | "ok button" = "OK"; 2 | 3 | "cancel button" = "Cancel"; 4 | -------------------------------------------------------------------------------- /Resources/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Sources/StringsHelperToolkit/Strings.swift: -------------------------------------------------------------------------------- 1 | struct Strings { 2 | let table: String 3 | let keys: [String] 4 | } 5 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import DevHelperToolkitTests 3 | 4 | XCTMain([ 5 | testCase(DevHelperToolkitTests.allTests), 6 | ]) 7 | -------------------------------------------------------------------------------- /Resources/test_color.csv: -------------------------------------------------------------------------------- 1 | theme_white,000000 2 | theme_black,ffffff 3 | theme_clear,00000000 4 | theme_gray,180 5 | theme_black_over,300 6 | web-safe-1,FF6699 7 | theme_gray2,0.8 8 | -------------------------------------------------------------------------------- /Resources/test_simple_url_date.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "simple url date model", 3 | "properties": { 4 | "image_url": "URL", 5 | "upload_date": "Date" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Resources/test_simple.json: -------------------------------------------------------------------------------- 1 | { 2 | "string": "string data", 3 | "integer": 15, 4 | "fraction": 1.5, 5 | "integer-as-fraction": 4.0, 6 | "boolean": false, 7 | "null": null 8 | } 9 | -------------------------------------------------------------------------------- /Resources/test_simple_url_date_array.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "simple url date array model", 3 | "properties": { 4 | "image_url": "[URL]", 5 | "upload_date": "[Date]" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Resources/test_nested_simple_url_date.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nested simple url date model", 3 | "properties": { 4 | "user": "String", 5 | "thumbnails": "[SimpleUrlDateModel]", 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Sources/StringsHelperToolkit/Configuration.swift: -------------------------------------------------------------------------------- 1 | public struct StringsToolkitConfiguration { 2 | let editorTabSpacing: String 3 | } 4 | 5 | internal let `default` = StringsToolkitConfiguration(editorTabSpacing: " ") 6 | -------------------------------------------------------------------------------- /Sources/StoryboardHelperToolkit/Storyboard.swift: -------------------------------------------------------------------------------- 1 | struct Storyboard { 2 | let name: String 3 | let scenes: [StoryboardScene] 4 | } 5 | 6 | struct StoryboardScene { 7 | let identifier: String 8 | let initial: Bool 9 | } 10 | -------------------------------------------------------------------------------- /Sources/DevHelperToolkit/main.swift: -------------------------------------------------------------------------------- 1 | // 2 | // main.swift 3 | // DevHelperToolkit 4 | // 5 | // Created by Naohiro Hamada on 2016/09/02. 6 | // 7 | // 8 | 9 | let toolkit = DevHelperToolkit(args: CommandLine.arguments) 10 | toolkit.run() 11 | -------------------------------------------------------------------------------- /Resources/test_color.json: -------------------------------------------------------------------------------- 1 | { 2 | "theme_white": "000000", 3 | "theme_black": "ffffff", 4 | "theme_clear": "00000000", 5 | "theme_gray": 180, 6 | "theme_black_over": 300, 7 | "web-safe-1": "FF6699", 8 | "theme_gray2": 0.8 9 | } 10 | -------------------------------------------------------------------------------- /Resources/test_userdata.json: -------------------------------------------------------------------------------- 1 | { 2 | "user_data": { 3 | "id": 1, 4 | "name": "Test User", 5 | "follow_ids": [ 6 | 10, 15, 20 7 | ], 8 | "follower_ids": [ 9 | 2, 3, 4, 5 10 | ], 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Resources/jp.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | DevHelperToolkit 4 | 5 | Created by Naohiro Hamada on 2016/11/02. 6 | 7 | */ 8 | 9 | "Localizable string" = "ローカライズ文字列"; 10 | 11 | "Localizable string with %@" = "%@ 付きローカライズ文字列"; 12 | 13 | "\"Escaped characters\t\"" = "\"エスケープ\t\""; 14 | -------------------------------------------------------------------------------- /Resources/Base.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | DevHelperToolkit 4 | 5 | Created by Naohiro Hamada on 2016/11/02. 6 | 7 | */ 8 | 9 | "Localizable string" = "Localizable string"; 10 | 11 | "Localizable string with %@" = "Localizable string with %@"; 12 | 13 | "\"Escaped characters\t\"" = "\"Escaped characters\t\""; 14 | -------------------------------------------------------------------------------- /Sources/StringsHelperToolkit/StringsToken.swift: -------------------------------------------------------------------------------- 1 | internal enum StringsTokenType { 2 | case value 3 | case assign 4 | case space 5 | case deliminator 6 | case comment 7 | } 8 | 9 | internal struct StringsToken { 10 | let type: StringsTokenType 11 | let value: String 12 | let startIndex: Int 13 | let endIndex: Int 14 | } 15 | -------------------------------------------------------------------------------- /Sources/ColorHelperToolkit/URL+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URL+Extension.swift 3 | // DevHelperToolkit 4 | // 5 | // Created by Naohiro Hamada on 2016/09/05. 6 | // 7 | // 8 | 9 | import Foundation 10 | 11 | extension URL { 12 | internal var fileType: FileType? { 13 | return FileType(rawValue: self.pathExtension.lowercased()) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Sources/JSONHelperToolkit/Configuration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Configuration.swift 3 | // DevHelperToolkit 4 | // 5 | // Created by Naohiro Hamada on 2016/08/26. 6 | // 7 | // 8 | 9 | public struct JSONHelperToolkitConfiguration { 10 | let editorTabSpacing: String 11 | } 12 | 13 | internal let `default` = JSONHelperToolkitConfiguration(editorTabSpacing: " ") 14 | -------------------------------------------------------------------------------- /Resources/test_simple_json_model.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "simple json model", 3 | "properties": { 4 | "signed integer": "Int", 5 | "unsigned integer": "UInt", 6 | "boolean value": "Bool", 7 | "string value": "String", 8 | "optional string value": "String?", 9 | "int array": "[Int]", 10 | "dictionary": "[String:Int]" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Sources/ColorHelperToolkit/FileType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FileType.swift 3 | // DevHelperToolkit 4 | // 5 | // Created by Naohiro Hamada on 2016/09/05. 6 | // 7 | // 8 | 9 | enum FileType: String { 10 | case json 11 | case csv 12 | 13 | func parser() -> Parser { 14 | switch self { 15 | case .json: 16 | return JSONParser() 17 | case .csv: 18 | return CSVParser() 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Resources/Assets.xcassets/Monster.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "monster.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "monster@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Resources/Assets.xcassets/Player.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "player.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "player@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Resources/Assets.xcassets/misc/Projectile.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "projectile.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "projectile@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Sources/ColorHelperToolkit/Parser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Parser.swift 3 | // DevHelperToolkit 4 | // 5 | // Created by Naohiro Hamada on 2016/09/05. 6 | // 7 | // 8 | 9 | import Foundation 10 | 11 | protocol Parser { 12 | func parse(filepath: String) -> [ColorDefinition] 13 | 14 | func parse(url: URL) -> [ColorDefinition] 15 | } 16 | 17 | extension Parser { 18 | func parse(filepath: String) -> [ColorDefinition] { 19 | let url = URL(fileURLWithPath: filepath) 20 | return parse(url: url) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Tests/ImageHelperToolkitTests/ImageHelpertToolkitTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import ImageHelperToolkit 3 | 4 | class ImageHelperToolkitTests: XCTestCase { 5 | 6 | func testImageAssets() { 7 | let filepath = "./Resources/Assets.xcassets" 8 | ImageHelperToolkit.shared.generate(from: filepath, to: "./Resources") 9 | } 10 | 11 | static var allTests : [(String, (ImageHelperToolkitTests) -> () throws -> Void)] { 12 | return [ 13 | ("testImageAssets", testImageAssets), 14 | ] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Sources/ColorHelperToolkit/JSONParser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JSONParser.swift 3 | // DevHelperToolkit 4 | // 5 | // Created by Naohiro Hamada on 2016/09/05. 6 | // 7 | // 8 | 9 | import Foundation 10 | 11 | final class JSONParser: Parser { 12 | func parse(url: URL) -> [ColorDefinition] { 13 | guard let data = try? Data(contentsOf: url) else { 14 | abort() 15 | } 16 | guard let jsonObject = try? JSONSerialization.jsonObject(with: data, options: []), 17 | let dic = jsonObject as? [String:Any] else { 18 | abort() 19 | } 20 | return dic.colodModels 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Resources/test_multi_userdata.json: -------------------------------------------------------------------------------- 1 | { 2 | "user_data": [ 3 | { 4 | "id": 1, 5 | "name": "Test User", 6 | "follow_ids": [ 7 | 10, 15, 20 8 | ], 9 | "follower_ids": [ 10 | 2, 3, 4, 5 11 | ], 12 | }, 13 | { 14 | "id": 2, 15 | "name": "Test User 2", 16 | "follow_ids": [ 17 | 10, 15, 20 18 | ], 19 | "follower_ids": [ 20 | 2, 3, 4, 5 21 | ], 22 | }, 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /Tests/StoryboardHelperToolkitTests/StoryboardHelperToolkitTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import StoryboardHelperToolkit 3 | 4 | class StoryboardHelperToolkitTests: XCTestCase { 5 | 6 | func testParseSingleScene() { 7 | let filepath = "./Resources/Storyboards/SingleScene.storyboard" 8 | let storyboard = StoryboardParser.parse(filepath) 9 | 10 | XCTAssert(storyboard.scenes.count == 1) 11 | XCTAssert(storyboard.name == "SingleScene") 12 | } 13 | 14 | static var allTests : [(String, (StoryboardHelperToolkitTests) -> () throws -> Void)] { 15 | return [ 16 | ("testParseSingleScene", testParseSingleScene), 17 | ] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | import PackageDescription 2 | 3 | let package = Package( 4 | name: "DevHelperToolkit", 5 | targets: [ 6 | Target(name: "DevHelperToolkit", 7 | dependencies: ["JSONHelperToolkit", "ColorHelperToolkit", "ImageHelperToolkit", "StoryboardHelperToolkit", "StringsHelperToolkit"]), 8 | Target(name: "JSONHelperToolkit", dependencies: ["FoundationExtensions"]), 9 | Target(name: "ImageHelperToolkit", dependencies: ["FoundationExtensions"]), 10 | Target(name: "StoryboardHelperToolkit", dependencies: ["FoundationExtensions"]), 11 | Target(name: "StringsHelperToolkit", dependencies: ["FoundationExtensions"]), 12 | Target(name: "FoundationExtensions") 13 | ], 14 | exclude: ["Resources"] 15 | ) 16 | -------------------------------------------------------------------------------- /Sources/FoundationExtensions/String+Extension.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public extension String { 4 | func lowerCamelCased(skip: Bool = false) -> String { 5 | if skip { 6 | return self 7 | } 8 | if self.isEmpty { 9 | return "" 10 | } 11 | let comps = self.components(separatedBy: CharacterSet(charactersIn: " -_")) 12 | return comps.dropFirst().reduce(comps[0].lowercased(), { $0 + $1.capitalized }) 13 | } 14 | 15 | func upperCamelCased() -> String { 16 | if self.isEmpty { 17 | return "" 18 | } 19 | let comps = self.components(separatedBy: CharacterSet(charactersIn: " -_")) 20 | return comps.reduce("", { $0 + $1.capitalized }) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/ImageHelperToolkit/URL+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URL+Extension.swift 3 | // DevHelperToolkit 4 | // 5 | // Created by Naohiro Hamada on 2016/09/15. 6 | // 7 | // 8 | 9 | import Foundation 10 | import FoundationExtensions 11 | 12 | extension URL { 13 | var propertyName: String { 14 | let name = lastPathComponent.replacingOccurrences(of: ".\(pathExtension)", with: "") 15 | return name.lowerCamelCased() 16 | } 17 | 18 | var enumName: String { 19 | let name = lastPathComponent.replacingOccurrences(of: ".\(pathExtension)", with: "") 20 | return name.lowerCamelCased() 21 | } 22 | 23 | var assetName: String { 24 | return lastPathComponent.replacingOccurrences(of: ".\(pathExtension)", with: "") 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Tests/ColorHelperToolkitTests/ColorHelperToolkitTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import ColorHelperToolkit 3 | 4 | class ColorHelperToolkitTests: XCTestCase { 5 | 6 | func testReadFromJson() { 7 | let filepath = "./Resources/test_color.json" 8 | ColorHelperToolkit.shared.generate(from: filepath, to: "./Resources") 9 | } 10 | 11 | func testReadFromCsv() { 12 | let filepath = "./Resources/test_color.csv" 13 | ColorHelperToolkit.shared.generate(from: filepath, to: "./Resources") 14 | } 15 | 16 | static var allTests : [(String, (ColorHelperToolkitTests) -> () throws -> Void)] { 17 | return [ 18 | ("testReadFromJson", testReadFromJson), 19 | ("testReadFromCsv", testReadFromCsv), 20 | ] 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Tests/StringsHelperToolkitTests/StringsHelperToolkitTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import StringsHelperToolkit 3 | 4 | class StringsHelperToolkitTests: XCTestCase { 5 | 6 | func testParsser() { 7 | let filepath = "./Resources/Base.lproj/Localizable.strings" 8 | let stringsDictionary = StringsParser.parse(at: filepath) 9 | 10 | XCTAssert(stringsDictionary.count == 3) 11 | } 12 | 13 | func testToolkit() { 14 | let filepath = "./Resources" 15 | let stringsDictionary = StringsHelperToolkit.shared.generate(from: filepath, to: "./Resources") 16 | } 17 | 18 | static var allTests : [(String, (StringsHelperToolkitTests) -> () throws -> Void)] { 19 | return [ 20 | ("testParsser", testParsser), 21 | ] 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/ColorHelperToolkit/Dictionary+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Dictionary+Extension.swift 3 | // DevHelperToolkit 4 | // 5 | // Created by Naohiro Hamada on 2016/09/05. 6 | // 7 | // 8 | 9 | import Foundation 10 | 11 | extension Dictionary { 12 | var colodModels: [ColorDefinition] { 13 | let models:[ColorDefinition] = self.map { 14 | let name = String(describing: $0.key) 15 | switch $0.value { 16 | case is ColorValueConvertiable: 17 | guard let value = $0.value as? ColorValueConvertiable else { 18 | fatalError("Failed to cast") 19 | } 20 | let color = value.colorValue 21 | return ColorDefinition.init(name: name, colorValue: color) 22 | default: 23 | print($0.value) 24 | fatalError("Invalid data type") 25 | } 26 | } 27 | return models 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Sources/JSONHelperToolkit/Array+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Array+Extension.swift 3 | // DevHelperToolkit 4 | // 5 | // Created by Naohiro Hamada on 2016/08/26. 6 | // 7 | // 8 | 9 | import Foundation 10 | 11 | extension Array { 12 | internal var validJsonArray: Bool { 13 | guard let types = try? self.map({ Mirror(reflecting: $0).description }) 14 | .reduce([String:Int](), { 15 | var result = $0 16 | result[$1] = $0.keys.contains($1) ? $0[$1]! + 1 : 0 17 | return result 18 | } ) else { 19 | fatalError("Failed to extract type in array") 20 | } 21 | return types.count == 1 22 | } 23 | 24 | internal var elementSwiftType: ModelType { 25 | guard validJsonArray else { 26 | fatalError("") 27 | } 28 | guard let value = first as? JSONValueTypeAnnotetable else { 29 | fatalError("Cannot annotate type") 30 | } 31 | return value.swiftType 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Sources/ColorHelperToolkit/ColorDefinition.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ColorDefinition.swift 3 | // DevHelperToolkit 4 | // 5 | // Created by Naohiro Hamada on 2016/09/05. 6 | // 7 | // 8 | 9 | import Foundation 10 | 11 | struct ColorDefinition { 12 | let name: String 13 | let red: Double 14 | let green: Double 15 | let blue: Double 16 | let alpha: Double 17 | 18 | init(name: String, red: Double, green: Double, blue: Double, alpha: Double) { 19 | self.name = name 20 | self.red = red 21 | self.green = green 22 | self.blue = blue 23 | self.alpha = alpha 24 | } 25 | 26 | init(name: String, colorValue: ColorValue) { 27 | self.init(name: name, red: colorValue.red, green: colorValue.green, blue: colorValue.blue, alpha: colorValue.alpha) 28 | } 29 | 30 | func initializeStatement(platform: ColorHelperToolkitConfiguration.TargetPlatform) -> String { 31 | return "\(platform.className)(red: \(red), green: \(green), blue: \(blue), alpha: \(alpha))" 32 | } 33 | 34 | var propertyName: String { 35 | let comps = name.components(separatedBy: CharacterSet(charactersIn: " -_")) 36 | return comps.dropFirst().reduce(comps[0], { $0 + $1.capitalized }) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Sources/ColorHelperToolkit/Configuration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Configuration.swift 3 | // DevHelperToolkit 4 | // 5 | // Created by Naohiro Hamada on 2016/09/05. 6 | // 7 | // 8 | 9 | import Foundation 10 | 11 | public struct ColorHelperToolkitConfiguration { 12 | public enum TargetPlatform: String { 13 | case ios 14 | case osx 15 | 16 | var className: String { 17 | switch self { 18 | case .ios: 19 | return "UIColor" 20 | case .osx: 21 | return "NSColor" 22 | } 23 | } 24 | 25 | var framework: String { 26 | switch self { 27 | case .ios: 28 | return "UIKit" 29 | case .osx: 30 | return "AppKit" 31 | } 32 | } 33 | } 34 | 35 | let editorTabSpacing: String 36 | let platform: TargetPlatform 37 | 38 | public static func configuration(platform: TargetPlatform) -> ColorHelperToolkitConfiguration { 39 | return ColorHelperToolkitConfiguration(editorTabSpacing: `default`.editorTabSpacing, platform: platform) 40 | } 41 | } 42 | 43 | internal let `default` = ColorHelperToolkitConfiguration(editorTabSpacing: " ", platform: .ios) 44 | -------------------------------------------------------------------------------- /Sources/ImageHelperToolkit/Configuration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Configuration.swift 3 | // DevHelperToolkit 4 | // 5 | // Created by Naohiro Hamada on 2016/09/13. 6 | // 7 | // 8 | 9 | import Foundation 10 | 11 | public struct ImageHelperToolkitConfiguration { 12 | public enum TargetPlatform: String { 13 | case ios 14 | case osx 15 | 16 | var className: String { 17 | switch self { 18 | case .ios: 19 | return "UIImage" 20 | case .osx: 21 | return "NSImage" 22 | } 23 | } 24 | 25 | var framework: String { 26 | switch self { 27 | case .ios: 28 | return "UIKit" 29 | case .osx: 30 | return "AppKit" 31 | } 32 | } 33 | } 34 | 35 | let editorTabSpacing: String 36 | let platform: TargetPlatform 37 | 38 | public static func configuration(platform: TargetPlatform) -> ImageHelperToolkitConfiguration { 39 | return ImageHelperToolkitConfiguration(editorTabSpacing: `default`.editorTabSpacing, platform: platform) 40 | } 41 | } 42 | 43 | internal let `default` = ImageHelperToolkitConfiguration(editorTabSpacing: " ", platform: .ios) 44 | -------------------------------------------------------------------------------- /Sources/StoryboardHelperToolkit/Configuration.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public struct StoryboardHelperToolkitConfiguration { 4 | public enum TargetPlatform: String { 5 | case ios 6 | case osx 7 | 8 | var className: String { 9 | switch self { 10 | case .ios: 11 | return "UIStoryboard" 12 | case .osx: 13 | return "NSStoryboard" 14 | } 15 | } 16 | 17 | var framework: String { 18 | switch self { 19 | case .ios: 20 | return "UIKit" 21 | case .osx: 22 | return "AppKit" 23 | } 24 | } 25 | 26 | var viewController: String { 27 | switch self { 28 | case .ios: 29 | return "UIViewController" 30 | case .osx: 31 | return "NSViewController" 32 | } 33 | } 34 | } 35 | 36 | let editorTabSpacing: String 37 | let platform: TargetPlatform 38 | 39 | public static func configuration(platform: TargetPlatform) -> StoryboardHelperToolkitConfiguration { 40 | return StoryboardHelperToolkitConfiguration(editorTabSpacing: `default`.editorTabSpacing, platform: platform) 41 | } 42 | } 43 | 44 | internal let `default` = StoryboardHelperToolkitConfiguration(editorTabSpacing: " ", platform: .ios) 45 | -------------------------------------------------------------------------------- /Sources/StringsHelperToolkit/StringsParser.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | internal struct StringsParser { 4 | static func parse(at filepath: String) -> [String:String] { 5 | let tokens = StringsTokenizer.tokenize(at: filepath) 6 | return parse(tokens) 7 | } 8 | 9 | static func parse(_ url: URL) -> [String:String] { 10 | let tokens = StringsTokenizer.tokenize(url) 11 | return parse(tokens) 12 | } 13 | 14 | static func parse(_ string: String) -> [String:String] { 15 | let tokens = StringsTokenizer.tokenize(string) 16 | return parse(tokens) 17 | } 18 | 19 | private static func parse(_ tokens: [StringsToken]) -> [String:String] { 20 | let dic = makeDictionary(from: tokens) 21 | return dic 22 | } 23 | 24 | private static func makeDictionary(from tokens: [StringsToken]) -> [String:String] { 25 | var dic = [String:String]() 26 | var queue = [StringsToken]() 27 | for token in tokens { 28 | switch token.type { 29 | case .value: 30 | queue.append(token) 31 | case .deliminator: 32 | let key = queue.removeFirst() 33 | let value = queue.removeFirst() 34 | dic[key.value] = value.value 35 | default: 36 | break 37 | } 38 | } 39 | return dic 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Resources/Storyboards/SingleScene.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Sources/FoundationExtensions/FileManager+Extension.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public extension FileManager { 4 | func enumerateImageSet(atPath path: String) -> [URL] { 5 | return enumerate(at: path, pathExtension: "imageset", isDir: true) 6 | } 7 | 8 | func enumerateImageSet(at url: URL) -> [URL] { 9 | return enumerateImageSet(atPath: url.path) 10 | } 11 | 12 | func enumerateStoryboard(atPath path: String) -> [URL] { 13 | return enumerate(at: path, pathExtension: "storyboard") 14 | } 15 | 16 | func enumerateStoryboard(at url: URL) -> [URL] { 17 | return enumerateStoryboard(atPath: url.path) 18 | } 19 | 20 | func enumerateStrings(atPath path: String) -> [URL] { 21 | return enumerate(at: path, pathExtension: "strings") 22 | } 23 | 24 | func enumerateStrings(at url: URL) -> [URL] { 25 | return enumerateStrings(atPath: url.path) 26 | } 27 | 28 | private func enumerate(at path:String, pathExtension: String, isDir: Bool = false) -> [URL] { 29 | guard isDirectory(path), let list = enumerator(atPath: path) else { 30 | return [] 31 | } 32 | let parent = URL(fileURLWithPath: path) 33 | return list.reduce([URL]()) { (partialResults, current) in 34 | guard let path = current as? String else { 35 | return partialResults 36 | } 37 | let url = parent.appendingPathComponent(path) 38 | guard url.pathExtension == pathExtension, isDirectory(url.path) == isDir else { 39 | return partialResults 40 | } 41 | return partialResults + [url] 42 | } 43 | } 44 | 45 | private func isDirectory(_ path: String) -> Bool { 46 | var isDirectory:ObjCBool = false 47 | guard fileExists(atPath: path, isDirectory: &isDirectory) else { 48 | return false 49 | } 50 | return isDirectory.boolValue 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Sources/ColorHelperToolkit/ColorHelperToolkit.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ColorHelperToolkit.swift 3 | // DevHelperToolkit 4 | // 5 | // Created by Naohiro Hamada on 2016/09/05. 6 | // 7 | // 8 | 9 | import Foundation 10 | 11 | public final class ColorHelperToolkit { 12 | public static let shared = ColorHelperToolkit() 13 | 14 | private init() { } 15 | 16 | public func generate(from filepath: String, 17 | to outputDirectory: String, 18 | withConfiguration configuration: ColorHelperToolkitConfiguration = `default`) { 19 | let url = URL(fileURLWithPath: filepath) 20 | generate(from: url, to: outputDirectory, withConfiguration: configuration) 21 | } 22 | 23 | public func generate(from url: URL, 24 | to outputDirectory: String, 25 | withConfiguration configuration: ColorHelperToolkitConfiguration = `default`) { 26 | guard let fileType = url.fileType else { 27 | fatalError("Invalid file type: \(url.absoluteString)") 28 | } 29 | let parser = fileType.parser() 30 | let models = parser.parse(url: url) 31 | let contents = makeContents(models, configuration) 32 | 33 | let outputDirUrl = URL(fileURLWithPath: outputDirectory) 34 | let destination = outputDirUrl.appendingPathComponent("\(configuration.platform.className)+Extension.swift") 35 | do { 36 | try contents.write(to: destination, atomically: true, encoding: .utf8) 37 | } catch let error { 38 | fatalError(error.localizedDescription) 39 | } 40 | } 41 | 42 | private func makeContents(_ models: [ColorDefinition], _ configuration: ColorHelperToolkitConfiguration) -> String { 43 | let platform = configuration.platform 44 | let tab = configuration.editorTabSpacing 45 | var contents = [String]() 46 | 47 | contents.append("import \(platform.framework)") 48 | contents.append("") 49 | contents.append("extension \(platform.className) {") 50 | for m in models { 51 | contents.append("\(tab)class var \(m.propertyName): \(platform.className) {") 52 | contents.append("\(tab)\(tab)return \(m.initializeStatement(platform: platform))") 53 | contents.append("\(tab)}") 54 | } 55 | contents.append("}") 56 | 57 | return contents.reduce("") { $0 + $1 + "\n" } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Sources/StoryboardHelperToolkit/StoryboardParser.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct StoryboardParser { 4 | 5 | 6 | static func parse(_ filepath: String) -> Storyboard { 7 | let url = URL(fileURLWithPath: filepath) 8 | return parse(url) 9 | } 10 | 11 | static func parse(_ url: URL) -> Storyboard { 12 | guard let parser = XMLParser(contentsOf: url) else { 13 | fatalError("Cannot instantiate parser") 14 | } 15 | let delegate = StoryboardParserDelegate() 16 | parser.delegate = delegate 17 | parser.parse() 18 | let name = url.lastPathComponent.replacingOccurrences(of: "." + url.pathExtension, with: "") 19 | return Storyboard(name: name, scenes: delegate.scenes) 20 | } 21 | } 22 | 23 | fileprivate class StoryboardParserDelegate: NSObject, XMLParserDelegate { 24 | override init() { 25 | super.init() 26 | } 27 | 28 | func parserDidStartDocument(_ parser: XMLParser) { 29 | } 30 | 31 | func parserDidEndDocument(_ parser: XMLParser) { 32 | } 33 | 34 | func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:]) { 35 | guard let element = Element(rawValue: elementName) else { 36 | return 37 | } 38 | switch element { 39 | case .document: 40 | guard let initial = attributeDict[Attribute.initialViewController] else { 41 | return 42 | } 43 | initialStoryboardIdentifier = initial 44 | case .viewController: 45 | guard let id = attributeDict[Attribute.id], let identifier = attributeDict[Attribute.storyboardIdentifier] else { 46 | return 47 | } 48 | scenes.append(StoryboardScene(identifier: identifier, initial: id == initialStoryboardIdentifier)) 49 | } 50 | } 51 | 52 | func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) { 53 | } 54 | 55 | var scenes: [StoryboardScene] = [] 56 | var initialStoryboardIdentifier: String = "" 57 | 58 | enum Element: String { 59 | case document = "document" 60 | case viewController = "viewController" 61 | } 62 | 63 | struct Attribute { 64 | static let initialViewController = "initialViewController" 65 | static let id = "id" 66 | static let storyboardIdentifier = "storyboardIdentifier" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Sources/ColorHelperToolkit/Foundation+Color.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Foundation+Color.swift 3 | // DevHelperToolkit 4 | // 5 | // Created by Naohiro Hamada on 2016/09/05. 6 | // 7 | // 8 | 9 | import Foundation 10 | 11 | typealias ColorValue = (red: Double, green: Double, blue: Double, alpha: Double) 12 | 13 | protocol ColorValueConvertiable { 14 | var colorValue: ColorValue { get } 15 | } 16 | 17 | extension String: ColorValueConvertiable { 18 | var colorValue: ColorValue { 19 | let values = self.characters.map { 20 | Int.init(String($0), radix: 16) 21 | } 22 | let results: ([Int], Bool) = values.reduce(([Int](), false)) { 23 | guard let value = $0.1 else { 24 | abort() 25 | } 26 | var temp = Array($0.0.0) 27 | if $0.0.1 { 28 | temp[temp.count - 1] += value 29 | } else { 30 | temp.append(value * 16) 31 | } 32 | return (temp, !$0.0.1) 33 | } 34 | let resultValue = results.0 35 | if resultValue.count == 3 { 36 | return (resultValue[0].bounded, resultValue[1].bounded, resultValue[2].bounded, 1.0) 37 | } else if resultValue.count == 4 { 38 | return (resultValue[0].bounded, resultValue[1].bounded, resultValue[2].bounded, resultValue[3].bounded) 39 | } else { 40 | fatalError("Ill-formatted") 41 | } 42 | } 43 | } 44 | 45 | extension NSString: ColorValueConvertiable { 46 | var colorValue: ColorValue { 47 | let string = self as String 48 | return string.colorValue 49 | } 50 | } 51 | 52 | extension Int: ColorValueConvertiable { 53 | var colorValue: ColorValue { 54 | let value = bounded 55 | return (value, value, value, 1.0) 56 | } 57 | 58 | var uint8: UInt8 { 59 | if self < Int(UInt8.min) { 60 | return UInt8.min 61 | } else if self > Int(UInt8.max) { 62 | return UInt8.max 63 | } else { 64 | return UInt8(self) 65 | } 66 | } 67 | 68 | var bounded: Double { 69 | return Double(uint8) / 255.0 70 | } 71 | } 72 | 73 | extension Double: ColorValueConvertiable { 74 | var colorValue: ColorValue { 75 | return (bounded, bounded, bounded, 1.0) 76 | } 77 | 78 | var bounded: Double { 79 | if self < 0.0 { 80 | return 0.0 81 | } else if self > 1.0 { 82 | return 1.0 83 | } else { 84 | return self 85 | } 86 | } 87 | } 88 | 89 | extension NSNumber: ColorValueConvertiable { 90 | var colorValue: ColorValue { 91 | let objcType = String(cString: objCType) 92 | switch objcType { 93 | case "c", "C", "B": 94 | fatalError("Type mismatch") 95 | case "i", "s", "l", "q", "I", "S", "L", "Q": 96 | return self.intValue.colorValue 97 | case "f", "d": 98 | return self.doubleValue.colorValue 99 | default: 100 | fatalError("Type mismatch") 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /Tests/JSONHelperToolkitTests/JSONHelperToolkitTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import JSONHelperToolkit 3 | 4 | class JSONHelperToolkitTests: XCTestCase { 5 | 6 | func testDictionaryToModel() { 7 | let dic: [String:Any] = ["key": "value", "integer": 10, "bool": true] 8 | let model = dic.jsonModels 9 | XCTAssertEqual(model.count, 1) 10 | for p in model.first!.properties { 11 | print("\(p.name) = \(p.type)") 12 | } 13 | } 14 | 15 | func testDictionariesToModel() { 16 | let dic: [String:Any] = ["key": "value", "integer": 10, "bool": true, "dic": ["element1": "element1"]] 17 | let model = dic.jsonModels 18 | XCTAssertEqual(model.count, 2) 19 | for m in model { 20 | print("Model Name: \(m.name)") 21 | for p in m.properties { 22 | print("\(p.name) = \(p.type)") 23 | } 24 | } 25 | } 26 | 27 | func testReadFromJsonSimple() { 28 | let filepath = "./Resources/test_simple.json" 29 | JSONHelperToolkit.shared.generate(from: filepath, to: "./Resources") 30 | } 31 | 32 | func testReadFromJsonUserData() { 33 | let filepath = "./Resources/test_userdata.json" 34 | JSONHelperToolkit.shared.generate(from: filepath, to: "./Resources") 35 | } 36 | 37 | func testReadFromJsonMultiUserData() { 38 | let filepath = "./Resources/test_multi_userdata.json" 39 | JSONHelperToolkit.shared.generate(from: filepath, to: "./Resources") 40 | } 41 | 42 | func testReadFromSimpleJsonModel() { 43 | let filepath = "./Resources/test_simple_json_model.json" 44 | JSONHelperToolkit.shared.generate(from: filepath, to: "./Resources") 45 | } 46 | 47 | func testReadFromSimpleUrlDate() { 48 | let filepath = "./Resources/test_simple_url_date.json" 49 | JSONHelperToolkit.shared.generate(from: filepath, to: "./Resources") 50 | } 51 | 52 | func testReadFromNestedSimpleUrlDate() { 53 | let filepath = "./Resources/test_nested_simple_url_date.json" 54 | JSONHelperToolkit.shared.generate(from: filepath, to: "./Resources") 55 | } 56 | 57 | func testReadFromSimpleUrlDateArray() { 58 | let filepath = "./Resources/test_simple_url_date_array.json" 59 | JSONHelperToolkit.shared.generate(from: filepath, to: "./Resources") 60 | } 61 | 62 | static var allTests : [(String, (JSONHelperToolkitTests) -> () throws -> Void)] { 63 | return [ 64 | ("testDictionaryToModel", testDictionaryToModel), 65 | ("testDictionariesToModel", testDictionariesToModel), 66 | ("testReadFromJsonSimple", testReadFromJsonSimple), 67 | ("testReadFromJsonUserData", testReadFromJsonUserData), 68 | ("testReadFromJsonMultiUserData", testReadFromJsonMultiUserData), 69 | ("testReadFromSimpleJsonModel", testReadFromSimpleJsonModel), 70 | ("testReadFromSimpleUrlDate", testReadFromSimpleUrlDate), 71 | ("testReadFromNestedSimpleUrlDate", testReadFromNestedSimpleUrlDate), 72 | ("testReadFromSimpleUrlDateArray", testReadFromSimpleUrlDateArray), 73 | ] 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Sources/ImageHelperToolkit/ImageHelperToolkit.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageHelperToolkit 3 | // DevHelperToolkit 4 | // 5 | // Created by Naohiro Hamada on 2016/09/13. 6 | // 7 | // 8 | 9 | import Foundation 10 | import FoundationExtensions 11 | 12 | public final class ImageHelperToolkit { 13 | public static let shared = ImageHelperToolkit() 14 | 15 | private init() { } 16 | 17 | public func generate(from filepath: String, 18 | to outputDirectory: String, 19 | withConfiguration configuration: ImageHelperToolkitConfiguration = `default`) { 20 | let url = URL(fileURLWithPath: filepath) 21 | generate(from: url, to: outputDirectory, withConfiguration: configuration) 22 | } 23 | 24 | public func generate(from url: URL, 25 | to outputDirectory: String, 26 | withConfiguration configuration: ImageHelperToolkitConfiguration = `default`) { 27 | let fileManager = FileManager.default 28 | let imageSetUrls = fileManager.enumerateImageSet(at: url) 29 | let contents = makeContents(imageSetUrls, configuration) 30 | 31 | let outputDirUrl = URL(fileURLWithPath: outputDirectory) 32 | let destination = outputDirUrl.appendingPathComponent("\(configuration.platform.className)+Extension.swift") 33 | do { 34 | try contents.write(to: destination, atomically: true, encoding: .utf8) 35 | } catch let error { 36 | fatalError(error.localizedDescription) 37 | } 38 | } 39 | 40 | private func makeContents(_ imageSetUrls: [URL], _ configuration: ImageHelperToolkitConfiguration) -> String { 41 | let platform = configuration.platform 42 | let tab = configuration.editorTabSpacing 43 | var contents = [String]() 44 | 45 | contents.append("import \(platform.framework)") 46 | contents.append("") 47 | contents.append("extension \(platform.className) {") 48 | contents.append("\(tab)enum Asset {") 49 | for url in imageSetUrls { 50 | contents.append("\(tab)\(tab)case \(url.enumName)") 51 | } 52 | contents.append("") 53 | contents.append("\(tab)\(tab)var name: String {") 54 | contents.append("\(tab)\(tab)\(tab)switch self {") 55 | for url in imageSetUrls { 56 | contents.append("\(tab)\(tab)\(tab)case .\(url.enumName):") 57 | contents.append("\(tab)\(tab)\(tab)\(tab)return \"\(url.assetName)\"") 58 | } 59 | contents.append("\(tab)\(tab)\(tab)}") 60 | contents.append("\(tab)\(tab)}") 61 | contents.append("") 62 | contents.append("\(tab)\(tab)var image: \(platform.className) {") 63 | contents.append("\(tab)\(tab)\(tab)return \(platform.className)(asset: self)") 64 | contents.append("\(tab)\(tab)}") 65 | contents.append("\(tab)}") 66 | contents.append("") 67 | contents.append("\(tab)convenience init(asset: Asset) {") 68 | contents.append("\(tab)\(tab)self.init(named: asset.name)!") 69 | contents.append("\(tab)}") 70 | contents.append("}") 71 | 72 | return contents.reduce("") { $0 + $1 + "\n" } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Sources/ColorHelperToolkit/CSVParser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CSVParser.swift 3 | // DevHelperToolkit 4 | // 5 | // Created by Naohiro Hamada on 2016/09/05. 6 | // 7 | // 8 | 9 | import Foundation 10 | 11 | final class CSVParser: Parser { 12 | func parse(url: URL) -> [ColorDefinition] { 13 | guard let stringContent = try? String(contentsOf: url) else { 14 | return [] 15 | } 16 | return stringContent.components(separatedBy: CharacterSet.newlines).reduce([ColorDefinition]()) { (partial, line) in 17 | let components = line.components(separatedBy: CharacterSet(charactersIn: ",\t")) 18 | switch components.count { 19 | case 2: 20 | let propertyName = components[0].propertyName 21 | let colorValue = components[1].appropriateColorValue 22 | var newResult = Array(partial) 23 | newResult.append(ColorDefinition(name: propertyName, colorValue: colorValue)) 24 | return newResult 25 | default: 26 | return partial 27 | } 28 | } 29 | } 30 | } 31 | 32 | fileprivate extension String { 33 | var hasRGB: Bool { 34 | guard characters.count == 6 || characters.count == 8 else { 35 | return false 36 | } 37 | let flags: [Bool] = self.characters.map { 38 | if let _ = Int(String($0), radix: 16) { 39 | return true 40 | } 41 | return false 42 | } 43 | return flags.reduce(true) { 44 | return $0.0 && $0.1 45 | } 46 | } 47 | 48 | var valid: Bool { 49 | guard characters.count == 0 else { 50 | return false 51 | } 52 | return true 53 | } 54 | 55 | var quoted: Bool { 56 | guard let first = characters.first, let last = characters.last else { 57 | return false 58 | } 59 | return first == "\"" && last == "\"" 60 | } 61 | 62 | var actualString: String { 63 | guard quoted else { 64 | return self 65 | } 66 | return self.trimmingCharacters(in: CharacterSet.init(charactersIn: "\"")) 67 | } 68 | 69 | var propertyName: String { 70 | guard !valid else { 71 | return "" 72 | } 73 | return actualString.lowerCamelCased 74 | } 75 | 76 | var lowerCamelCased: String { 77 | let comps = self.components(separatedBy: CharacterSet(charactersIn: " -_")) 78 | return comps.dropFirst().reduce(comps[0].lowercased(), { $0 + $1.capitalized }) 79 | } 80 | 81 | var appropriateColorValue: ColorValue { 82 | if hasRGB { 83 | return colorValue 84 | } 85 | if let val = Int(self) { 86 | return val.colorValue 87 | } else if let val = Double(self) { 88 | return val.colorValue 89 | } 90 | return colorValue 91 | } 92 | 93 | var isInt: Bool { 94 | guard let _ = Int(self) else { 95 | return false 96 | } 97 | return true 98 | } 99 | 100 | var isDouble: Bool { 101 | guard let _ = Double(self) else { 102 | return false 103 | } 104 | return true 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /Sources/StoryboardHelperToolkit/StoryboardHelperToolkit.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import FoundationExtensions 3 | 4 | public final class StoryboardHelperToolkit { 5 | public static let shared = StoryboardHelperToolkit() 6 | 7 | private init() { } 8 | 9 | public func generate(from filepath: String, 10 | to outputDirectory: String, 11 | withConfiguration configuration: StoryboardHelperToolkitConfiguration = `default`) { 12 | let url = URL(fileURLWithPath: filepath) 13 | generate(from: url, to: outputDirectory, withConfiguration: configuration) 14 | } 15 | 16 | public func generate(from url: URL, 17 | to outputDirectory: String, 18 | withConfiguration configuration: StoryboardHelperToolkitConfiguration = `default`) { 19 | let fileManager = FileManager.default 20 | let storyboards = fileManager.enumerateStoryboard(at: url).map { 21 | return StoryboardParser.parse($0) 22 | } 23 | let contents = makeContents(storyboards, configuration) 24 | 25 | let outputDirUrl = URL(fileURLWithPath: outputDirectory) 26 | let destination = outputDirUrl.appendingPathComponent("\(configuration.platform.className)+Extension.swift") 27 | do { 28 | try contents.write(to: destination, atomically: true, encoding: .utf8) 29 | } catch let error { 30 | fatalError(error.localizedDescription) 31 | } 32 | } 33 | 34 | private func makeContents(_ storyboards: [Storyboard], _ configuration: StoryboardHelperToolkitConfiguration) -> String { 35 | let platform = configuration.platform 36 | let tab = configuration.editorTabSpacing 37 | var contents = [String]() 38 | 39 | contents.append("import \(platform.framework)") 40 | contents.append("") 41 | contents.append("extension \(platform.className) {") 42 | for storyboard in storyboards { 43 | if storyboard.scenes.isEmpty { 44 | continue 45 | } 46 | 47 | let name = storyboard.name 48 | contents.append("\(tab)enum \(name.upperCamelCased()) {") 49 | for scene in storyboard.scenes { 50 | contents.append("\(tab)\(tab)case \(scene.identifier.lowerCamelCased())") 51 | } 52 | contents.append("") 53 | contents.append("\(tab)\(tab)private var identifier: String {") 54 | contents.append("\(tab)\(tab)\(tab)switch self {") 55 | for scene in storyboard.scenes { 56 | contents.append("\(tab)\(tab)\(tab)case .\(scene.identifier.lowerCamelCased()):") 57 | contents.append("\(tab)\(tab)\(tab)\(tab)return \"\(scene.identifier)\"") 58 | } 59 | contents.append("\(tab)\(tab)\(tab)}") 60 | contents.append("\(tab)\(tab)}") 61 | contents.append("") 62 | contents.append("\(tab)\(tab)var viewController: \(platform.viewController) {") 63 | contents.append("\(tab)\(tab)\(tab)let storyboard = \(platform.className)(name: \"\(name)\", bundle: nil)") 64 | contents.append("\(tab)\(tab)\(tab)return storyboard.instantiateViewController(withIdentifier: self.identifier)") 65 | contents.append("\(tab)\(tab)}") 66 | contents.append("\(tab)}") 67 | 68 | if let last = storyboards.last, last.name != storyboard.name { 69 | contents.append("") 70 | } 71 | } 72 | contents.append("}") 73 | 74 | return contents.reduce("") { $0 + $1 + "\n" } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Sources/JSONHelperToolkit/Foundation+Types.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Foundation+Types.swift 3 | // DevHelperToolkit 4 | // 5 | // Created by Naohiro Hamada on 2016/08/26. 6 | // 7 | // 8 | 9 | import Foundation 10 | 11 | internal protocol JSONValueTypeAnnotetable { 12 | var swiftType: ModelType { get } 13 | } 14 | 15 | extension Int8: JSONValueTypeAnnotetable { 16 | internal var swiftType: ModelType { 17 | return .integer 18 | } 19 | } 20 | 21 | extension UInt8: JSONValueTypeAnnotetable { 22 | internal var swiftType: ModelType { 23 | return .integer 24 | } 25 | } 26 | 27 | extension Int16: JSONValueTypeAnnotetable { 28 | internal var swiftType: ModelType { 29 | return .integer 30 | } 31 | } 32 | 33 | extension UInt16: JSONValueTypeAnnotetable { 34 | internal var swiftType: ModelType { 35 | return .integer 36 | } 37 | } 38 | 39 | extension Int32: JSONValueTypeAnnotetable { 40 | internal var swiftType: ModelType { 41 | return .integer 42 | } 43 | } 44 | 45 | extension UInt32: JSONValueTypeAnnotetable { 46 | internal var swiftType: ModelType { 47 | return .integer 48 | } 49 | } 50 | 51 | extension Int64: JSONValueTypeAnnotetable { 52 | internal var swiftType: ModelType { 53 | return .integer 54 | } 55 | } 56 | 57 | extension UInt64: JSONValueTypeAnnotetable { 58 | internal var swiftType: ModelType { 59 | return .integer 60 | } 61 | } 62 | 63 | extension Int: JSONValueTypeAnnotetable { 64 | internal var swiftType: ModelType { 65 | return .integer 66 | } 67 | } 68 | 69 | extension UInt: JSONValueTypeAnnotetable { 70 | internal var swiftType: ModelType { 71 | return .integer 72 | } 73 | } 74 | 75 | extension Float: JSONValueTypeAnnotetable { 76 | internal var swiftType: ModelType { 77 | return .fraction 78 | } 79 | } 80 | 81 | extension Double: JSONValueTypeAnnotetable { 82 | internal var swiftType: ModelType { 83 | return .fraction 84 | } 85 | } 86 | 87 | extension NSNumber: JSONValueTypeAnnotetable { 88 | internal var swiftType: ModelType { 89 | let objcType = String(cString: objCType) 90 | switch objcType { 91 | case "c", "C", "B": 92 | return .boolean 93 | case "i", "s", "l", "q", "I", "S", "L", "Q": 94 | return .integer 95 | case "f", "d": 96 | return .fraction 97 | default: 98 | fatalError("Type mismatch") 99 | } 100 | } 101 | } 102 | 103 | extension String: JSONValueTypeAnnotetable { 104 | internal var swiftType: ModelType { 105 | return .string 106 | } 107 | } 108 | 109 | extension NSString: JSONValueTypeAnnotetable { 110 | internal var swiftType: ModelType { 111 | return .string 112 | } 113 | } 114 | 115 | extension Bool: JSONValueTypeAnnotetable { 116 | internal var swiftType: ModelType { 117 | return .boolean 118 | } 119 | } 120 | 121 | extension Array: JSONValueTypeAnnotetable { 122 | internal var swiftType: ModelType { 123 | return .array(elementSwiftType) 124 | } 125 | } 126 | 127 | extension NSArray: JSONValueTypeAnnotetable { 128 | internal var swiftType: ModelType { 129 | let array = self as Array 130 | return array.swiftType 131 | } 132 | } 133 | 134 | extension Dictionary: JSONValueTypeAnnotetable { 135 | internal var swiftType: ModelType { 136 | return .object("") 137 | } 138 | } 139 | 140 | extension NSDictionary: JSONValueTypeAnnotetable { 141 | internal var swiftType: ModelType { 142 | return .object("") 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /Sources/JSONHelperToolkit/JSONHelperToolkit.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Configuration.swift 3 | // DevHelperToolkit 4 | // 5 | // Created by Naohiro Hamada on 2016/08/26. 6 | // 7 | // 8 | 9 | import Foundation 10 | import FoundationExtensions 11 | 12 | public final class JSONHelperToolkit { 13 | public static let shared: JSONHelperToolkit = JSONHelperToolkit() 14 | 15 | private init() { } 16 | 17 | public func generate(from filepath: String, 18 | to outputDirectory: String, 19 | withConfiguration configuration: JSONHelperToolkitConfiguration = `default`) { 20 | let url = URL(fileURLWithPath: filepath) 21 | generate(from: url, to: outputDirectory, withConfiguration: configuration) 22 | } 23 | 24 | public func generate(from url: URL, 25 | to outputDirectory: String, 26 | withConfiguration configuration: JSONHelperToolkitConfiguration = `default`) { 27 | guard let rawData = try? Data(contentsOf: url) else { 28 | fatalError("Failed to open \(url)") 29 | } 30 | guard let dic = try? JSONSerialization.jsonObject(with: rawData, options: []) as? [String:Any] else { 31 | fatalError("File may not JSON") 32 | } 33 | guard let jsonModels = dic?.jsonModels else { 34 | fatalError("Failed to convert") 35 | } 36 | let topModelName = url.lastPathComponent.replacingOccurrences(of: ".\(url.pathExtension)", with: "").upperCamelCased() 37 | let outputDirectoryUrl = URL(fileURLWithPath: outputDirectory) 38 | 39 | for model in jsonModels { 40 | if model.name.isEmpty { 41 | model.name = topModelName 42 | } 43 | let contents = model.swiftContents(configuration: configuration) 44 | let outputUrl = outputDirectoryUrl.appendingPathComponent("\(model.name).swift") 45 | do { 46 | try contents.write(to: outputUrl, atomically: true, encoding: .utf8) 47 | } catch let error { 48 | fatalError(error.localizedDescription) 49 | } 50 | } 51 | 52 | generateProtocol(to: outputDirectoryUrl, withConfiguration: configuration) 53 | 54 | let hasDate = jsonModels.reduce(false) { 55 | return $0 || $1.hasDate() 56 | } 57 | if hasDate { 58 | generateDateExtension(to: outputDirectoryUrl, withConfiguration: configuration) 59 | } 60 | } 61 | 62 | func generateProtocol(to outputDirectoryUrl: URL, 63 | withConfiguration configuration: JSONHelperToolkitConfiguration = `default`) { 64 | let lines = ["protocol JSONDecodable {", 65 | "\(configuration.editorTabSpacing)static func decode(_ jsonObject: [String:Any]) -> Self", 66 | "}"] 67 | let fileUrl = outputDirectoryUrl.appendingPathComponent("JSONDecodable.swift") 68 | write(contents: lines, to: fileUrl) 69 | } 70 | 71 | func generateDateExtension(to outputDirectoryUrl: URL, 72 | withConfiguration configuration: JSONHelperToolkitConfiguration = `default`) { 73 | let lines = ["import Foundation", 74 | "", 75 | "extension DateFormatter {", 76 | "\(configuration.editorTabSpacing)class var iso8601formatter: DateFormatter {", 77 | "\(configuration.editorTabSpacing)\(configuration.editorTabSpacing)let formatter = DateFormatter()", 78 | "\(configuration.editorTabSpacing)\(configuration.editorTabSpacing)formatter.dateFormat = \"yyyy-MM-dd'T'HH:mm:ssZ\"", 79 | "\(configuration.editorTabSpacing)\(configuration.editorTabSpacing)return formatter", 80 | "\(configuration.editorTabSpacing)}", 81 | "}" 82 | ] 83 | let fileUrl = outputDirectoryUrl.appendingPathComponent("DateFormatter+ISO8601.swift") 84 | write(contents: lines, to: fileUrl) 85 | } 86 | 87 | private func write(contents: [String], to outputFileUrl: URL) { 88 | let contents = contents.reduce("") { $0 + $1 + "\n" } 89 | do { 90 | try contents.write(to: outputFileUrl, atomically: true, encoding: .utf8) 91 | } catch let error { 92 | fatalError(error.localizedDescription) 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /Sources/JSONHelperToolkit/Dictionary+Conversion.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Dictionary+Conversion.swift 3 | // DevHelperToolkit 4 | // 5 | // Created by Naohiro Hamada on 2016/08/26. 6 | // 7 | // 8 | 9 | import Foundation 10 | 11 | private enum ModelDefinitionKey: String { 12 | case name 13 | case properties 14 | } 15 | 16 | private let requiredModelDefinitionKeys: [ModelDefinitionKey] = [ 17 | .name, 18 | .properties, 19 | ] 20 | 21 | extension Dictionary { 22 | internal var jsonModels: [ModelDefinition] { 23 | return isModelDefinition ? modelJsonModels : actualJsonModels 24 | } 25 | 26 | private var modelJsonModels: [ModelDefinition] { 27 | var modelName = "" 28 | var properties = [PropertyDefinition]() 29 | for (key, value) in self { 30 | guard let key = ModelDefinitionKey(rawValue: String(describing: key)) else { 31 | fatalError("Unknown key") 32 | } 33 | switch key { 34 | case .name: 35 | let value = String(describing: value) 36 | modelName = value.upperCamelCased() 37 | case .properties: 38 | guard let dic = value as? [String:String] else { 39 | fatalError("Failed to convert properties") 40 | } 41 | for (propName, propValue) in dic { 42 | properties.append(PropertyDefinition(name: propName.lowerCamelCased(), key: propName, type: ModelType.generate(from: propValue))) 43 | } 44 | } 45 | } 46 | return [ModelDefinition(name: modelName, properties: properties)] 47 | } 48 | 49 | private var actualJsonModels: [ModelDefinition] { 50 | var models = [ModelDefinition]() 51 | 52 | var properties = [PropertyDefinition]() 53 | for (key, value) in self { 54 | let key = String(describing: key) 55 | if value is NSNull { 56 | continue 57 | } 58 | guard let value = value as? JSONValueTypeAnnotetable else { 59 | fatalError("Failed to convert JSON value type") 60 | } 61 | switch value.swiftType { 62 | case .object: 63 | properties.append(PropertyDefinition(name: key.lowerCamelCased(), key: key, type: .object(key.upperCamelCased()))) 64 | case .array(.object): 65 | properties.append(PropertyDefinition(name: key.lowerCamelCased(), key: key, type: .array(.object(key.upperCamelCased())))) 66 | default: 67 | properties.append(PropertyDefinition(name: key.lowerCamelCased(), key: key, type: value.swiftType)) 68 | } 69 | } 70 | models.append(ModelDefinition(name: "", properties: properties)) 71 | 72 | let submodels = subdictionaries 73 | for (key, value) in submodels { 74 | for subModel in value.jsonModels { 75 | if subModel.name.isEmpty { 76 | subModel.name = String(describing: key.upperCamelCased()) 77 | } 78 | models.append(subModel) 79 | } 80 | } 81 | return models 82 | } 83 | 84 | internal var subdictionaries: [String:Dictionary] { 85 | var dics = [String:Dictionary]() 86 | for (key, value) in self { 87 | let key = String(describing: key) 88 | switch value { 89 | case is Dictionary, is NSDictionary: 90 | guard let value = value as? Dictionary else { 91 | fatalError("Failed to cast") 92 | } 93 | dics[key] = value 94 | case is [Dictionary]: 95 | guard let array = value as? [Dictionary], let first = array.first else { 96 | fatalError("Failed to cast") 97 | } 98 | dics[key] = first 99 | case is [NSDictionary]: 100 | guard let array = value as? [NSDictionary], let first = array.first as? Dictionary else { 101 | fatalError("Failed to cast") 102 | } 103 | dics[key] = first 104 | default: 105 | break 106 | } 107 | } 108 | return dics 109 | } 110 | 111 | private var isModelDefinition: Bool { 112 | let required = keys.reduce([ModelDefinitionKey:Int]()) { 113 | guard let keyName = $1 as? String, let key = ModelDefinitionKey(rawValue: keyName) else { 114 | return $0 115 | } 116 | var partial = $0 117 | partial[key] = (partial[key] ?? 0) + 1 118 | return partial 119 | } 120 | return required.keys.count == requiredModelDefinitionKeys.count && required.reduce(true) { 121 | return $0 && ($1.value == 1) 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /Sources/StringsHelperToolkit/StringsTokenizer.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | internal struct StringsTokenizer { 4 | static func tokenize(at filepath: String) -> [StringsToken] { 5 | let url = URL(fileURLWithPath: filepath) 6 | return tokenize(url) 7 | } 8 | 9 | static func tokenize(_ url: URL) -> [StringsToken] { 10 | guard let string = try? String(contentsOf: url) else { 11 | return [] 12 | } 13 | return tokenize(string) 14 | } 15 | 16 | static func tokenize(_ string: String) -> [StringsToken] { 17 | return tokenize(string, from: 0) 18 | } 19 | 20 | private static func tokenize(_ string: String, from startIndex: Int) -> [StringsToken] { 21 | var results = [StringsToken]() 22 | while true { 23 | let startIndex = results.isEmpty ? 0 : (results.last?.endIndex ?? 0) 24 | guard let token = string.nextToken(startIndex) else { 25 | break 26 | } 27 | results.append(token) 28 | } 29 | return results 30 | } 31 | } 32 | 33 | private extension String { 34 | func nextToken(_ index: Int) -> StringsToken? { 35 | guard index < characters.count else { 36 | return nil 37 | } 38 | 39 | let start = self.index(startIndex, offsetBy: index) 40 | let character = String(self[start]) 41 | switch character { 42 | case "=": 43 | return StringsToken(type: .assign, value: character, startIndex: index, endIndex: index + 1) 44 | case ";": 45 | return StringsToken(type: .deliminator, value: character, startIndex: index, endIndex: index + 1) 46 | case " ", "\t", "\r", "\n": 47 | for endIndex in (index+1).. String { 48 | let tab = configuration.editorTabSpacing 49 | var contents = [String]() 50 | 51 | let localizable = "Localizable" 52 | 53 | contents.append("import Foundation") 54 | contents.append("") 55 | contents.append("extension String {") 56 | contents.append("\(tab)enum \(localizable) {") 57 | for localize in strings { 58 | if localize.table == localizable { 59 | continue 60 | } 61 | contents.append("\(tab)\(tab)enum \(localize.table) {") 62 | for key in localize.keys { 63 | contents.append("\(tab)\(tab)\(tab)case \(key.removing().lowerCamelCased())") 64 | } 65 | contents.append("") 66 | contents.append("\(tab)\(tab)\(tab)private static var tableName: String {") 67 | contents.append("\(tab)\(tab)\(tab)\(tab)return \"\(localize.table)\"") 68 | contents.append("\(tab)\(tab)\(tab)}") 69 | contents.append("") 70 | contents.append("\(tab)\(tab)\(tab)private var key: String {") 71 | contents.append("\(tab)\(tab)\(tab)\(tab)switch self {") 72 | for key in localize.keys { 73 | contents.append("\(tab)\(tab)\(tab)\(tab)case .\(key.removing().lowerCamelCased()):") 74 | contents.append("\(tab)\(tab)\(tab)\(tab)\(tab)return \"\(key)\"") 75 | } 76 | contents.append("\(tab)\(tab)\(tab)\(tab)}") 77 | contents.append("\(tab)\(tab)\(tab)}") 78 | contents.append("") 79 | contents.append("\(tab)\(tab)\(tab)func localizedString() -> String {") 80 | contents.append("\(tab)\(tab)\(tab)\(tab)return String.localizedString(key: self.key, table: String.\(localizable).\(localize.table).tableName)") 81 | contents.append("\(tab)\(tab)\(tab)}") 82 | contents.append("\(tab)\(tab)}") 83 | contents.append("") 84 | } 85 | for localize in strings { 86 | if localize.table != localizable { 87 | continue 88 | } 89 | for key in localize.keys { 90 | contents.append("\(tab)\(tab)case \(key.removing().lowerCamelCased())") 91 | } 92 | contents.append("") 93 | contents.append("\(tab)\(tab)private static var tableName: String {") 94 | contents.append("\(tab)\(tab)\(tab)return \"\(localize.table)\"") 95 | contents.append("\(tab)\(tab)}") 96 | contents.append("") 97 | contents.append("\(tab)\(tab)private var key: String {") 98 | contents.append("\(tab)\(tab)\(tab)switch self {") 99 | for key in localize.keys { 100 | contents.append("\(tab)\(tab)\(tab)case .\(key.removing().lowerCamelCased()):") 101 | contents.append("\(tab)\(tab)\(tab)\(tab)return \"\(key)\"") 102 | } 103 | contents.append("\(tab)\(tab)\(tab)}") 104 | contents.append("\(tab)\(tab)}") 105 | contents.append("") 106 | contents.append("\(tab)\(tab)func localizedString() -> String {") 107 | contents.append("\(tab)\(tab)\(tab)return String.localizedString(key: self.key, table: String.\(localize.table).tableName)") 108 | contents.append("\(tab)\(tab)}") 109 | } 110 | contents.append("\(tab)}") 111 | contents.append("") 112 | contents.append("\(tab)private static func localizedString(key: String, table: String) -> String {") 113 | contents.append("\(tab)\(tab)return NSLocalizedString(key, tableName: table, bundle: Bundle.main, value: \"\", comment: key)") 114 | contents.append("\(tab)}") 115 | contents.append("}") 116 | 117 | return contents.reduce("") { $0 + $1 + "\n" } 118 | } 119 | 120 | private func generateStrings(table: String, urls: [URL]) -> Strings { 121 | let tokens = urls.map { 122 | StringsTokenizer.tokenize($0).filter { 123 | $0.type == .value 124 | } 125 | } 126 | var keyArray = [String]() 127 | for tokens in tokens { 128 | for i in 0.. String { 142 | return self.removingFormattedCharacters().removingEscapedCharacters() 143 | } 144 | 145 | func removingFormattedCharacters() -> String { 146 | let exp = try? NSRegularExpression(pattern: "(%)(@|[0-9]*d|([0-9]*\\.[0-9]+)?(l)?f|g)", options: .allowCommentsAndWhitespace) 147 | let string = NSMutableString(string: self) 148 | exp?.replaceMatches(in: string, options: .withoutAnchoringBounds, range: NSRange(location: 0, length: string.length), withTemplate: " arg ") 149 | return String(string) 150 | } 151 | func removingEscapedCharacters() -> String { 152 | let exp = try? NSRegularExpression(pattern: "(\\t)|(\\\")|(\\n)|(\\r)|(\\\')|(\\\\)", options: .allowCommentsAndWhitespace) 153 | let string = NSMutableString(string: self) 154 | exp?.replaceMatches(in: string, options: .withoutAnchoringBounds, range: NSRange(location: 0, length: string.length), withTemplate: " ") 155 | return String(string) 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /Sources/DevHelperToolkit/DevHelperToolkit.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DevHelperToolkit.swift 3 | // DevHelperToolkit 4 | // 5 | // Created by Naohiro Hamada on 2016/09/02. 6 | // 7 | // 8 | 9 | import Foundation 10 | import JSONHelperToolkit 11 | import ColorHelperToolkit 12 | import ImageHelperToolkit 13 | import StoryboardHelperToolkit 14 | import StringsHelperToolkit 15 | 16 | public final class DevHelperToolkit { 17 | let args: [String] 18 | 19 | init(args: [String]) { 20 | self.args = args 21 | } 22 | 23 | public func run() { 24 | let option = parseCommandLineArguments() 25 | switch option.mode { 26 | case .json: 27 | let inputFile = option.inputFile 28 | let outputDirectory = option.outputDirectory 29 | JSONHelperToolkit.shared.generate(from: inputFile, to: outputDirectory) 30 | case .color: 31 | let inputFile = option.inputFile 32 | let outputDirectory = option.outputDirectory 33 | let configuration = ColorHelperToolkitConfiguration.configuration(platform: option.colorTargetPlatform) 34 | ColorHelperToolkit.shared.generate(from: inputFile, to: outputDirectory, withConfiguration: configuration) 35 | case .image: 36 | let inputFile = option.inputFile 37 | let outputDirectory = option.outputDirectory 38 | let configuration = ImageHelperToolkitConfiguration.configuration(platform: option.imageTargetPlatform) 39 | ImageHelperToolkit.shared.generate(from: inputFile, to: outputDirectory, withConfiguration: configuration) 40 | case .storyboard: 41 | let inputFile = option.inputFile 42 | let outputDirectory = option.outputDirectory 43 | let configuration = StoryboardHelperToolkitConfiguration.configuration(platform: option.storyboardTargetPlatform) 44 | StoryboardHelperToolkit.shared.generate(from: inputFile, to: outputDirectory, withConfiguration: configuration) 45 | case .strings: 46 | let inputFile = option.inputFile 47 | let outputDirectory = option.outputDirectory 48 | StringsHelperToolkit.shared.generate(from: inputFile, to: outputDirectory) 49 | } 50 | } 51 | } 52 | 53 | extension ToolkitOption { 54 | var inputFile: String { 55 | switch self.mode { 56 | case .json: 57 | for param in self.parameters { 58 | guard let param = param as? JsonToolkitParameter else { 59 | return "" 60 | } 61 | if case .inputFile(source: let source) = param.type { 62 | return source 63 | } 64 | } 65 | return "" 66 | case .color: 67 | for param in self.parameters { 68 | guard let param = param as? ColorToolkitParameter else { 69 | return "" 70 | } 71 | if case .inputFile(source: let source) = param.type { 72 | return source 73 | } 74 | } 75 | return "" 76 | case .image: 77 | for param in self.parameters { 78 | guard let param = param as? ImageToolkitParameter else { 79 | return "" 80 | } 81 | if case .inputAssetDirectory(source: let source) = param.type { 82 | return source 83 | } 84 | } 85 | return "" 86 | case .storyboard: 87 | for param in self.parameters { 88 | guard let param = param as? StoryboardToolkitParameter else { 89 | return "" 90 | } 91 | if case .inputProjectDirectory(source: let source) = param.type { 92 | return source 93 | } 94 | } 95 | return "" 96 | case .strings: 97 | for param in self.parameters { 98 | guard let param = param as? StringsToolkitParameter else { 99 | return "" 100 | } 101 | if case .inputProjectDirectory(source: let source) = param.type { 102 | return source 103 | } 104 | } 105 | return "" 106 | } 107 | } 108 | 109 | var outputDirectory: String { 110 | switch self.mode { 111 | case .json: 112 | for param in self.parameters { 113 | guard let param = param as? JsonToolkitParameter else { 114 | return "." 115 | } 116 | if case .outputDirectory(name: let directory) = param.type { 117 | return directory 118 | } 119 | } 120 | return "." 121 | case .color: 122 | for param in self.parameters { 123 | guard let param = param as? ColorToolkitParameter else { 124 | return "." 125 | } 126 | if case .outputDirectory(name: let directory) = param.type { 127 | return directory 128 | } 129 | } 130 | return "." 131 | case .image: 132 | for param in self.parameters { 133 | guard let param = param as? ImageToolkitParameter else { 134 | return "." 135 | } 136 | if case .outputDirectory(name: let directory) = param.type { 137 | return directory 138 | } 139 | } 140 | return "." 141 | case .storyboard: 142 | for param in self.parameters { 143 | guard let param = param as? StoryboardToolkitParameter else { 144 | return "." 145 | } 146 | if case .outputDirectory(name: let directory) = param.type { 147 | return directory 148 | } 149 | } 150 | return "." 151 | case .strings: 152 | for param in self.parameters { 153 | guard let param = param as? StringsToolkitParameter else { 154 | return "." 155 | } 156 | if case .outputDirectory(name: let directory) = param.type { 157 | return directory 158 | } 159 | } 160 | return "." 161 | } 162 | } 163 | 164 | var colorTargetPlatform: ColorHelperToolkitConfiguration.TargetPlatform { 165 | switch self.mode { 166 | case .color: 167 | for param in self.parameters { 168 | guard let param = param as? ColorToolkitParameter else { 169 | fatalError("Invalid parameter") 170 | } 171 | if case .targetPlatform(name: let platformName) = param.type { 172 | guard let platform = ColorHelperToolkitConfiguration.TargetPlatform(rawValue: platformName) else { 173 | fatalError("Invalid platform") 174 | } 175 | return platform 176 | } 177 | } 178 | return .ios 179 | default: 180 | fatalError("Invalid option") 181 | } 182 | } 183 | 184 | var imageTargetPlatform: ImageHelperToolkitConfiguration.TargetPlatform { 185 | switch self.mode { 186 | case .image: 187 | for param in self.parameters { 188 | guard let param = param as? ImageToolkitParameter else { 189 | fatalError("Invalid parameter") 190 | } 191 | if case .targetPlatform(name: let platformName) = param.type { 192 | guard let platform = ImageHelperToolkitConfiguration.TargetPlatform(rawValue: platformName) else { 193 | fatalError("Invalid platform") 194 | } 195 | return platform 196 | } 197 | } 198 | return .ios 199 | default: 200 | fatalError("Invalid option") 201 | } 202 | } 203 | 204 | var storyboardTargetPlatform: StoryboardHelperToolkitConfiguration.TargetPlatform { 205 | switch self.mode { 206 | case .storyboard: 207 | for param in self.parameters { 208 | guard let param = param as? StoryboardToolkitParameter else { 209 | fatalError("Invalid parameter") 210 | } 211 | if case .targetPlatform(name: let platformName) = param.type { 212 | guard let platform = StoryboardHelperToolkitConfiguration.TargetPlatform(rawValue: platformName) else { 213 | fatalError("Invalid platform") 214 | } 215 | return platform 216 | } 217 | } 218 | return .ios 219 | default: 220 | fatalError("Invalid option") 221 | } 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /Sources/JSONHelperToolkit/ModelDefinition.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ModelDefinition.swift 3 | // DevHelperToolkit 4 | // 5 | // Created by Naohiro Hamada on 2016/08/26. 6 | // 7 | // 8 | 9 | import Foundation 10 | 11 | indirect enum ModelType { 12 | case integer, unsigned, fraction, string, boolean 13 | case array(ModelType) 14 | case object(String) 15 | case optional(ModelType) 16 | case signedInteger(Int) 17 | case unsignedInteger(Int) 18 | case singlePrecisionFloating 19 | case dictionary(ModelType, ModelType) 20 | case url 21 | case date 22 | 23 | var swiftType: String { 24 | switch self { 25 | case .integer: 26 | return "Int" 27 | case .unsigned: 28 | return "UInt" 29 | case .fraction: 30 | return "Double" 31 | case .string: 32 | return "String" 33 | case .boolean: 34 | return "Bool" 35 | case .array(let element): 36 | return "[\(element.swiftType)]" 37 | case .object(let customClassName): 38 | return customClassName 39 | case .optional(let model): 40 | return "\(model.swiftType)?" 41 | case .signedInteger(let bit): 42 | return "Int\(bit)" 43 | case .unsignedInteger(let bit): 44 | return "UInt\(bit)" 45 | case .singlePrecisionFloating: 46 | return "Float" 47 | case .dictionary(let key, let value): 48 | return "[\(key.swiftType):\(value.swiftType)]" 49 | case .url: 50 | return "URL" 51 | case .date: 52 | return "Date" 53 | } 54 | } 55 | 56 | static func generate(from string: String) -> ModelType { 57 | switch string { 58 | case "Int": 59 | return .integer 60 | case "UInt": 61 | return .unsigned 62 | case "Bool": 63 | return .boolean 64 | case "Float": 65 | return .singlePrecisionFloating 66 | case "Double": 67 | return .fraction 68 | case "String": 69 | return .string 70 | case let s where s.hasSuffix("?"): 71 | return .optional(ModelType.generate(from: s.replacingOccurrences(of: "?", with: ""))) 72 | case "URL": 73 | return .url 74 | case "Date": 75 | return .date 76 | case let s where s.hasPrefix("Int"): 77 | guard let bit = Int(s.replacingOccurrences(of: "Int", with: "")) else { 78 | abort() 79 | } 80 | switch bit { 81 | case 8, 16, 32, 64: 82 | return .signedInteger(bit) 83 | default: 84 | fatalError("Invalid bitwide: \(bit)") 85 | } 86 | case let s where s.hasPrefix("UInt"): 87 | guard let bit = Int(s.replacingOccurrences(of: "UInt", with: "")) else { 88 | abort() 89 | } 90 | switch bit { 91 | case 8, 16, 32, 64: 92 | return .unsignedInteger(bit) 93 | default: 94 | fatalError("Invalid bitwide: \(bit)") 95 | } 96 | case let container where container.hasPrefix("[") && container.hasSuffix("]"): 97 | let element = container.replacingOccurrences(of: "[", with: "").replacingOccurrences(of: "]", with: "") 98 | let numberOfColons = element.characters.reduce(0) { 99 | return $0 + ($1 == ":" ? 1 : 0) 100 | } 101 | switch numberOfColons { 102 | case 1: 103 | let comb = element.components(separatedBy: ":") 104 | return .dictionary(ModelType.generate(from: comb[0]), ModelType.generate(from: comb[1])) 105 | case 0: 106 | return .array(ModelType.generate(from: element)) 107 | default: 108 | fatalError("Invalid format: \(container)") 109 | } 110 | default: 111 | return .object(string) 112 | } 113 | } 114 | 115 | static func ==(_ lhs: ModelType, _ rhs: ModelType) -> Bool { 116 | return lhs == rhs 117 | } 118 | } 119 | 120 | final class PropertyDefinition { 121 | let name: String 122 | let key: String 123 | let type: ModelType 124 | 125 | init(name: String, key: String, type: ModelType) { 126 | self.name = name 127 | self.key = key 128 | self.type = type 129 | } 130 | } 131 | 132 | final class ModelDefinition { 133 | var name: String 134 | var properties: [PropertyDefinition] 135 | 136 | init(name: String = "", properties: [PropertyDefinition] = []) { 137 | self.name = name 138 | self.properties = properties 139 | } 140 | } 141 | 142 | extension ModelDefinition { 143 | func swiftContents(configuration: JSONHelperToolkitConfiguration) -> String { 144 | let tab = configuration.editorTabSpacing 145 | 146 | var lines = [String]() 147 | lines.append("import Foundation") 148 | lines.append("") 149 | 150 | lines.append("struct \(name) {") 151 | for property in properties { 152 | lines.append("\(tab)let \(property.name): \(property.type.swiftType)") 153 | } 154 | lines.append("}") 155 | lines.append("") 156 | lines.append("extension \(name): JSONDecodable {") 157 | lines.append("\(tab)static func decode(_ jsonObject: [String:Any]) -> \(name) {") 158 | for property in properties { 159 | switch property.type { 160 | case .object(let typeName): 161 | lines.append("\(tab)\(tab)guard let \(property.name)Object = jsonObject[\"\(property.key)\"] as? [String:Any] else {") 162 | lines.append("\(tab)\(tab)\(tab)abort()") 163 | lines.append("\(tab)\(tab)}") 164 | lines.append("\(tab)\(tab)let \(property.name) = \(typeName).decode(\(property.name)Object)") 165 | case .array(let modelType): 166 | switch modelType { 167 | case .object(let name): 168 | lines.append("\(tab)\(tab)guard let \(property.name)Object = jsonObject[\"\(property.key)\"] as? [[String:Any]] else {") 169 | lines.append("\(tab)\(tab)\(tab)abort()") 170 | lines.append("\(tab)\(tab)}") 171 | lines.append("\(tab)\(tab)let \(property.name) = \(property.name)Object.map { \(name).decode($0) }") 172 | case .url: 173 | lines.append("\(tab)\(tab)guard let \(property.name)Strings = jsonObject[\"\(property.key)\"] as? [String] else {") 174 | lines.append("\(tab)\(tab)\(tab)abort()") 175 | lines.append("\(tab)\(tab)}") 176 | lines.append("\(tab)\(tab)let \(property.name) = \(property.name)Strings.map { (_urlString: String) -> URL in") 177 | lines.append("\(tab)\(tab)\(tab)guard let url = URL(string: _urlString) else {") 178 | lines.append("\(tab)\(tab)\(tab)\(tab)abort()") 179 | lines.append("\(tab)\(tab)\(tab)}") 180 | lines.append("\(tab)\(tab)\(tab)return url") 181 | lines.append("\(tab)\(tab)}") 182 | case .date: 183 | lines.append("\(tab)\(tab)guard let \(property.name)Strings = jsonObject[\"\(property.key)\"] as? [String] else {") 184 | lines.append("\(tab)\(tab)\(tab)abort()") 185 | lines.append("\(tab)\(tab)}") 186 | lines.append("\(tab)\(tab)let \(property.name) = \(property.name)Strings.map { (_dateString: String) -> Date in") 187 | lines.append("\(tab)\(tab)\(tab)guard let date = DateFormatter.iso8601formatter.date(from: _dateString) else {") 188 | lines.append("\(tab)\(tab)\(tab)\(tab)abort()") 189 | lines.append("\(tab)\(tab)\(tab)}") 190 | lines.append("\(tab)\(tab)\(tab)return date") 191 | lines.append("\(tab)\(tab)}") 192 | default: 193 | lines.append("\(tab)\(tab)guard let \(property.name) = jsonObject[\"\(property.key)\"] as? \(property.type.swiftType) else {") 194 | lines.append("\(tab)\(tab)\(tab)abort()") 195 | lines.append("\(tab)\(tab)}") 196 | } 197 | case .optional(let model): 198 | lines.append("\(tab)\(tab)let \(property.name) = jsonObject[\"\(property.key)\"] as? \(model.swiftType)") 199 | case .url: 200 | lines.append("\(tab)\(tab)guard let \(property.name)String = jsonObject[\"\(property.key)\"] as? String, let \(property.name) = URL(string: \(property.name)String) else {") 201 | lines.append("\(tab)\(tab)\(tab)abort()") 202 | lines.append("\(tab)\(tab)}") 203 | case .date: 204 | lines.append("\(tab)\(tab)guard let \(property.name)String = jsonObject[\"\(property.key)\"] as? String, let \(property.name) = DateFormatter.iso8601formatter.date(from: \(property.name)String) else {") 205 | lines.append("\(tab)\(tab)\(tab)abort()") 206 | lines.append("\(tab)\(tab)}") 207 | default: 208 | lines.append("\(tab)\(tab)guard let \(property.name) = jsonObject[\"\(property.key)\"] as? \(property.type.swiftType) else {") 209 | lines.append("\(tab)\(tab)\(tab)abort()") 210 | lines.append("\(tab)\(tab)}") 211 | } 212 | } 213 | lines.append("\(tab)\(tab)return \(name)(") 214 | for property in properties.dropLast() { 215 | lines.append("\(tab)\(tab)\(tab)\(property.name): \(property.name),") 216 | } 217 | guard let last = properties.last else { 218 | abort() 219 | } 220 | lines.append("\(tab)\(tab)\(tab)\(last.name): \(last.name)") 221 | lines.append("\(tab)\(tab))") 222 | lines.append("\(tab)}") 223 | lines.append("}") 224 | 225 | return lines.reduce("") { $0 + $1 + "\n" } 226 | } 227 | 228 | func hasDate() -> Bool { 229 | return properties.reduce(false) { 230 | switch $1.type { 231 | case .url: 232 | return true 233 | default: 234 | return $0 || false 235 | } 236 | 237 | } 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /Sources/DevHelperToolkit/ToolkitOptions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ToolkitOptions.swift 3 | // DevHelperToolkit 4 | // 5 | // Created by Naohiro Hamada on 2016/09/02. 6 | // 7 | // 8 | 9 | import Foundation 10 | 11 | enum ToolkitModeOption: String { 12 | case json 13 | case color 14 | case image 15 | case storyboard 16 | case strings 17 | } 18 | 19 | protocol ParameterType { } 20 | 21 | enum JsonToolkitParameterType: ParameterType { 22 | case inputFile(source: String) 23 | case outputDirectory(name: String) 24 | } 25 | 26 | enum ColorToolkitParameterType: ParameterType { 27 | case inputFile(source: String) 28 | case outputDirectory(name: String) 29 | case targetPlatform(name: String) 30 | } 31 | 32 | enum ImageToolkitParameterType: ParameterType { 33 | case inputAssetDirectory(source: String) 34 | case outputDirectory(name: String) 35 | case targetPlatform(name: String) 36 | } 37 | 38 | enum StoryboardToolkitParameterType: ParameterType { 39 | case inputProjectDirectory(source: String) 40 | case outputDirectory(name: String) 41 | case targetPlatform(name: String) 42 | } 43 | 44 | enum StringsToolkitParameterType: ParameterType { 45 | case inputProjectDirectory(source: String) 46 | case outputDirectory(name: String) 47 | } 48 | 49 | protocol ToolkitParameter { 50 | var shortName: String { get } 51 | var longName: String { get } 52 | 53 | func match(_ option: String) -> Bool 54 | } 55 | 56 | extension ToolkitParameter { 57 | func match(_ option: String) -> Bool { 58 | return option == shortName || option == longName 59 | } 60 | } 61 | 62 | struct JsonToolkitParameter: ToolkitParameter { 63 | let shortName: String 64 | let longName: String 65 | let type: JsonToolkitParameterType 66 | } 67 | 68 | struct ColorToolkitParameter: ToolkitParameter { 69 | let shortName: String 70 | let longName: String 71 | let type: ColorToolkitParameterType 72 | } 73 | 74 | struct ImageToolkitParameter: ToolkitParameter { 75 | let shortName: String 76 | let longName: String 77 | let type: ImageToolkitParameterType 78 | } 79 | 80 | struct StoryboardToolkitParameter: ToolkitParameter { 81 | let shortName: String 82 | let longName: String 83 | let type: StoryboardToolkitParameterType 84 | } 85 | 86 | struct StringsToolkitParameter: ToolkitParameter { 87 | let shortName: String 88 | let longName: String 89 | let type: StringsToolkitParameterType 90 | } 91 | 92 | struct ToolkitOption { 93 | let mode: ToolkitModeOption 94 | let parameters: [ToolkitParameter] 95 | } 96 | 97 | func parseCommandLineArguments() -> ToolkitOption { 98 | let args = CommandLine.arguments.dropFirst() 99 | guard !args.isEmpty else { 100 | exit(0) 101 | } 102 | 103 | guard let first = args.first, 104 | let modeOption = ToolkitModeOption(rawValue: first) else { 105 | exit(0) 106 | } 107 | switch modeOption { 108 | case .json: 109 | let parser = JsonParameterParser() 110 | let parameters = parser.parse(parameters: Array(args.dropFirst())) 111 | return ToolkitOption(mode: modeOption, parameters: parameters) 112 | case .color: 113 | let parser = ColorParameterParer() 114 | let parameters = parser.parse(parameters: Array(args.dropFirst())) 115 | return ToolkitOption(mode: modeOption, parameters: parameters) 116 | case .image: 117 | let parser = ImageParameterParer() 118 | let parameters = parser.parse(parameters: Array(args.dropFirst())) 119 | return ToolkitOption(mode: modeOption, parameters: parameters) 120 | case .storyboard: 121 | let parser = StoryboardParameterParer() 122 | let parameters = parser.parse(parameters: Array(args.dropFirst())) 123 | return ToolkitOption(mode: modeOption, parameters: parameters) 124 | case .strings: 125 | let parser = StringsParameterParser() 126 | let parameters = parser.parse(parameters: Array(args.dropFirst())) 127 | return ToolkitOption(mode: modeOption, parameters: parameters) 128 | } 129 | } 130 | 131 | private final class JsonParameterParser { 132 | private let paramDefinition: [JsonToolkitParameter] = [ 133 | JsonToolkitParameter(shortName: "", longName: "", type: .inputFile(source: "")), 134 | JsonToolkitParameter(shortName: "-o", longName: "--output-directory", type: .outputDirectory(name: "")), 135 | ] 136 | 137 | internal func parse(parameters: [String]) -> [ToolkitParameter] { 138 | guard let first = parameters.first else { 139 | return [] 140 | } 141 | var current = [ToolkitParameter]() 142 | var rest = [ToolkitParameter]() 143 | if first.hasSuffix(".json") { 144 | current.append(JsonToolkitParameter(shortName: "", longName: "", type: .inputFile(source: first))) 145 | rest = parse(parameters: Array(parameters.dropFirst())) 146 | } else { 147 | for param in paramDefinition { 148 | if param.match(first) { 149 | switch param.type { 150 | case .outputDirectory(name: _): 151 | guard parameters.count > 1 else { 152 | return [] 153 | } 154 | current.append(JsonToolkitParameter(shortName: param.shortName, longName: param.longName, type: .outputDirectory(name: parameters[1]))) 155 | rest = parse(parameters: Array(parameters.dropFirst(2))) 156 | case .inputFile(source: _): 157 | return [] 158 | } 159 | } 160 | } 161 | } 162 | current.append(contentsOf: rest) 163 | return current 164 | } 165 | } 166 | 167 | private final class ColorParameterParer { 168 | private let paramDefinition: [ColorToolkitParameter] = [ 169 | ColorToolkitParameter(shortName: "", longName: "", type: .inputFile(source: "")), 170 | ColorToolkitParameter(shortName: "-o", longName: "--output-directory", type: .outputDirectory(name: "")), 171 | ColorToolkitParameter(shortName: "-p", longName: "--platform", type: .targetPlatform(name: "")), 172 | ] 173 | 174 | internal func parse(parameters: [String]) -> [ToolkitParameter] { 175 | guard let first = parameters.first else { 176 | return [] 177 | } 178 | var current = [ToolkitParameter]() 179 | var rest = [ToolkitParameter]() 180 | if first.hasSuffix(".json") || first.hasSuffix(".csv") { 181 | current.append(ColorToolkitParameter(shortName: "", longName: "", type: .inputFile(source: first))) 182 | rest = parse(parameters: Array(parameters.dropFirst())) 183 | } else { 184 | for param in paramDefinition { 185 | if param.match(first) { 186 | switch param.type { 187 | case .outputDirectory(name: _): 188 | guard parameters.count > 1 else { 189 | return [] 190 | } 191 | current.append(ColorToolkitParameter(shortName: param.shortName, longName: param.longName, type: .outputDirectory(name: parameters[1]))) 192 | rest = parse(parameters: Array(parameters.dropFirst(2))) 193 | case .targetPlatform(name: _): 194 | guard parameters.count > 1 else { 195 | return [] 196 | } 197 | current.append(ColorToolkitParameter(shortName: param.shortName, longName: param.longName, type: .targetPlatform(name: parameters[1]))) 198 | rest = parse(parameters: Array(parameters.dropFirst(2))) 199 | case .inputFile(source: _): 200 | return [] 201 | } 202 | } 203 | } 204 | } 205 | current.append(contentsOf: rest) 206 | return current 207 | } 208 | } 209 | 210 | private final class ImageParameterParer { 211 | private let paramDefinition: [ImageToolkitParameter] = [ 212 | ImageToolkitParameter(shortName: "", longName: "", type: .inputAssetDirectory(source: "")), 213 | ImageToolkitParameter(shortName: "-o", longName: "--output-directory", type: .outputDirectory(name: "")), 214 | ImageToolkitParameter(shortName: "-p", longName: "--platform", type: .targetPlatform(name: "")), 215 | ] 216 | 217 | internal func parse(parameters: [String]) -> [ToolkitParameter] { 218 | guard let first = parameters.first else { 219 | return [] 220 | } 221 | var current = [ToolkitParameter]() 222 | var rest = [ToolkitParameter]() 223 | if first.hasSuffix(".xcassets") { 224 | current.append(ImageToolkitParameter(shortName: "", longName: "", type: .inputAssetDirectory(source: first))) 225 | rest = parse(parameters: Array(parameters.dropFirst())) 226 | } else { 227 | for param in paramDefinition { 228 | if param.match(first) { 229 | switch param.type { 230 | case .outputDirectory(name: _): 231 | guard parameters.count > 1 else { 232 | return [] 233 | } 234 | current.append(ImageToolkitParameter(shortName: param.shortName, longName: param.longName, type: .outputDirectory(name: parameters[1]))) 235 | rest = parse(parameters: Array(parameters.dropFirst(2))) 236 | case .targetPlatform(name: _): 237 | guard parameters.count > 1 else { 238 | return [] 239 | } 240 | current.append(ImageToolkitParameter(shortName: param.shortName, longName: param.longName, type: .targetPlatform(name: parameters[1]))) 241 | rest = parse(parameters: Array(parameters.dropFirst(2))) 242 | case .inputAssetDirectory(source: _): 243 | return [] 244 | } 245 | } 246 | } 247 | } 248 | current.append(contentsOf: rest) 249 | return current 250 | } 251 | } 252 | 253 | private final class StoryboardParameterParer { 254 | private let paramDefinition: [StoryboardToolkitParameter] = [ 255 | StoryboardToolkitParameter(shortName: "", longName: "", type: .inputProjectDirectory(source: "")), 256 | StoryboardToolkitParameter(shortName: "-o", longName: "--output-directory", type: .outputDirectory(name: "")), 257 | StoryboardToolkitParameter(shortName: "-p", longName: "--platform", type: .targetPlatform(name: "")), 258 | ] 259 | 260 | internal func parse(parameters: [String]) -> [ToolkitParameter] { 261 | guard let first = parameters.first else { 262 | return [] 263 | } 264 | var current = [ToolkitParameter]() 265 | var rest = [ToolkitParameter]() 266 | for param in paramDefinition { 267 | if param.match(first) { 268 | switch param.type { 269 | case .outputDirectory(name: _): 270 | guard parameters.count > 1 else { 271 | return [] 272 | } 273 | current.append(StoryboardToolkitParameter(shortName: param.shortName, longName: param.longName, type: .outputDirectory(name: parameters[1]))) 274 | rest = parse(parameters: Array(parameters.dropFirst(2))) 275 | case .targetPlatform(name: _): 276 | guard parameters.count > 1 else { 277 | return [] 278 | } 279 | current.append(StoryboardToolkitParameter(shortName: param.shortName, longName: param.longName, type: .targetPlatform(name: parameters[1]))) 280 | rest = parse(parameters: Array(parameters.dropFirst(2))) 281 | case .inputProjectDirectory(source: _): 282 | return [] 283 | } 284 | } else { 285 | current.append(StoryboardToolkitParameter(shortName: "", longName: "", type: .inputProjectDirectory(source: first))) 286 | rest = parse(parameters: Array(parameters.dropFirst(1))) 287 | } 288 | } 289 | current.append(contentsOf: rest) 290 | return current 291 | } 292 | } 293 | 294 | private final class StringsParameterParser { 295 | private let paramDefinition: [StringsToolkitParameter] = [ 296 | StringsToolkitParameter(shortName: "", longName: "", type: .inputProjectDirectory(source: "")), 297 | StringsToolkitParameter(shortName: "-o", longName: "--output-directory", type: .outputDirectory(name: "")), 298 | ] 299 | 300 | internal func parse(parameters: [String]) -> [ToolkitParameter] { 301 | guard let first = parameters.first else { 302 | return [] 303 | } 304 | var current = [ToolkitParameter]() 305 | var rest = [ToolkitParameter]() 306 | for param in paramDefinition { 307 | if param.match(first) { 308 | switch param.type { 309 | case .outputDirectory(name: _): 310 | guard parameters.count > 1 else { 311 | return [] 312 | } 313 | current.append(StringsToolkitParameter(shortName: param.shortName, longName: param.longName, type: .outputDirectory(name: parameters[1]))) 314 | rest = parse(parameters: Array(parameters.dropFirst(2))) 315 | case .inputProjectDirectory(source: _): 316 | return [] 317 | } 318 | } else { 319 | current.append(StringsToolkitParameter(shortName: "", longName: "", type: .inputProjectDirectory(source: first))) 320 | rest = parse(parameters: Array(parameters.dropFirst(1))) 321 | } 322 | } 323 | current.append(contentsOf: rest) 324 | return current 325 | } 326 | } 327 | --------------------------------------------------------------------------------