├── .swift-version ├── .codecov.yml ├── .gitignore ├── Sources ├── autobahn │ └── main.swift ├── AutobahnFramework │ ├── Error.swift │ ├── Commands │ │ ├── Action.swift │ │ ├── Actions.swift │ │ ├── Highways.swift │ │ ├── Edit.swift │ │ ├── Init.swift │ │ └── Drive.swift │ └── Autobahn.swift ├── Actions │ ├── Fastlane.swift │ ├── Xcode.swift │ ├── Shell.swift │ └── Action.swift └── AutobahnDescription │ └── AutobahnDescription.swift ├── Tests ├── ActionsTests │ ├── ShellTests.swift │ ├── XcodeTests.swift │ └── FastlaneTests.swift ├── AutobahnDescriptionTests │ └── AutobahnDescriptionTests.swift ├── AutobahnFrameworkTests │ ├── EditTests.swift │ ├── ActionTests.swift │ ├── ActionsTests.swift │ ├── HighwaysTests.swift │ ├── InitTests.swift │ ├── DriveTests.swift │ └── TestConsole.swift └── LinuxMain.swift ├── .circle.yml ├── .sourcery.yml ├── Makefile ├── Vagrantfile ├── .travis.yml ├── .sourcery └── LinuxMain.stencil ├── CONTRIBUTING.md ├── LICENSE ├── Autobahn.swift ├── Package.resolved ├── Package.swift ├── CODE_OF_CONDUCT.md └── README.md /.swift-version: -------------------------------------------------------------------------------- 1 | 4.0 2 | -------------------------------------------------------------------------------- /.codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | range: 0...100 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | 6 | *.log 7 | .vagrant -------------------------------------------------------------------------------- /Sources/autobahn/main.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import AutobahnFramework 3 | 4 | print("Autobahn v0.1.0") 5 | Autobahn.run() 6 | -------------------------------------------------------------------------------- /Tests/ActionsTests/ShellTests.swift: -------------------------------------------------------------------------------- 1 | @testable import Actions 2 | import XCTest 3 | 4 | class ShellTests: XCTestCase { 5 | func testExample() { 6 | XCTAssertTrue(true) 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Tests/ActionsTests/XcodeTests.swift: -------------------------------------------------------------------------------- 1 | @testable import Actions 2 | import XCTest 3 | 4 | class XcodeTests: XCTestCase { 5 | func testExample() { 6 | XCTAssertTrue(true) 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Tests/ActionsTests/FastlaneTests.swift: -------------------------------------------------------------------------------- 1 | @testable import Actions 2 | import XCTest 3 | 4 | class FastlaneTests: XCTestCase { 5 | func testExample() { 6 | XCTAssertTrue(true) 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.circle.yml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | override: 3 | - sudo apt-get install swift 4 | - sudo chmod -R a+rx /usr/ 5 | test: 6 | override: 7 | - swift build 8 | - swift build -c release 9 | - swift test 10 | -------------------------------------------------------------------------------- /Tests/AutobahnDescriptionTests/AutobahnDescriptionTests.swift: -------------------------------------------------------------------------------- 1 | @testable import AutobahnDescription 2 | import XCTest 3 | 4 | class AutobahnDescriptionTests: XCTestCase { 5 | func testExample() { 6 | XCTAssertTrue(true) 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.sourcery.yml: -------------------------------------------------------------------------------- 1 | sources: 2 | - Tests 3 | templates: 4 | - .sourcery 5 | args: 6 | testImports: 7 | - "@testable import AutobahnDescriptionTests" 8 | - "@testable import AutobahnFrameworkTests" 9 | - "@testable import ActionsTests" 10 | -------------------------------------------------------------------------------- /Sources/AutobahnFramework/Error.swift: -------------------------------------------------------------------------------- 1 | 2 | 3 | public enum AutobahnError: Error { 4 | case general(String, help: String) 5 | 6 | var error: String { 7 | switch self { 8 | case .general(let error, _): return error 9 | } 10 | } 11 | var helpMessage: String { 12 | switch self { 13 | case .general(_, let help): return help 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Sources/Actions/Fastlane.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public enum FastlaneError: ActionError { 4 | 5 | } 6 | 7 | enum Fastlane { 8 | static func lane(_ name: String) throws { 9 | try ShellCommand.run("fastlane", args: [name]) 10 | } 11 | 12 | static func lane(_ name: String, platform: String) throws { 13 | try ShellCommand.run("fastlane", args: [platform, name]) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Sources/Actions/Xcode.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public enum XcodeError: ActionError { 4 | 5 | } 6 | 7 | public enum XcodeProject { 8 | public static var versionNumber: String { 9 | return "0.1.0" 10 | } 11 | public static var buildNumber: String { 12 | return "0" 13 | } 14 | } 15 | 16 | public enum XcodeWorkspace { 17 | public static var schemes: [String] { 18 | return [""] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Tests/AutobahnFrameworkTests/EditTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import AutobahnFramework 3 | 4 | class EditTests: XCTestCase { 5 | var console: TestConsole = .default() 6 | lazy var command = Edit(console: self.console) 7 | 8 | // MARK: Tests 9 | 10 | func testExample() throws { 11 | // try command.run(arguments: []) 12 | // 13 | // XCTAssertEqual("Running Hello ...", console.outputBuffer.last ?? "") 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Tests/AutobahnFrameworkTests/ActionTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import AutobahnFramework 3 | 4 | class ActionTests: XCTestCase { 5 | var console: TestConsole = .default() 6 | lazy var command = Action(console: self.console) 7 | 8 | // MARK: Tests 9 | 10 | func testExample() throws { 11 | // try command.run(arguments: []) 12 | // 13 | // XCTAssertEqual("Running Hello ...", console.outputBuffer.last ?? "") 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Tests/AutobahnFrameworkTests/ActionsTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import AutobahnFramework 3 | 4 | class ActionsTests: XCTestCase { 5 | var console: TestConsole = .default() 6 | lazy var command = Actions(console: self.console) 7 | 8 | // MARK: Tests 9 | 10 | func testExample() throws { 11 | // try command.run(arguments: []) 12 | // 13 | // XCTAssertEqual("Running Hello ...", console.outputBuffer.last ?? "") 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Tests/AutobahnFrameworkTests/HighwaysTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import AutobahnFramework 3 | 4 | class HighwaysTests: XCTestCase { 5 | var console: TestConsole = .default() 6 | lazy var command = Highways(console: self.console) 7 | 8 | // MARK: Tests 9 | 10 | func testExample() throws { 11 | // try command.run(arguments: []) 12 | // 13 | // XCTAssertEqual("Running Hello ...", console.outputBuffer.last ?? "") 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Tests/AutobahnFrameworkTests/InitTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import AutobahnFramework 3 | 4 | class InitTests: XCTestCase { 5 | var console: TestConsole = .default() 6 | lazy var command = Init(console: self.console) 7 | 8 | // MARK: Tests 9 | 10 | func testInitNameResolution() throws { 11 | // try command.run(arguments: ["build"]) 12 | // 13 | // XCTAssertEqual("Running Hello ...", console.outputBuffer.last ?? "") 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Tests/AutobahnFrameworkTests/DriveTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import AutobahnFramework 3 | 4 | class DriveTests: XCTestCase { 5 | var console: TestConsole = .default() 6 | lazy var command = Drive(console: self.console) 7 | 8 | // MARK: Tests 9 | 10 | func testDriveNameResolution() throws { 11 | // try command.run(arguments: ["build"]) 12 | // 13 | // XCTAssertEqual("Running Hello ...", console.outputBuffer.last ?? "") 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PREFIX?=/usr/local 2 | CLI_NAME = autobahn 3 | LIB_NAME = libAutobahnDescription.dylib 4 | 5 | all: build install 6 | 7 | build: 8 | swift build --enable-prefetching -c release -Xswiftc -static-stdlib 9 | 10 | install: install_bin install_lib 11 | 12 | install_bin: 13 | mkdir -p $(PREFIX)/bin 14 | install .build/Release/$(CLI_NAME) $(PREFIX)/bin 15 | 16 | install_lib: 17 | mkdir -p $(PREFIX)/lib 18 | install .build/Release/$(LIB_NAME) $(PREFIX)/lib 19 | 20 | uninstall: 21 | rm -f $(INSTALL_PATH) -------------------------------------------------------------------------------- /Sources/AutobahnFramework/Commands/Action.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Console 3 | 4 | public final class Action: Command { 5 | public let id = "action" 6 | public let help: [String] = ["Gives more info about a specific Autobahn action"] 7 | public let console: ConsoleProtocol 8 | 9 | public init(console: ConsoleProtocol) { 10 | self.console = console 11 | } 12 | 13 | public func run(arguments: [String]) throws { 14 | print("action command ran!") 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Sources/AutobahnFramework/Commands/Actions.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Console 3 | 4 | public final class Actions: Command { 5 | public let id = "actions" 6 | public let help: [String] = ["prints a list of available Autobahn actions"] 7 | public let console: ConsoleProtocol 8 | 9 | public init(console: ConsoleProtocol) { 10 | self.console = console 11 | } 12 | 13 | public func run(arguments: [String]) throws { 14 | print("actions command ran!") 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Sources/AutobahnFramework/Commands/Highways.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Console 3 | 4 | public final class Highways: Command { 5 | public let id = "highways" 6 | public let help: [String] = ["Lists all available highways and their description"] 7 | public let console: ConsoleProtocol 8 | 9 | public init(console: ConsoleProtocol) { 10 | self.console = console 11 | } 12 | 13 | public func run(arguments: [String]) throws { 14 | print("highways command ran!") 15 | } 16 | } 17 | 18 | -------------------------------------------------------------------------------- /Sources/AutobahnFramework/Commands/Edit.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Console 3 | 4 | public final class Edit: Command { 5 | public let id = "edit" 6 | public let help: [String] = ["Opens Autobahn.swift in Xcode playground with autocomplete"] 7 | public let signature: [Argument] = [ 8 | Option(name: "path", help: ["Location of your Autobahn.swift file"]) 9 | ] 10 | public let console: ConsoleProtocol 11 | 12 | public init(console: ConsoleProtocol) { 13 | self.console = console 14 | } 15 | 16 | public func run(arguments: [String]) throws { 17 | print("edit command ran!") 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | Vagrant.configure("2") do |config| 5 | 6 | config.vm.box = "ubuntu/xenial64" 7 | config.vm.network "private_network", ip: "192.168.33.10" 8 | config.vm.hostname = "xenial64" 9 | config.vm.synced_folder ".", "/home/ubuntu/swift-package", :mount_options => ["dmode=775", "fmode=776"] 10 | 11 | # Optional NFS. Make sure to remove other synced_folder line too 12 | #config.vm.synced_folder ".", "/var/www", :nfs => { :mount_options => ["dmode=777","fmode=666"] } 13 | 14 | config.vm.provider "virtualbox" do |vm| 15 | vm.memory = 4096 16 | vm.cpus = 4 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /Sources/Actions/Shell.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public enum ShellError: ActionError { 4 | case nonZeroExitCode(String) 5 | } 6 | 7 | public struct ShellCommand: Action { 8 | public let description = "" 9 | public var authors = ["Kaden Wilkinson"] 10 | 11 | public var options: [Option] = [ 12 | Option(key: "cmd") 13 | ] 14 | 15 | public static func run(_ cmd: String, args: [String]) throws { 16 | let proc = Process() 17 | proc.launchPath = "/usr/bin/env" 18 | proc.arguments = [cmd] + args 19 | proc.launch() 20 | proc.waitUntilExit() 21 | 22 | guard proc.terminationStatus == 0 else { 23 | throw ShellError.nonZeroExitCode("Shell command returned with non zero exit code") 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | branches: 2 | only: 3 | - "master" 4 | language: generic 5 | matrix: 6 | include: 7 | - os: linux 8 | before_install: 9 | - wget -q -O - https://swift.org/keys/all-keys.asc | gpg --import - 10 | - wget -q https://swift.org/builds/swift-4.0-branch/ubuntu1404/swift-4.0-DEVELOPMENT-SNAPSHOT-2017-09-08-a/swift-4.0-DEVELOPMENT-SNAPSHOT-2017-09-08-a-ubuntu14.04.tar.gz 11 | - tar xzf swift-4.0-DEVELOPMENT-SNAPSHOT-2017-09-08-a-ubuntu14.04.tar.gz 12 | - export PATH=${PWD}/swift-4.0-DEVELOPMENT-SNAPSHOT-2017-09-08-a-ubuntu14.04/usr/bin:"${PATH}" 13 | script: 14 | - swift build 15 | - .build/debug/autobahn drive ci 16 | - os: osx 17 | osx_image: xcode9 18 | sudo: required 19 | script: 20 | - swift build 21 | - .build/debug/autobahn drive ci 22 | -------------------------------------------------------------------------------- /.sourcery/LinuxMain.stencil: -------------------------------------------------------------------------------- 1 | // sourcery:file:Tests/LinuxMain.swift 2 | import XCTest 3 | 4 | {% for testImport in argument.testImports %} 5 | {{ testImport }} 6 | {% endfor %} 7 | 8 | {% for type in types.classes|based:"XCTestCase" %} 9 | {% if not type.annotations.disableTests %}extension {{ type.name }} { 10 | static var allTests: [(String, ({{ type.name }}) -> () throws -> Void)] = [ 11 | {% for method in type.methods %}{% if method.parameters.count == 0 and method.shortName|hasPrefix:"test" %} ("{{ method.shortName }}", {{ method.shortName }}){% if not forloop.last %},{% endif %} 12 | {% endif %}{% endfor %}] 13 | } 14 | 15 | {% endif %}{% endfor %} 16 | XCTMain([ 17 | {% for type in types.classes|based:"XCTestCase" %}{% if not type.annotations.disableTests %} testCase({{ type.name }}.allTests), 18 | {% endif %}{% endfor %}]) 19 | // sourcery:end -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Autobahn 2 | 3 | 👍🎉 First off, thanks for taking the time to contribute! 🎉👍 4 | 5 | ### Feature Requests 6 | 7 | Go ahead and open an issue describing the feature you would like to see added to Autobahn. There we can discuss how we could go about adding that feature. 8 | 9 | ### Development 10 | 11 | To get started developing Autobahn follow the these steps 12 | 13 | ```sh 14 | $ git clone https://github.com/kdawgwilk/Autobahn.git 15 | $ cd Autobahn 16 | $ swift build 17 | 18 | # You can run the cli from the build dir like this: 19 | # .build/debug/autobahn drive 20 | $ .build/debug/autobahn drive build 21 | 22 | # Or I find it helpful to symlink to `/usr/local/bin` so I can run it directly 23 | $ ln -s `pwd`/.build/debug/autobahn /usr/local/bin/autobahn 24 | 25 | # autobahn drive 26 | $ autobahn drive build 27 | ``` -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-present Kaden Wilkinson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Autobahn.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import AutobahnDescription 3 | 4 | enum Highway: String, AutobahnDescription.Highway { 5 | case build, buildRelease 6 | case test 7 | case ci 8 | case deploy, release 9 | case experiments 10 | } 11 | 12 | Autobahn(Highway.self) 13 | 14 | .beforeAll { highway in 15 | 16 | } 17 | 18 | .highway(.build) { 19 | print("Building...") 20 | try sh("swift", "build", "--enable-prefetching") 21 | } 22 | 23 | .highway(.buildRelease) { 24 | try sh("swift", "build", "--enable-prefetching", "-c", "release", "--static-swift-stdlib") 25 | } 26 | 27 | .highway(.test) { 28 | try sh("swift", "test", "--enable-prefetching") 29 | } 30 | 31 | .highway(.ci, dependsOn: [.buildRelease, .test]) 32 | 33 | .highway(.deploy) { 34 | print("Deploying...") 35 | } 36 | 37 | .highway(.release, dependsOn: [.build, .deploy]) { 38 | print("Releasing...") 39 | } 40 | 41 | .highway(.experiments) { 42 | try ShellCommand.run("swift", args: ["test"]) 43 | } 44 | 45 | .afterAll { highway in 46 | print("Successfully drove highway: \(highway)") 47 | } 48 | 49 | .onError { name, error in 50 | print("Error driving highway: \(name)") 51 | print("Error: \(error)") 52 | } 53 | 54 | .drive() 55 | -------------------------------------------------------------------------------- /Sources/Actions/Action.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public enum ActionCategory { 4 | case testing 5 | case building 6 | case screenshots 7 | case project 8 | case codeSigning 9 | case documentation 10 | case beta 11 | case push 12 | case production 13 | case sourceControl 14 | case notifications 15 | case misc 16 | case deprecated 17 | } 18 | 19 | public struct Option { 20 | let key: String 21 | let envName: String? 22 | let description: String? 23 | 24 | init(key: String, envName: String? = nil, description: String? = nil) { 25 | self.key = key 26 | self.envName = envName 27 | self.description = description 28 | } 29 | } 30 | 31 | public protocol ActionError: Error { 32 | 33 | } 34 | 35 | public protocol Action { 36 | var id: String { get } 37 | var description: String { get } 38 | var codeExamples: [String] { get } 39 | var authors: [String] { get } 40 | var options: [Option] { get } 41 | } 42 | 43 | public extension Action { 44 | var id: String { 45 | return String(describing: Self.self) 46 | } 47 | var description: String { return "" } 48 | var codeExamples: [String] { return [] } 49 | var authors: [String] { return [] } 50 | } 51 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "Bits", 6 | "repositoryURL": "https://github.com/vapor/bits.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "c32f5e6ae2007dccd21a92b7e33eba842dd80d2f", 10 | "version": "1.1.0" 11 | } 12 | }, 13 | { 14 | "package": "Console", 15 | "repositoryURL": "https://github.com/vapor/console.git", 16 | "state": { 17 | "branch": null, 18 | "revision": "11c0694857d1be6c7b8b30d8db8b1162b73f2a2b", 19 | "version": "2.2.0" 20 | } 21 | }, 22 | { 23 | "package": "Core", 24 | "repositoryURL": "https://github.com/vapor/core.git", 25 | "state": { 26 | "branch": null, 27 | "revision": "b8330808f4f6b69941961afe8ad6b015562f7b7c", 28 | "version": "2.1.2" 29 | } 30 | }, 31 | { 32 | "package": "Debugging", 33 | "repositoryURL": "https://github.com/vapor/debugging.git", 34 | "state": { 35 | "branch": null, 36 | "revision": "49c5e8f0a7cb5456a8f7c72c6cd9f1553e5885a8", 37 | "version": "1.1.0" 38 | } 39 | } 40 | ] 41 | }, 42 | "version": 1 43 | } 44 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:4.0 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "Autobahn", 8 | products: [ 9 | .executable(name: "autobahn", targets: ["autobahn"]), 10 | .library(name: "AutobahnDescription", type: .dynamic, targets: ["AutobahnDescription"]), 11 | ], 12 | dependencies: [ 13 | // .package(url: "https://github.com/swift-xcode/xcodeproj.git", .upToNextMajor(from: "0.1.2")), 14 | // .package(url: "https://github.com/kdawgwilk/Shuttle.git", .branch("master")), 15 | .package(url: "https://github.com/vapor/console.git", .upToNextMajor(from: "2.2.0")), 16 | // .package(url: "https://github.com/Quick/Quick.git", .branch("support-swift-4")), 17 | // .package(url: "https://github.com/Quick/Nimble.git", .branch("support-swift-4")), 18 | // .package(url: "https://github.com/JohnSundell/Files.git", .upToNextMajor(from: "1.11.0")), 19 | // .package(url: "https://github.com/JohnSundell/ShellOut.git", .upToNextMajor(from: "1.2.1")), 20 | ], 21 | targets: [ 22 | .target(name: "autobahn", dependencies: ["AutobahnFramework"]), 23 | .target(name: "AutobahnDescription", dependencies: ["Actions"]), 24 | .target(name: "AutobahnFramework", dependencies: ["Console", "AutobahnDescription"]), 25 | .target(name: "Actions", dependencies: []),//["Shuttle", "xcodeproj"]), 26 | 27 | .testTarget(name: "AutobahnDescriptionTests", dependencies: ["AutobahnDescription"]), 28 | .testTarget(name: "AutobahnFrameworkTests", dependencies: ["AutobahnFramework"]), 29 | .testTarget(name: "ActionsTests", dependencies: ["Actions"]), 30 | ], 31 | swiftLanguageVersions: [3, 4] 32 | ) 33 | -------------------------------------------------------------------------------- /Sources/AutobahnFramework/Commands/Init.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Console 3 | 4 | public final class Init: Command { 5 | public let id = "init" 6 | public let help: [String] = ["Creates a default Autobahn.swift file"] 7 | public let signature: [Argument] = [ 8 | Option(name: "path", help: ["Location where you would like it to be created"]) 9 | ] 10 | public let console: ConsoleProtocol 11 | 12 | public init(console: ConsoleProtocol) { 13 | self.console = console 14 | } 15 | 16 | public func run(arguments: [String]) throws { 17 | // let path = try value("path", from: arguments) 18 | let path = "Autobahn.swift" 19 | let fm = FileManager.default 20 | if fm.fileExists(atPath: path) { 21 | let answer = console.ask("Do you want to override the existing Autobahn.swift file? (y/n): ") 22 | if answer == "y" { 23 | let fileData = autobahnSwiftTemplate.data(using: .utf8) 24 | if !fm.createFile(atPath: path, contents: fileData, attributes: nil) { 25 | throw AutobahnError.general("Unable to create Autobahn.swift", help: "¯\\_(ツ)_/¯") 26 | } 27 | } else { 28 | console.warning("Init aborted!") 29 | } 30 | } 31 | } 32 | } 33 | 34 | let autobahnSwiftTemplate = """ 35 | import Foundation 36 | import AutobahnDescription 37 | 38 | beforeAll { highway in 39 | print("Driving highway: \\(highway)") 40 | } 41 | 42 | highway("build") { 43 | print("Building...") 44 | try sh("swift", "build") 45 | } 46 | 47 | highway("test") { 48 | try sh("swift", "test") 49 | } 50 | 51 | highway("deploy") { 52 | print("Deploying...") 53 | } 54 | 55 | highway("release", dependsOn: ["build", "deploy"]) { 56 | print("Releasing...") 57 | } 58 | 59 | afterAll { highway in 60 | print("Successfully drove highway: \\(highway)") 61 | } 62 | 63 | onError { highway, error in 64 | print("Error driving highway: \\(highway)") 65 | print("Error: \\(error.localizedDescription)") 66 | } 67 | """ 68 | -------------------------------------------------------------------------------- /Sources/AutobahnFramework/Autobahn.swift: -------------------------------------------------------------------------------- 1 | #if os(Linux) 2 | import Glibc 3 | #else 4 | import Darwin.C 5 | #endif 6 | 7 | import Foundation 8 | import Console 9 | 10 | 11 | public final class Autobahn { 12 | 13 | public static func run() { 14 | var args = CommandLine.arguments 15 | let executable = args.removeFirst() 16 | 17 | let terminal = Terminal(arguments: args) 18 | 19 | let group = Group(id: executable, commands: [ 20 | // Action(console: terminal), 21 | // Actions(console: terminal), 22 | // Highways(console: terminal), 23 | Drive(console: terminal), 24 | // Edit(console: terminal), 25 | Init(console: terminal), 26 | ], help: [ 27 | "CLI for 'autobahn' - The swiftiest way to automate mundane developer tasks for your iOS apps", 28 | ], fallback: Drive(console: terminal)) 29 | 30 | do { 31 | try terminal.run(group, arguments: args) 32 | } catch let error as AutobahnError { 33 | terminal.error("Error: ", newLine: false) 34 | terminal.print(error.error) 35 | terminal.info("Help: ", newLine: false) 36 | terminal.print(error.helpMessage) 37 | exit(1) 38 | } catch ConsoleError.insufficientArguments { 39 | terminal.error("Error: ", newLine: false) 40 | terminal.print("Insufficient arguments.") 41 | } catch ConsoleError.help { 42 | exit(0) 43 | } catch ConsoleError.cancelled { 44 | print("Cancelled") 45 | exit(2) 46 | } catch ConsoleError.noCommand { 47 | terminal.error("Error: ", newLine: false) 48 | terminal.print("No command supplied.") 49 | } catch ConsoleError.commandNotFound(let id) { 50 | do { 51 | let drive = Drive(console: terminal) 52 | try drive.run(arguments: args) 53 | } catch { 54 | terminal.error("Error: ", newLine: false) 55 | terminal.print("Command \"\(id)\" not found.") 56 | exit(1) 57 | } 58 | } catch { 59 | terminal.error("Error: ", newLine: false) 60 | terminal.print("\(error)") 61 | exit(1) 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | // Generated using Sourcery 0.8.0 — https://github.com/krzysztofzablocki/Sourcery 2 | // DO NOT EDIT 3 | 4 | import XCTest 5 | 6 | @testable import AutobahnDescriptionTests 7 | @testable import AutobahnFrameworkTests 8 | @testable import ActionsTests 9 | 10 | extension ActionTests { 11 | static var allTests: [(String, (ActionTests) -> () throws -> Void)] = [ 12 | ("testExample", testExample) 13 | ] 14 | } 15 | 16 | extension ActionsTests { 17 | static var allTests: [(String, (ActionsTests) -> () throws -> Void)] = [ 18 | ("testExample", testExample) 19 | ] 20 | } 21 | 22 | extension AutobahnDescriptionTests { 23 | static var allTests: [(String, (AutobahnDescriptionTests) -> () throws -> Void)] = [ 24 | ("testExample", testExample) 25 | ] 26 | } 27 | 28 | extension DriveTests { 29 | static var allTests: [(String, (DriveTests) -> () throws -> Void)] = [ 30 | ("testDriveNameResolution", testDriveNameResolution) 31 | ] 32 | } 33 | 34 | extension EditTests { 35 | static var allTests: [(String, (EditTests) -> () throws -> Void)] = [ 36 | ("testExample", testExample) 37 | ] 38 | } 39 | 40 | extension FastlaneTests { 41 | static var allTests: [(String, (FastlaneTests) -> () throws -> Void)] = [ 42 | ("testExample", testExample) 43 | ] 44 | } 45 | 46 | extension HighwaysTests { 47 | static var allTests: [(String, (HighwaysTests) -> () throws -> Void)] = [ 48 | ("testExample", testExample) 49 | ] 50 | } 51 | 52 | extension InitTests { 53 | static var allTests: [(String, (InitTests) -> () throws -> Void)] = [ 54 | ("testInitNameResolution", testInitNameResolution) 55 | ] 56 | } 57 | 58 | extension ShellTests { 59 | static var allTests: [(String, (ShellTests) -> () throws -> Void)] = [ 60 | ("testExample", testExample) 61 | ] 62 | } 63 | 64 | extension XcodeTests { 65 | static var allTests: [(String, (XcodeTests) -> () throws -> Void)] = [ 66 | ("testExample", testExample) 67 | ] 68 | } 69 | 70 | XCTMain([ 71 | testCase(ActionTests.allTests), 72 | testCase(ActionsTests.allTests), 73 | testCase(AutobahnDescriptionTests.allTests), 74 | testCase(DriveTests.allTests), 75 | testCase(EditTests.allTests), 76 | testCase(FastlaneTests.allTests), 77 | testCase(HighwaysTests.allTests), 78 | testCase(InitTests.allTests), 79 | testCase(ShellTests.allTests), 80 | testCase(XcodeTests.allTests), 81 | ]) 82 | -------------------------------------------------------------------------------- /Sources/AutobahnFramework/Commands/Drive.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Console 3 | 4 | 5 | extension Array where Element == String { 6 | var isVerbose: Bool { 7 | return flag("verbose") 8 | } 9 | } 10 | 11 | public final class Drive: Command { 12 | public let id = "drive" 13 | public let help: [String] = ["*Drive a specific highway"] 14 | public let signature: [Argument] = [ 15 | Value(name: "highway", help: [ 16 | "The name of the highway you would like to drive", 17 | ]), 18 | Option(name: "verbose", help: ["Print more info to help debug issues"]), 19 | 20 | ] 21 | public let console: ConsoleProtocol 22 | 23 | private let supportedPaths = ["Autobahn.swift", "autobahn/Autobahn.swift", "Autobahn/Autobahn.swift", ".autobahn/Autobahn.swift"] 24 | // Finds first occurrence of supported path 25 | private var resolvedPath: String? { 26 | return supportedPaths.first { path in 27 | return FileManager.default.fileExists(atPath: path) 28 | } 29 | } 30 | 31 | public init(console: ConsoleProtocol) { 32 | self.console = console 33 | } 34 | 35 | public func run(arguments: [String]) throws { 36 | let isVerbose = arguments.isVerbose 37 | 38 | // Exit if a Autobahn.swift was not found at any supported path 39 | guard let autobahnFilePath = resolvedPath else { 40 | let error = "Could not find a Autobahn.swift file" 41 | let help = "Please use a supported path: \n\(supportedPaths)" 42 | throw AutobahnError.general(error, help: help) 43 | } 44 | 45 | var args = [String]() 46 | args += ["swiftc"] 47 | args += ["--driver-mode=swift"] // Eval in swift mode, I think? 48 | args += ["-L", ".build/debug"] // Find libs inside this folder (may need to change in production) 49 | args += ["-I", ".build/debug"] // Find libs inside this folder (may need to change in production) 50 | //args += ["-L", "/usr/local/lib"] 51 | //args += ["-I", "/usr/local/lib"] 52 | args += ["-lAutobahnDescription"] // Eval the code with the Target Autobahn added 53 | isVerbose ? args += ["-v"] : () 54 | args += [autobahnFilePath] // The Autobahn.swift 55 | 56 | args += arguments 57 | 58 | // More portable for Linux/macOS 59 | let cmd = "/usr/bin/env" 60 | isVerbose ? print("Command: \(cmd) \(args.joined(separator: " "))") : () 61 | let proc = Process() 62 | proc.launchPath = cmd 63 | proc.arguments = args 64 | proc.launch() 65 | proc.waitUntilExit() 66 | 67 | if proc.terminationStatus != 0 { 68 | let error = "Running Autobahn.swift failed" 69 | let help = "Try running with the --verbose flag to get more info" 70 | throw AutobahnError.general(error, help: help) 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | * Our Responsibilities 25 | 26 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 27 | 28 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 29 | 30 | ## Scope 31 | 32 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 33 | 34 | ## Enforcement 35 | 36 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at [INSERT EMAIL ADDRESS]. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 37 | 38 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 39 | 40 | ## Attribution 41 | 42 | This Code of Conduct is adapted from the Contributor Covenant, version 1.4, available at http://contributor-covenant.org/version/1/4. -------------------------------------------------------------------------------- /Tests/AutobahnFrameworkTests/TestConsole.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import Console 3 | import libc 4 | @testable import AutobahnFramework 5 | 6 | final class TestConsole: ConsoleProtocol { 7 | /// Returns a string of input read from the console 8 | /// until a line feed character was found, 9 | /// hides entry for security 10 | func secureInput() -> String { 11 | return "" 12 | } 13 | 14 | var inputBuffer: [String] 15 | var outputBuffer: [String] 16 | var executeBuffer: [String] 17 | var backgroundExecuteOutputBuffer: [String: String] 18 | var newLine: Bool 19 | 20 | // MARK: Protocol conformance 21 | var confirmOverride: Bool? 22 | var size: (width: Int, height: Int) 23 | 24 | init() { 25 | inputBuffer = [] 26 | outputBuffer = [] 27 | executeBuffer = [] 28 | backgroundExecuteOutputBuffer = [:] 29 | 30 | confirmOverride = true 31 | size = (0, 0) 32 | newLine = false 33 | } 34 | 35 | 36 | func output(_ string: String, style: ConsoleStyle, newLine: Bool) { 37 | if self.newLine { 38 | self.newLine = false 39 | outputBuffer.append("") 40 | } 41 | 42 | let last = outputBuffer.last ?? "" 43 | outputBuffer = Array(outputBuffer.dropLast()) 44 | outputBuffer.append(last + string) 45 | 46 | self.newLine = newLine 47 | } 48 | 49 | 50 | func input() -> String { 51 | let input = inputBuffer.joined(separator: "\n") 52 | inputBuffer = [] 53 | return input 54 | } 55 | 56 | func clear(_ clear: ConsoleClear) { 57 | switch clear { 58 | case .line: 59 | outputBuffer = Array(outputBuffer.dropLast()) 60 | case .screen: 61 | outputBuffer = [] 62 | } 63 | } 64 | 65 | func execute(program: String, arguments: [String], input: Int32?, output: Int32?, error: Int32?) throws { 66 | exec(program, args: arguments) 67 | } 68 | 69 | func backgroundExecute(program: String, arguments: [String]) throws -> String { 70 | exec(program, args: arguments) 71 | let command = program + " " + arguments.joined(separator: " ") 72 | guard let val = backgroundExecuteOutputBuffer[command] else { 73 | throw AutobahnError.general("No command set for '\(command)'", help: "") 74 | } 75 | return val 76 | } 77 | 78 | private func exec(_ command: String, args: [String]) { 79 | executeBuffer.append(command + (!args.isEmpty ? " " + args.joined(separator: " ") : "")) 80 | } 81 | 82 | static func `default`() -> TestConsole { 83 | let console = TestConsole() 84 | 85 | // swift commands 86 | // console.backgroundExecuteOutputBuffer["swift package --enable-prefetching fetch"] = "" 87 | // console.backgroundExecuteOutputBuffer["swift package fetch"] = "" 88 | // console.backgroundExecuteOutputBuffer["swift build --enable-prefetching"] = "" 89 | // console.backgroundExecuteOutputBuffer["swift build"] = "" 90 | // 91 | // console.backgroundExecuteOutputBuffer["swift package dump-package"] = try! JSON(["name": "Hello"]).serialize().makeString() 92 | // // find commands 93 | // console.backgroundExecuteOutputBuffer["find ./Sources -type f -name main.swift"] = 94 | // "~/Desktop/MyProject/Sources/Hello/main.swift" 95 | // 96 | // // ls commands 97 | // console.backgroundExecuteOutputBuffer["ls .build/debug/Hello"] = ".build/debug/Hello\n" 98 | // console.backgroundExecuteOutputBuffer["ls -a ."] = "" 99 | // console.backgroundExecuteOutputBuffer["ls .build/debug"] = "" 100 | // console.backgroundExecuteOutputBuffer["ls .build/release"] = "" 101 | // console.backgroundExecuteOutputBuffer["ls .build/release/Hello"] = ".build/release/Hello\n" 102 | // 103 | // // rm commands 104 | // console.backgroundExecuteOutputBuffer["rm -rf .build"] = "" 105 | // console.backgroundExecuteOutputBuffer["rm -rf *.xcodeproj"] = "" 106 | // console.backgroundExecuteOutputBuffer["rm -rf Package.pins"] = "" 107 | return console 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |

Autobahn

3 | 4 |

5 | 6 | Travis status 7 | 8 | 11 | 12 | 13 | 14 | Version 15 | 16 | Swift 4.0 17 | 18 | 21 | 22 | License 23 | 24 | 25 | Twitter: @kdawgwilk 26 | 27 |

28 | 29 | Autobahn is a set of tools (written in Swift) heavily inspired by [fastlane](https://github.com/fastlane/fastlane) to automate many day to day tasks associated with development of apps in the Apple ecosystem. Currently most progess has been made on a sub package called [Shuttle](https://github.com/kdawgwilk/Shuttle) that is basically a port of fastlane's [spaceship](https://github.com/fastlane/fastlane/tree/master/spaceship) which is an HTTP client for interacting with the Apple Developer portal and iTunesConnect. 30 | 31 | >NOTE: This is still a work in progress and there is still much to do, here is a rough list of things I would like to see in the near future 32 | 33 | ### ToDO List: 34 | 35 | - [x] DSL for defining `highways` 36 | - [ ] >90% Code Coverage 37 | - [ ] CLI tool 38 | - [x] `drive ` drives the highway specified 39 | - [ ] need to make this the default still 40 | - [x] `init` creates template `Autobahn.swift` file 41 | - [ ] `edit` command that creates a temp playground with autocomplete working for Autobahn.swift 42 | - [ ] `verbose` obviously 43 | - [ ] `actions` lists all available actions 44 | - [ ] `action ` that describes the action 45 | - [x] `help` to explain how each command works 46 | - [x] Autobahn.swift config file see [danger-swift](https://github.com/danger/danger-swift) 47 | - [ ] Homebrew install support 48 | - [ ] Plugin architecture 49 | - [ ] Git support 50 | - [ ] Support `.env` 51 | 52 | 53 | ## Usage 54 | 55 | ``` 56 | $ autobahn drive [highway] 57 | ``` 58 | 59 | ## Example Autobahn.swift 60 | 61 | ```swift 62 | enum Highway: String, AutobahnDescription.Highway { 63 | case build, test, deploy, release 64 | } 65 | 66 | Autobahn(Highway.self) 67 | 68 | .beforeAll { highway in 69 | print("Driving highway: \(highway)") 70 | } 71 | 72 | .highway(.build) { 73 | print("Building...") 74 | try sh("swift", "build") 75 | } 76 | 77 | .highway(.test) { 78 | try sh("swift", "test") 79 | } 80 | 81 | .highway(.deploy) { 82 | print("Deploying...") 83 | } 84 | 85 | .highway(.release, dependsOn: [.build, .deploy]) { 86 | print("Releasing...") 87 | } 88 | 89 | .afterAll { highway in 90 | print("Successfully drove highway: \(highway)") 91 | } 92 | 93 | .onError { name, error in 94 | print("Error driving highway: \(name)") 95 | print("Error: \(error)") 96 | } 97 | 98 | .drive() 99 | ``` 100 | 101 | ## Installation 102 | 103 | TODO: Not supported quite yet 104 | 105 | ``` 106 | $ brew tap kdawgwilk/homebrew-tap 107 | $ brew install autobahn 108 | ``` 109 | 110 | ### Development 111 | 112 | ```sh 113 | $ git clone https://github.com/kdawgwilk/Autobahn.git 114 | $ cd Autobahn 115 | $ swift build 116 | 117 | # You can run the cli from the build dir like this: 118 | # .build/debug/autobahn drive 119 | $ .build/debug/autobahn drive build 120 | 121 | # Or I find it helpful to symlink to `/usr/local/bin` so I can run it directly 122 | $ ln -s `pwd`/.build/debug/autobahn /usr/local/bin/autobahn 123 | 124 | # autobahn drive 125 | $ autobahn drive build 126 | ``` 127 | 128 | ### 🚀 Contributing 129 | 130 | All developers should feel welcome and encouraged to contribute to Autobahn, see our [getting started](/CONTRIBUTING.md) document here to get involved. 131 | 132 | ### 💙 Code of Conduct 133 | 134 | Our goal is to create a safe and empowering environment for anyone who decides to use or contribute to Autobahn. Please help us make the community a better place by abiding to this [Code of Conduct](/CODE_OF_CONDUCT.md) during your interactions surrounding this project. 135 | 136 | > This project is in no way affiliated with Apple Inc. 137 | -------------------------------------------------------------------------------- /Sources/AutobahnDescription/AutobahnDescription.swift: -------------------------------------------------------------------------------- 1 | #if os(Linux) 2 | import Glibc 3 | #else 4 | import Darwin.C 5 | #endif 6 | 7 | import Foundation 8 | @_exported import Actions 9 | 10 | private extension Array { 11 | var second: Element? { 12 | guard count > 1 else { 13 | return nil 14 | } 15 | return self[1] 16 | } 17 | } 18 | 19 | /// Highway is the protocol which every highway should follow. 20 | /// 21 | /// We suggest defining the highways as an enum like this: 22 | /// 23 | /// ```Swift 24 | /// enum MyHighways { 25 | /// case build, test, deploy, release 26 | /// 27 | /// var usage: String { 28 | /// switch self { 29 | /// case .build: return "This is how you should use build:…" 30 | /// // … handle all cases 31 | /// } 32 | /// } 33 | /// ``` 34 | public protocol Highway: RawRepresentable, Hashable where RawValue == String { 35 | var usage: String { get } 36 | } 37 | 38 | public extension Highway { 39 | var usage: String { 40 | return "No usage specified for \(self)" 41 | } 42 | } 43 | 44 | public final class Autobahn { 45 | public let version = "0.1.0" 46 | 47 | private var highwayStart: Date 48 | private var highwayEnd: Date? 49 | private var beforeAllHandler: ((H) throws -> Void)? 50 | private var highways = [H: () throws -> Void]() 51 | private var dependings = [H: [H]]() 52 | private var afterAllHandler: ((H) throws -> Void)? 53 | private var onErrorHandler: ((String, Error) -> Void)? 54 | 55 | // MARK: - Possible errors 56 | 57 | public enum HighwayError: Error { 58 | case noHighwaySpecified 59 | case noValidHighwayName(String) 60 | case highwayNotDefined(String) 61 | } 62 | 63 | // MARK: - Public Functions 64 | 65 | public init(_ highwayType: H.Type) { 66 | self.highwayStart = Date() 67 | } 68 | 69 | public func beforeAll(handler: @escaping (H) throws -> Void) -> Autobahn { 70 | precondition(beforeAllHandler == nil, "beforeAll declared more than once") 71 | beforeAllHandler = handler 72 | return self 73 | } 74 | 75 | public func highway(_ highway: H, dependsOn highways: [H], handler: @escaping () throws -> Void) -> Autobahn { 76 | return self.addHighway(highway, dependsOn: highways, handler: handler) 77 | } 78 | 79 | public func highway(_ highway: H, handler: @escaping () throws -> Void) -> Autobahn { 80 | return self.addHighway(highway, handler: handler) 81 | } 82 | 83 | public func highway(_ highway: H, dependsOn highways: [H]) -> Autobahn { 84 | return self.addHighway(highway, dependsOn: highways) 85 | } 86 | 87 | public func afterAll(handler: @escaping (H) throws -> Void) -> Autobahn { 88 | precondition(afterAllHandler == nil, "afterAll declared more than once") 89 | afterAllHandler = handler 90 | return self 91 | } 92 | 93 | public func onError(handler: @escaping (String, Error) -> Void) -> Autobahn { 94 | precondition(onErrorHandler == nil, "onError declared more than once") 95 | onErrorHandler = handler 96 | return self 97 | } 98 | 99 | public func drive() { 100 | let args = CommandLine.arguments 101 | let secondArg = args.second 102 | 103 | do { 104 | guard let raw = secondArg else { 105 | throw HighwayError.noHighwaySpecified 106 | } 107 | guard let currentHighway = H(rawValue: raw) else { 108 | throw HighwayError.noValidHighwayName(raw) 109 | } 110 | try self.beforeAllHandler?(currentHighway) 111 | try self.driveHighway(currentHighway) 112 | try self.afterAllHandler?(currentHighway) 113 | } catch { 114 | self.onErrorHandler?(secondArg ?? "", error) 115 | } 116 | highwayEnd = Date() 117 | 118 | let duration = highwayEnd!.timeIntervalSince(highwayStart) 119 | let rounded = Double(round(100 * duration) / 100) 120 | print("Duration: \(rounded)s") 121 | } 122 | 123 | // MARK: - Private 124 | 125 | private func addHighway(_ highway: H, dependsOn highways: [H]? = nil, handler: (() throws -> Void)? = nil) -> Autobahn { 126 | self.highways[highway] = handler 127 | dependings[highway] = highways 128 | return self 129 | } 130 | 131 | private func driveHighway(_ highway: H) throws { 132 | if let dependings = self.dependings[highway] { 133 | for dependingHighway in dependings { 134 | guard let d = self.highways[dependingHighway] else { 135 | throw HighwayError.highwayNotDefined(dependingHighway.rawValue) 136 | } 137 | printHeader(for: dependingHighway) 138 | try d() 139 | } 140 | } 141 | if let handler = highways[highway] { 142 | printHeader(for: highway) 143 | try handler() 144 | } 145 | } 146 | 147 | private func printHeader(for highway: H) { 148 | let count = highway.rawValue.count 149 | print("-------------\(String(repeating: "-", count: count))----") 150 | print("--- Highway: \(highway.rawValue) ---") 151 | print("-------------\(String(repeating: "-", count: count))----") 152 | } 153 | } 154 | 155 | public func sh(_ cmd: String, _ args: String...) throws { 156 | try ShellCommand.run(cmd, args: args) 157 | } 158 | --------------------------------------------------------------------------------