├── .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 |
--------------------------------------------------------------------------------