├── .gitignore ├── .swift-version ├── .swiftlint.yml ├── .travis.yml ├── LICENSE ├── Makefile ├── Package.resolved ├── Package.swift ├── README.md ├── Sources ├── PackageBuilder │ └── main.swift └── PackageBuilderCore │ └── PackageBuilder.swift ├── Templates ├── LinuxMain.swift ├── Makefile ├── Package.swift ├── README.md ├── XCTestManifests.swift ├── main.swift ├── {PACKAGE_NAME}.swift └── {PACKAGE_NAME}Tests.swift └── Tests ├── LinuxMain.swift └── PackageBuilderTests └── PackageBuilderTests.swift /.gitignore: -------------------------------------------------------------------------------- 1 | ## Build generated 2 | build/ 3 | DerivedData/ 4 | *.xcodeproj/ 5 | 6 | # Swift Package Manager 7 | # 8 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 9 | Packages/ 10 | .build/ 11 | 12 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 5.1 2 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | disabled_rules: 2 | - line_length 3 | - force_try 4 | - force_cast 5 | 6 | included: 7 | - Sources 8 | - Tests 9 | 10 | type_body_length: 11 | - 700 #warning 12 | - 1000 #error 13 | 14 | file_length: 15 | - 1000 #warning 16 | - 1200 #error 17 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: linux 2 | language: generic 3 | sudo: required 4 | dist: trusty 5 | env: 6 | - SWIFT_VERSION=5.1 7 | install: 8 | - eval "$(curl -sL https://swiftenv.fuller.li/install.sh)" 9 | script: 10 | - swift test 11 | notifications: 12 | email: false 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 pixyzehn 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. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | INSTALL_PATH = /usr/local/bin/PackageBuilder 2 | 3 | install: 4 | swift package update 5 | swift build -c release 6 | cp -f .build/release/PackageBuilder $(INSTALL_PATH) 7 | 8 | uninstall: 9 | rm -f $(INSTALL_PATH) 10 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "Files", 6 | "repositoryURL": "https://github.com/JohnSundell/Files.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "6568bfe636f02dfd85dc9d51b3782555b83080d3", 10 | "version": "4.0.2" 11 | } 12 | }, 13 | { 14 | "package": "ShellOut", 15 | "repositoryURL": "https://github.com/JohnSundell/ShellOut.git", 16 | "state": { 17 | "branch": "master", 18 | "revision": "d3d54ce662dfee7fef619330b71d251b8d4869f9", 19 | "version": null 20 | } 21 | } 22 | ] 23 | }, 24 | "version": 1 25 | } 26 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "PackageBuilder", 7 | dependencies: [ 8 | .package(url: "https://github.com/JohnSundell/Files.git", from: "4.0.2"), 9 | .package(url: "https://github.com/JohnSundell/ShellOut.git", .branch("master")) 10 | ], 11 | targets: [ 12 | .target( 13 | name: "PackageBuilder", 14 | dependencies: ["PackageBuilderCore"]), 15 | .target( 16 | name: "PackageBuilderCore", 17 | dependencies: ["Files", "ShellOut"]), 18 | .testTarget( 19 | name: "PackageBuilderTests", 20 | dependencies: ["PackageBuilderCore"]) 21 | ] 22 | ) 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PackageBuilder 2 | [![SPM](https://img.shields.io/badge/spm-compatible-brightgreen.svg?style=for-the-badge)](https://github.com/apple/swift-package-manager) 3 | [![Build Status](https://img.shields.io/travis/com/pixyzehn/PackageBuilder/master?style=for-the-badge)](https://travis-ci.com/pixyzehn/PackageBuilder) 4 | 5 | PackageBuilder builds a simple command-line structure by SwiftPM, inspired by [JohnSundell/SwiftPlate](https://github.com/JohnSundell/SwiftPlate). PackageBuilder is originally created by using PackageBuilder. 6 | See also [Building a command line tool using the Swift Package Manager](https://www.swiftbysundell.com/posts/building-a-command-line-tool-using-the-swift-package-manager). 7 | 8 | ```console 9 | $ packagebuilder 10 | 11 | PackageBuilder 12 | -------------- 13 | PackageBuilder builds a simple command-line structure by SwiftPM. 14 | . 15 | ├── Package.swift 16 | ├── README.md 17 | ├── Makefile 18 | ├── {PACKAGE_NAME}.xcodeproj 19 | ├── Sources 20 | │   ├── {PACKAGE_NAME} 21 | │ │ └── main.swift 22 | │   └── {PACKAGE_NAME}Core 23 | │   └── {PACKAGE_NAME}.swift 24 | └── Tests 25 |    ├── {PACKAGE_NAME}Tests 26 | │ ├── {PACKAGE_NAME}Tests.swift 27 | │ └── XCTestManifests.swift 28 |    └── LinuxMain.swift 29 | -------------- 30 | Examples: 31 | - packagebuilder {PACKAGE_NAME} 32 | - packagebuilder {PACKAGE_NAME} --path ~/Developer 33 | ``` 34 | 35 | ## Requirements 36 | 37 | - Git 38 | 39 | ## Installation 40 | 41 | On macOS 42 | 43 | ### Makefile 44 | 45 | ```console 46 | $ git clone git@github.com:pixyzehn/PackageBuilder.git && cd PackageBuilder 47 | $ make 48 | ``` 49 | 50 | ### SwiftPM 51 | 52 | ```console 53 | $ git clone git@github.com:pixyzehn/PackageBuilder.git && cd PackageBuilder 54 | $ swift build -c release 55 | $ cp -f .build/release/PackageBuilder /usr/local/bin/PackageBuilder 56 | ``` 57 | 58 | ### [Mint](https://github.com/yonaskolb/mint) 59 | ```console 60 | $ mint run pixyzehn/PackageBuilder 61 | ``` 62 | 63 | On Linux 64 | 65 | ```console 66 | $ git clone git@github.com:pixyzehn/PackageBuilder.git && cd PackageBuilder 67 | $ swift build -c release 68 | $ cp -f .build/release/PackageBuilder /usr/local/bin/PackageBuilder 69 | ``` 70 | 71 | ## Usage 72 | 73 | ```console 74 | $ packagebuilder {PACKAGE_NAME} 75 | $ packagebuilder {PACKAGE_NAME} --path ~/Developer/project 76 | ``` 77 | 78 | ## Contributing 79 | 80 | 1. Fork it ( https://github.com/pixyzehn/PackageBuilder ) 81 | 2. Create your feature branch (`git checkout -b new-feature`) 82 | 3. Commit your changes (`git commit -am 'Add some feature'`) 83 | 4. Push to the branch (`git push origin new-feature`) 84 | 5. Create a new Pull Request 85 | 86 | ## License 87 | [MIT License](https://github.com/pixyzehn/PackageBuilder/blob/master/LICENSE) 88 | -------------------------------------------------------------------------------- /Sources/PackageBuilder/main.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * PackageBuilder 3 | * Copyright (c) Hiroki Nagasawa 2017 4 | * Licensed under the MIT license. See LICENSE file. 5 | */ 6 | 7 | import PackageBuilderCore 8 | 9 | do { 10 | try PackageBuilder().run() 11 | } catch { 12 | print("An error occurred: \(error)") 13 | } 14 | -------------------------------------------------------------------------------- /Sources/PackageBuilderCore/PackageBuilder.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * PackageBuilder 3 | * Copyright (c) Hiroki Nagasawa 2017 4 | * Licensed under the MIT license. See LICENSE file. 5 | */ 6 | 7 | import Foundation 8 | import Files 9 | import ShellOut 10 | 11 | public final class PackageBuilder { 12 | private let arguments: [String] 13 | 14 | public init(arguments: [String] = CommandLine.arguments) { 15 | self.arguments = arguments 16 | } 17 | 18 | // swiftlint:disable function_body_length 19 | public func run() throws { 20 | guard arguments.count > 1 else { 21 | printDescription() 22 | return 23 | } 24 | 25 | let packageName = arguments[1] 26 | var folder = try Folder.current.createSubfolder(named: packageName) 27 | 28 | var expectingPath = false 29 | for argument in arguments[2.. [XCTestCaseEntry] { 5 | return [ 6 | testCase({PACKAGE_NAME}Tests.allTests), 7 | ] 8 | } 9 | #endif 10 | -------------------------------------------------------------------------------- /Templates/main.swift: -------------------------------------------------------------------------------- 1 | import {PACKAGE_NAME}Core 2 | 3 | do { 4 | try {PACKAGE_NAME}().run() 5 | } catch { 6 | print("An error occurred: \(error)") 7 | } 8 | -------------------------------------------------------------------------------- /Templates/{PACKAGE_NAME}.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public final class {PACKAGE_NAME} { 4 | private let arguments: [String] 5 | 6 | public init(arguments: [String] = CommandLine.arguments) { 7 | self.arguments = arguments 8 | } 9 | 10 | public func run() throws { 11 | print("Hello, world!") 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Templates/{PACKAGE_NAME}Tests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import class Foundation.Bundle 3 | import {PACKAGE_NAME}Core 4 | 5 | final class {PACKAGE_NAME}Tests: XCTestCase { 6 | func testExample() throws { 7 | // This is an example of a functional test case. 8 | // Use XCTAssert and related functions to verify your tests produce the correct 9 | // results. 10 | 11 | // Some of the APIs that we use below are available in macOS 10.13 and above. 12 | guard #available(macOS 10.13, *) else { 13 | return 14 | } 15 | 16 | let fooBinary = productsDirectory.appendingPathComponent("{PACKAGE_NAME}") 17 | 18 | let process = Process() 19 | process.executableURL = fooBinary 20 | 21 | let pipe = Pipe() 22 | process.standardOutput = pipe 23 | 24 | try process.run() 25 | process.waitUntilExit() 26 | 27 | let data = pipe.fileHandleForReading.readDataToEndOfFile() 28 | let output = String(data: data, encoding: .utf8) 29 | 30 | XCTAssertEqual(output, "Hello, world!\n") 31 | } 32 | 33 | /// Returns path to the built products directory. 34 | var productsDirectory: URL { 35 | #if os(macOS) 36 | for bundle in Bundle.allBundles where bundle.bundlePath.hasSuffix(".xctest") { 37 | return bundle.bundleURL.deletingLastPathComponent() 38 | } 39 | fatalError("couldn't find the products directory") 40 | #else 41 | return Bundle.main.bundleURL 42 | #endif 43 | } 44 | 45 | static var allTests = [ 46 | ("testExample", testExample), 47 | ] 48 | } 49 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * PackageBuilder 3 | * Copyright (c) Hiroki Nagasawa 2017 4 | * Licensed under the MIT license. See LICENSE file. 5 | */ 6 | 7 | import XCTest 8 | @testable import PackageBuilderTests 9 | 10 | XCTMain([ 11 | testCase(PackageBuilderTests.allTests) 12 | ]) 13 | -------------------------------------------------------------------------------- /Tests/PackageBuilderTests/PackageBuilderTests.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * PackageBuilder 3 | * Copyright (c) Hiroki Nagasawa 2017 4 | * Licensed under the MIT license. See LICENSE file. 5 | */ 6 | 7 | import XCTest 8 | import PackageBuilderCore 9 | import Files 10 | import ShellOut 11 | 12 | class PackageBuilderTests: XCTestCase { 13 | private var folder: Folder! 14 | private let packageName = "SamplePackage" 15 | private let packageBuilderTests = ".PackageBuilderTests" 16 | 17 | override func setUp() { 18 | super.setUp() 19 | // Reset the test folder just in case. 20 | try? Folder.home.subfolder(named: packageBuilderTests).delete() 21 | 22 | folder = try? Folder.home.createSubfolder(named: packageBuilderTests) 23 | 24 | // Delete the {PACKAGE_NAME} directory if needed. 25 | try? folder.subfolder(named: "\(packageName)").delete() 26 | } 27 | 28 | override func tearDown() { 29 | try? folder.delete() 30 | super.tearDown() 31 | } 32 | 33 | func testCreatingPackage() throws { 34 | try PackageBuilder(arguments: ["packagebuilder", packageName, "--path", "~/\(packageBuilderTests)"]).run() 35 | 36 | let projectFolder = try folder.subfolder(named: "\(packageName)") 37 | 38 | // Ensure it creates needed files and folders under the {PACKAGE_NAME}/. 39 | XCTAssertTrue(projectFolder.containsFile(named: "Package.swift")) 40 | XCTAssertTrue(projectFolder.containsFile(named: "README.md")) 41 | XCTAssertTrue(projectFolder.containsFile(named: "Makefile")) 42 | XCTAssertTrue(projectFolder.containsSubfolder(named: "\(packageName).xcodeproj")) 43 | XCTAssertTrue(projectFolder.containsSubfolder(named: "Sources")) 44 | XCTAssertTrue(projectFolder.containsSubfolder(named: "Tests")) 45 | 46 | // Ensure all `{}` are replaced for sure under the {PACKAGE_NAME}/. 47 | for file in projectFolder.files { 48 | try checkIfAllTempNamesReplaced(file: file) 49 | } 50 | 51 | let sourcesFolder = try projectFolder.subfolder(named: "Sources") 52 | 53 | let projectNameFolder = try sourcesFolder.subfolder(named: "\(packageName)") 54 | 55 | // Ensure it creates a needed file under the {PACKAGE_NAME}/Sources/{PACKAGE_NAME}/. 56 | XCTAssertTrue(projectNameFolder.containsFile(named: "main.swift")) 57 | 58 | // Ensure all `{}` are replaced for sure under the {PACKAGE_NAME}/Sources/{PACKAGE_NAME}/. 59 | if let file = projectNameFolder.files.first { 60 | try checkIfAllTempNamesReplaced(file: file) 61 | } 62 | 63 | let projectNameCoreFolder = try sourcesFolder.subfolder(named: "\(packageName)Core") 64 | 65 | // Ensure it creates a needed file under the {PACKAGE_NAME}/Sources/{PACKAGE_NAME}Core/. 66 | XCTAssertTrue(projectNameCoreFolder.containsFile(named: "\(packageName).swift")) 67 | 68 | // Ensure all `{}` are replaced for sure under the {PACKAGE_NAME}/Sources/{PACKAGE_NAME}Core/. 69 | if let file = projectNameCoreFolder.files.first { 70 | try checkIfAllTempNamesReplaced(file: file) 71 | } 72 | 73 | let testsFolder = try projectFolder.subfolder(named: "Tests") 74 | 75 | // Ensure it creates a needed file under the {PACKAGE_NAME}/Tests/. 76 | XCTAssertTrue(testsFolder.containsFile(named: "LinuxMain.swift")) 77 | 78 | // Ensure all `{}` are replaced for sure under the {PACKAGE_NAME}/Tests/. 79 | if let file = testsFolder.files.first { 80 | try checkIfAllTempNamesReplaced(file: file) 81 | } 82 | 83 | let projectTestsFolder = try testsFolder.subfolder(named: "\(packageName)Tests") 84 | 85 | // Ensure it creates a needed file under the {PACKAGE_NAME}/Tests/{PACKAGE_NAME}Tests/. 86 | XCTAssertTrue(projectTestsFolder.containsFile(named: "\(packageName)Tests.swift")) 87 | XCTAssertTrue(projectTestsFolder.containsFile(named: "XCTestManifests.swift")) 88 | 89 | // Ensure all `{}` are replaced for sure under the {PACKAGE_NAME}/Tests/{PACKAGE_NAME}Tests/. 90 | for file in projectTestsFolder.files { 91 | try checkIfAllTempNamesReplaced(file: file) 92 | } 93 | } 94 | 95 | // MARK: - Utilities 96 | 97 | private func checkIfAllTempNamesReplaced(file: File) throws { 98 | let contents = try file.readAsString(encodedAs: .utf8) 99 | XCTAssertFalse(contents.contains("{PACKAGE_NAME}")) 100 | } 101 | } 102 | 103 | extension PackageBuilderTests { 104 | static var allTests: [(String, (PackageBuilderTests) -> () throws -> Void)] { 105 | return [ 106 | ("testCreatingPackage", testCreatingPackage) 107 | ] 108 | } 109 | } 110 | --------------------------------------------------------------------------------