diff --git a/.travis.yml b/.travis.yml index 8eb74c5..4e96cc2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,5 @@ os: osx -osx_image: xcode11.2 +osx_image: xcode11.3 language: swift sudo: required script: diff --git a/Package.resolved b/Package.resolved index b6be056..106f5e3 100644 --- a/Package.resolved +++ b/Package.resolved @@ -29,12 +29,12 @@ } }, { - "package": "swift-tools-support-core", - "repositoryURL": "https://github.com/apple/swift-tools-support-core.git", + "package": "swift-argument-parser", + "repositoryURL": "https://github.com/apple/swift-argument-parser", "state": { - "branch": "master", - "revision": "f9e7ad4d3dd25d698c53a723ca67cd145418e821", - "version": null + "branch": null, + "revision": "f6ac7b8118ff5d1bc0faee7f37bf6f8fd8f95602", + "version": "0.0.1" } }, { diff --git a/Package.swift b/Package.swift index addadbb..bd5c10e 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.0 +// swift-tools-version:5.1 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription @@ -10,12 +10,12 @@ let package = Package( ], dependencies: [ .package(url: "https://github.com/tuist/xcodeproj.git", .upToNextMajor(from: "7.5.0")), - .package(url: "https://github.com/apple/swift-tools-support-core.git", .branch("master")) + .package(url: "https://github.com/apple/swift-argument-parser", from: "0.0.1"), ], targets: [ .target( name: "Shark", - dependencies: ["SwiftToolsSupport-auto", "XcodeProj"]), + dependencies: ["XcodeProj", "ArgumentParser"]), .testTarget( name: "SharkTests", dependencies: ["Shark"]), diff --git a/Sources/Shark/Parser.swift b/Sources/Shark/Parser.swift index 1574b6e..fa4f13c 100644 --- a/Sources/Shark/Parser.swift +++ b/Sources/Shark/Parser.swift @@ -1,67 +1,58 @@ import Foundation -import TSCUtility +import ArgumentParser -enum Parser { - struct Result { - let projectPath: String - let outputURL: Foundation.URL - let topLevelEnumName: String - let targetName: String? - let locale: String - } - - static func parse() throws -> Result { - let exampleUsageString = "$PROJECT_FILE_PATH $PROJECT_DIR/$PROJECT_NAME" - let parser = ArgumentParser(usage: exampleUsageString, overview: "Shark") - let pathArgument = parser.add(positional: ".xcodeproj path", kind: String.self, usage: "The path to the .xcodeproj file") - let outputArgument = parser.add(positional: "output path", kind: String.self, usage: "Path for the output file. Creates a Shark.swift file when this value is a folder") - let nameArgument = parser.add(option: "--name", kind: String.self, usage: #"Top level enum name under which the cases are defined. Defaults to "Shark""#, completion: nil) - let targetArgument = parser.add(option: "--target", kind: String.self, usage: "Target name of the application, useful in case there are multiple application targets", completion: nil) - let localeArgument = parser.add(option: "--locale",kind: String.self,usage: - #"Localization code to use when selecting the Localizable.strings. i.e "en", "de", "es" The "en" locale is used unless specified"#) - - let parseResults: ArgumentParser.Result - do { - parseResults = try parser.parse(Array(CommandLine.arguments.dropFirst())) - } catch { - switch error { - case ArgumentParserError.expectedArguments(_, let missingArguments): - print("Missing arguments: \(missingArguments.joined(separator: ", "))") - print("Example usage: \(exampleUsageString)") - case ArgumentParserError.unknownOption(let option): - print("Unknown option: \(option)") - default: - print(error.localizedDescription) - } - exit(EXIT_FAILURE) - } - - guard let projectPath = parseResults.get(pathArgument)?.expandingTildeInPath, let outputPath = parseResults.get(outputArgument)?.expandingTildeInPath else { - print("xcodeproj file path and output path parameters are required") - exit(EXIT_FAILURE) - } - - guard projectPath.pathExtension == "xcodeproj" else { - print("\(projectPath) should point to a .xcodeproj file") - exit(EXIT_FAILURE) +struct Options: ParsableArguments { + @Argument(help: "The .xcodeproj file path") + fileprivate(set) var projectPath: String + + @Argument(help: "The output file path") + fileprivate(set) var outputPath: String + + @Option(name: .customLong("name"), + default: "Shark", + help: "Top level enum name under which the cases are defined.") + private(set) var topLevelEnumName: String + + @Option(name: .customLong("target"), + help: "Target name of the application, useful in case there are multiple application targets") + private(set) var targetName: String? + + @Option(name: .long, + default: "en", + help: "Localization code to use when selecting the Localizable.strings. i.e en, de, es.") + private(set) var locale: String +} + +struct Shark: ParsableCommand { + static var configuration: CommandConfiguration = .init(abstract:#""" +Paste the following line in a Xcode run phase script that runs after the "Compile Sources" run phase: +$PROJECT_FILE_PATH $PROJECT_DIR/$PROJECT_NAME +"""#) + + @OptionGroup() + private var options: Options + + mutating func validate() throws { + guard options.projectPath.pathExtension == "xcodeproj" else { + throw ValidationError("\(options.projectPath) should point to a .xcodeproj file") } - + var isDirectory: ObjCBool = false - - let outputURL: Foundation.URL - if FileManager.default.fileExists(atPath: outputPath, isDirectory: &isDirectory), isDirectory.boolValue { - outputURL = URL(fileURLWithPath: outputPath).appendingPathComponent("Shark.swift") - } else if outputPath.pathExtension == "swift" { - outputURL = URL(fileURLWithPath: outputPath) - } else { - print("The output path should either point to an existing folder or end with a .swift extension") - exit(2) + if FileManager.default.fileExists(atPath: options.outputPath, isDirectory: &isDirectory), isDirectory.boolValue { + options.outputPath.append("Shark.swift") + } else if options.outputPath.pathExtension != "swift" { + throw ValidationError("The output path should either point to an existing folder or end with a .swift extension") } - - return Result(projectPath: projectPath, - outputURL: outputURL, - topLevelEnumName: parseResults.get(nameArgument) ?? "Shark", - targetName: parseResults.get(targetArgument), - locale: parseResults.get(localeArgument) ?? "en") + + options.projectPath = options.projectPath.expandingTildeInPath + options.outputPath = options.outputPath.expandingTildeInPath + } + + func run() throws { + let enumString = try SharkEnumBuilder.sharkEnumString(forOptions: options) + + try FileBuilder + .fileContents(with: enumString, filename: options.outputPath.lastPathComponent) + .write(to: URL(fileURLWithPath: options.outputPath), atomically: true, encoding: .utf8) } } diff --git a/Sources/Shark/SharkEnumBuilder.swift b/Sources/Shark/SharkEnumBuilder.swift index a245fc0..6c38e07 100644 --- a/Sources/Shark/SharkEnumBuilder.swift +++ b/Sources/Shark/SharkEnumBuilder.swift @@ -2,9 +2,9 @@ import Foundation enum SharkEnumBuilder { static var topLevelEnumName = "Shark" - static func sharkEnumString(forParseResult parseResult: Parser.Result) throws -> String { - SharkEnumBuilder.topLevelEnumName = parseResult.topLevelEnumName - let resourcePaths = try XcodeProjectHelper(parseResult: parseResult).resourcePaths() + static func sharkEnumString(forOptions options: Options) throws -> String { + SharkEnumBuilder.topLevelEnumName = options.topLevelEnumName + let resourcePaths = try XcodeProjectHelper(options: options).resourcePaths() let imagesString = try ImageEnumBuilder.imageEnumString(forFilesAtPaths: resourcePaths.assetsPaths, topLevelName: "I") let colorsString = try ColorEnumBuilder.colorEnumString(forFilesAtPaths: resourcePaths.assetsPaths, topLevelName: "C") @@ -13,7 +13,7 @@ enum SharkEnumBuilder { let declarations = [imagesString, colorsString, localizationsString].compactMap({ $0?.indented(withLevel: 1) }).joined(separator: "\n\n") return """ - public enum \(parseResult.topLevelEnumName) { + public enum \(topLevelEnumName) { private class Custom {} static var bundle: Bundle { return Bundle(for: Custom.self) } \(declarations) diff --git a/Sources/Shark/XcodeProjectHelper.swift b/Sources/Shark/XcodeProjectHelper.swift index 5fdc0ad..6e7c26b 100644 --- a/Sources/Shark/XcodeProjectHelper.swift +++ b/Sources/Shark/XcodeProjectHelper.swift @@ -17,11 +17,11 @@ struct XcodeProjectHelper { private let targetName: String? private let locale: String - init(parseResult: Parser.Result) throws { - projectPath = Path(parseResult.projectPath) + init(options: Options) throws { + projectPath = Path(options.projectPath) xcodeproj = try XcodeProj(path: projectPath) - targetName = parseResult.targetName - locale = parseResult.locale + targetName = options.targetName + locale = options.locale } func resourcePaths() throws -> ResourcePaths { diff --git a/Sources/Shark/main.swift b/Sources/Shark/main.swift index 5e5bfdc..d6e4051 100644 --- a/Sources/Shark/main.swift +++ b/Sources/Shark/main.swift @@ -1,6 +1 @@ -let parseResult = try Parser.parse() -let enumString = try SharkEnumBuilder.sharkEnumString(forParseResult: parseResult) - -try FileBuilder - .fileContents(with: enumString, filename: parseResult.outputURL.lastPathComponent) - .write(to: parseResult.outputURL, atomically: true, encoding: .utf8) +Shark.main()