Skip to content

Commit

Permalink
refactored commands
Browse files Browse the repository at this point in the history
  • Loading branch information
yonaskolb committed Jan 5, 2018
1 parent 19203a9 commit b92689a
Show file tree
Hide file tree
Showing 17 changed files with 242 additions and 203 deletions.
26 changes: 4 additions & 22 deletions Package.resolved
@@ -1,22 +1,13 @@
{
"object": {
"pins": [
{
"package": "Guaka",
"repositoryURL": "https://github.com/nsomar/Guaka.git",
"state": {
"branch": null,
"revision": "9dfb2ea95b8ab9e799d0f65a79a2316679a758e0",
"version": "0.1.3"
}
},
{
"package": "PathKit",
"repositoryURL": "https://github.com/kylef/PathKit.git",
"state": {
"branch": null,
"revision": "891a3fec2699fc43aed18b7649950677c0152a22",
"version": "0.8.0"
"revision": "404d60fd725d1ba7dbf55cb95a3046285a066169",
"version": "0.9.0"
}
},
{
Expand All @@ -33,17 +24,8 @@
"repositoryURL": "https://github.com/kylef/Spectre.git",
"state": {
"branch": null,
"revision": "e46b75cf03ad5e563b4b0a5068d3d6f04d77d80b",
"version": "0.7.2"
}
},
{
"package": "StringScanner",
"repositoryURL": "https://github.com/oarrabi/StringScanner",
"state": {
"branch": null,
"revision": "246c697efe2f57d9042f58b1b53ace4fddb1efc4",
"version": "0.2.0"
"revision": "e34d5687e1e9d865e3527dd58bc2f7464ef6d936",
"version": "0.8.0"
}
},
{
Expand Down
2 changes: 0 additions & 2 deletions Package.swift
Expand Up @@ -19,7 +19,6 @@ let package = Package(
.package(url: "https://github.com/kylef/PathKit.git", from: "0.8.0"),
.package(url: "https://github.com/kareman/SwiftShell.git", from: "4.0.0"),
.package(url: "https://github.com/onevcat/Rainbow.git", from: "2.1.0"),
.package(url: "https://github.com/nsomar/Guaka.git", from: "0.1.3"),
.package(url: "https://github.com/apple/swift-package-manager.git", from: "0.1.0"),
],
targets: [
Expand All @@ -30,7 +29,6 @@ let package = Package(
dependencies: [
"MintKit",
"Rainbow",
"Guaka",
"SwiftShell",
]),
.target(
Expand Down
6 changes: 3 additions & 3 deletions README.md
Expand Up @@ -93,13 +93,13 @@ Run `mint --help` to see usage instructions.

Run, install and update commands have 1 or 2 arguments:

- **repo (required)**: This can be a shorthand for a github repo `install realm/SwiftLint` or a fully qualified git path `install https://github.com/realm/SwiftLint.git`. In the case of `run` you can also just pass the name of the repo if it is already installed `run swiftlint`. This will do a lookup of all installed packages. An optional version can be specified by appending `@version`, otherwise the newest tag or master will be used. Note that if you don't specify a version, the current tags must be loaded remotely each time.
- **command (optional)**: The command to install or run. This defaults to the the last path in the repo (so for `realm/swiftlint` it will be `swiftlint`). In the case of `run` you can also pass any arguments to the command but make sure the whole thing is surrounded by quotes eg `mint run realm/swiftlint "swiftlint --path source"`
- **package (required)**: This can be a shorthand for a github repo `install realm/SwiftLint` or a fully qualified git path `install https://github.com/realm/SwiftLint.git`. In the case of `run` you can also just pass the name of the repo if it is already installed `run swiftlint`. This will do a lookup of all installed packages. An optional version can be specified by appending `@version`, otherwise the newest tag or master will be used. Note that if you don't specify a version, the current tags must be loaded remotely each time.
- **command (optional)**: The command to install or run. This defaults to the the last path in the repo (so for `realm/swiftlint` it will be `swiftlint`). In the case of `run` you can also pass any arguments to the command eg `mint run realm/swiftlint swiftlint --path source`


#### Examples
```sh
$ mint install yonaskolb/XcodeGen@1.2.4 "XcodeGen --spec spec.yml" # pass some arguments
$ mint run yonaskolb/XcodeGen@1.2.4 xcodegen --spec spec.yml # pass some arguments
$ mint install yonaskolb/XcodeGen@1.2.4 # use version 1.2.4
$ mint install yonaskolb/XcodeGen # use newest tag
$ mint run yonaskolb/XcodeGen@1.2.4 # run 1.2.4
Expand Down
151 changes: 15 additions & 136 deletions Sources/Mint/main.swift
@@ -1,143 +1,22 @@
import Foundation
import MintKit
import Rainbow
import Foundation
import SwiftShell
import Guaka

let mint = Mint(path: "/usr/local/lib/mint")

func catchError(closure: () throws -> Void) {
do {
try closure()
} catch {
if let error = error as? CommandError {
print("🌱 \(error.description)".red)
} else if let error = error as? SwiftShell.CommandError {
switch error {
case .inAccessibleExecutable(let path): main.stderror.print("Couldn't run command \(path)")
case .returnedErrorCode(let command, _): main.stderror.print("\(command.quoted) failed")
}
} else if let error = error as? MintError {
print("🌱 \(error.description)".red)
} else {
print("🌱 \(error.localizedDescription)".red)
}
exit(1)
}
}

enum CommandError: Error, CustomStringConvertible {
case repoRequired
case invalidRepo(String)
case tooManyArguments

var description: String {
switch self {
case .repoRequired:
return "Repo required"
case let .invalidRepo(repo):
return "The repo was invalid: \(repo)"
case .tooManyArguments:
return "Too many arguments. Make sure command is surrounded in quotes"
do {
let mint = Mint(path: "/usr/local/lib/mint")
let mintInterface = MintInterace(mint: mint)
try mintInterface.execute(arguments: Array(ProcessInfo.processInfo.arguments.dropFirst()))
} catch {
if let error = error as? SwiftShell.CommandError {
switch error {
case let .inAccessibleExecutable(path): main.stderror.print("Couldn't run command \(path)")
case let .returnedErrorCode(command, _): main.stderror.print("\(command.quoted) failed")
}
} else if error._domain == NSCocoaErrorDomain {
print("🌱 \(error.localizedDescription)".red)
} else {
print("🌱 \(error)".red)
}
exit(1)
}

func getOptions(flags: Flags, args: [String]) throws -> (repo: String, version: String, command: String, verbose: Bool, global: Bool) {
guard let repoVersion = args.first else { throw CommandError.repoRequired }
let version: String
let command: String
let repoVersionParts = repoVersion.components(separatedBy: "@")
let repo: String

switch repoVersionParts.count {
case 2:
repo = repoVersionParts[0]
version = repoVersionParts[1]
case 1:
repo = repoVersion
version = ""
default:
throw CommandError.invalidRepo(repoVersion)
}

switch args.count {
case 2:
command = args[1]
case 1:
command = repo.components(separatedBy: "/").last!.components(separatedBy: ".").first!
default:
throw CommandError.tooManyArguments
}
return (repo: repo,
version: version,
command: command,
verbose: flags.getBool(name: "verbose") ?? false,
global: flags.getBool(name: "global") ?? false)
}

let versionFlag = Flag(longName: "version", value: false, description: "Prints the version")
let verboseFlag = Flag(longName: "verbose", value: false, description: "Show installation output")
let globalFlag = Flag(longName: "global", value: false, description: "Install executable globally so it's accessible without \"mint run\". This will overwrite any other globally installed versions. An extra $PATH entry will also be added")

let command = Command(usage: "mint", flags: [versionFlag])
command.run = { flags, _ in
if let hasVersion = flags.getBool(name: "version"), hasVersion {
print(Mint.version)
return
}
print(command.helpMessage)
}

let commandHelp = """
This command takes allows you to specify a repo, a version and an executable command to run.
- Repo is either in shorthand for a github repo \"githubName/repo\", or a fully qualified .git path.
- An optional version can be specified by appending @version to the repo, otherwise the newest tag or master will be used.
- The second argument qualifies the command name, otherwise this will be assumed to the be the end of the repo name.
"""

let runCommand = Command(usage: "run package(@version) (command)", shortMessage: "Run a package", longMessage: "This will run a package. If it isn't installed if will do so first.\n\(commandHelp) The command can include any arguments and flags but the whole command must then be surrounded in quotes.", flags: [verboseFlag], example: "mint run realm/SwiftLint@0.22.0") { flags, args in
catchError {
let options = try getOptions(flags: flags, args: args)
try mint.run(repo: options.repo, version: options.version, command: options.command, verbose: options.verbose)
}
}

let installCommand = Command(usage: "install package(@version) (command)", shortMessage: "Install a package", longMessage: "This will install a package. If it's already installed no action will be taken.\n\(commandHelp)", flags: [verboseFlag, globalFlag], example: "mint install realm/SwiftLint@0.22.0") { flags, args in
catchError {
let options = try getOptions(flags: flags, args: args)
try mint.install(repo: options.repo, version: options.version, command: options.command, force: false, verbose: options.verbose, global: options.global)
}
}

let updateCommand = Command(usage: "update package(@version) (command)", shortMessage: "Update a package", longMessage: "This will update a package even if it's already installed.\n\(commandHelp)", flags: [verboseFlag, globalFlag], example: "mint install realm/SwiftLint@0.22.0") { flags, args in
catchError {
let options = try getOptions(flags: flags, args: args)
try mint.install(repo: options.repo, version: options.version, command: options.command, force: true, verbose: options.verbose, global: options.global)
}
}

let uninstallCommand = Command(usage: "uninstall (package)", shortMessage: "Uninstall a package", longMessage: "This will uninstall a package by name. See all installed packages with \"mint list\")", example: "mint uninstall SwiftLint") { flags, args in
catchError {
let options = try getOptions(flags: flags, args: args)
try mint.uninstall(name: options.repo)
}
}

let listCommand = Command(usage: "list", shortMessage: "List packages", longMessage: "This lists all the currently installed packages", example: "mint list") { _, _ in
catchError {
try mint.listPackages()
}
}

let bootstrapCommand = Command(usage: "bootstrap") { _, _ in
}

command.add(subCommand: runCommand)
command.add(subCommand: installCommand)
command.add(subCommand: updateCommand)
command.add(subCommand: uninstallCommand)
command.add(subCommand: listCommand)

command.execute()
13 changes: 13 additions & 0 deletions Sources/MintKit/Commands/InstallCommand.swift
@@ -0,0 +1,13 @@
import Foundation
import Utility

class InstallCommand: _InstallCommand {

init(mint: Mint, parser: ArgumentParser) {
super.init(mint: mint, parser: parser, name: "install", description: "Installs a package. If the version is already installed no action will be taken")
}

override func execute(parsedArguments: ArgumentParser.Result, repo: String, version: String, verbose: Bool, executable: String?, global: Bool) throws {
try mint.install(repo: repo, version: version, command: executable, force: false, verbose: verbose, global: global)
}
}
13 changes: 13 additions & 0 deletions Sources/MintKit/Commands/ListCommand.swift
@@ -0,0 +1,13 @@
import Foundation
import Utility

class ListCommand: MintCommand {

init(mint: Mint, parser: ArgumentParser) {
super.init(mint: mint, parser: parser, name: "list", description: "Lists all the currently installed packages")
}

override func execute(parsedArguments: ArgumentParser.Result) throws {
try mint.listPackages()
}
}
16 changes: 16 additions & 0 deletions Sources/MintKit/Commands/MintCommand.swift
@@ -0,0 +1,16 @@
import Foundation
import Utility

class MintCommand {

let mint: Mint
let subparser: ArgumentParser

init(mint: Mint, parser: ArgumentParser, name: String, description: String) {
self.mint = mint
subparser = parser.add(subparser: name, overview: description)
}

func execute(parsedArguments: ArgumentParser.Result) throws {
}
}
39 changes: 39 additions & 0 deletions Sources/MintKit/Commands/MintInterface.swift
@@ -0,0 +1,39 @@
import Basic
import Foundation
import Utility

public class MintInterace {

let mint: Mint

public init(mint: Mint) {
self.mint = mint
}

public func execute(arguments: [String]) throws {
let parser = ArgumentParser(commandName: "mint", usage: "mint [subcommand]", overview: "run and install Swift PM executables")
let versionArgument = parser.add(option: "--version", shortName: "-v", kind: Bool.self, usage: "Prints the current version of Mint")

let commands: [String: MintCommand] = [
"run": RunCommand(mint: mint, parser: parser),
"install": InstallCommand(mint: mint, parser: parser),
"update": UpdateCommand(mint: mint, parser: parser),
"uninstall": UninstallCommand(mint: mint, parser: parser),
"list": ListCommand(mint: mint, parser: parser),
]

let parsedArguments = try parser.parse(arguments)

if let printVersion = parsedArguments.get(versionArgument), printVersion == true {
print(Mint.version)
return
}

if let subParser = parsedArguments.subparser(parser),
let command = commands[subParser] {
try command.execute(parsedArguments: parsedArguments)
} else {
parser.printUsage(on: stdoutStream)
}
}
}
41 changes: 41 additions & 0 deletions Sources/MintKit/Commands/PackageCommand.swift
@@ -0,0 +1,41 @@
import Foundation
import Utility

class PackageCommand: MintCommand {

var verboseArgument: OptionArgument<Bool>!
var packageArgument: PositionalArgument<String>!

override init(mint: Mint, parser: ArgumentParser, name: String, description: String) {
super.init(mint: mint, parser: parser, name: name, description: description)

let packageHelp = """
The identifier for the Swift Package to use. It can be a shorthand for a github repo \"githubName/repo\", or a fully qualified .git path.
An optional version can be specified by appending @version to the repo, otherwise the newest tag will be used (or master if no tags are found)
"""
packageArgument = subparser.add(positional: "package", kind: String.self, optional: false, usage: packageHelp)
verboseArgument = subparser.add(option: "--verbose", kind: Bool.self, usage: "Show installation output")
}

override func execute(parsedArguments: ArgumentParser.Result) throws {
let verbose = parsedArguments.get(verboseArgument) ?? false
let package = parsedArguments.get(packageArgument)!

let version: String
let packageParts = package.components(separatedBy: "@")
let repo: String

if packageParts.count > 1 {
repo = packageParts[0]
version = packageParts[1]
} else {
repo = package
version = ""
}

try execute(parsedArguments: parsedArguments, repo: repo, version: version, verbose: verbose)
}

func execute(parsedArguments: ArgumentParser.Result, repo: String, version: String, verbose: Bool) throws {
}
}
17 changes: 17 additions & 0 deletions Sources/MintKit/Commands/RunCommand.swift
@@ -0,0 +1,17 @@
import Foundation
import Utility

class RunCommand: PackageCommand {

var commandArgument: PositionalArgument<[String]>!

init(mint: Mint, parser: ArgumentParser) {
super.init(mint: mint, parser: parser, name: "run", description: "Installs and then runs a package")
commandArgument = subparser.add(positional: "command", kind: [String].self, optional: true, strategy: .remaining, usage: "The command to run. This will default to the package name")
}

override func execute(parsedArguments: ArgumentParser.Result, repo: String, version: String, verbose: Bool) throws {
let arguments = parsedArguments.get(commandArgument)
try mint.run(repo: repo, version: version, verbose: verbose, arguments: arguments)
}
}
17 changes: 17 additions & 0 deletions Sources/MintKit/Commands/UninstallCommand.swift
@@ -0,0 +1,17 @@
import Foundation
import Utility

class UninstallCommand: MintCommand {

var packageArgument: PositionalArgument<String>!

init(mint: Mint, parser: ArgumentParser) {
super.init(mint: mint, parser: parser, name: "uninstall", description: "Uninstalls a package by name.\nUse mint list to see all installed packages")
packageArgument = subparser.add(positional: "name", kind: String.self, optional: false, usage: "The name of the package to uninstall")
}

override func execute(parsedArguments: ArgumentParser.Result) throws {
let package = parsedArguments.get(packageArgument)!
try mint.uninstall(name: package)
}
}

0 comments on commit b92689a

Please sign in to comment.