├── .editorconfig ├── .gitignore ├── .swift-version ├── .swiftlint.yml ├── .travis.yml ├── CHANGELOG.md ├── Dangerfile ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── Package.swift ├── README.md ├── Rockfile ├── Scripts └── Install.generated.swift ├── Sources ├── RockLib │ ├── Error.swift │ ├── Generated │ │ └── Version.generated.swift │ ├── IO │ │ ├── Reporter.swift │ │ └── Theme.swift │ ├── Model │ │ ├── RockSpec.swift │ │ └── RocketSpec.swift │ ├── RockConfig.swift │ ├── RockProject.swift │ ├── Rockfile.swift │ ├── Runner │ │ ├── Repository.swift │ │ ├── RockSpecRunner.swift │ │ ├── RocketBinRunner.swift │ │ └── RocketSrcRunner.swift │ ├── ScriptReporter.swift │ └── YamlAndStencil.swift └── rock │ ├── Arguments.swift │ ├── Commandant.swift │ ├── Either.swift │ ├── InitCommand.swift │ ├── InstallCommand.swift │ ├── ProjectDependencyOptions.swift │ ├── Reporter.swift │ ├── RunCommand.swift │ ├── ScriptCommand.swift │ ├── UninstallCommand.swift │ ├── VersionCommand.swift │ ├── curry.swift │ └── main.swift ├── Templates ├── Install │ └── Install.swift.stencil └── RockLib │ └── Version.swift.stencil └── Tests ├── LinuxMain.swift └── RockTests └── RockTests.swift /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | .rock 6 | .vscode 7 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 3.0.2 2 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | included: # paths to include during linting. `--path` is ignored if present. 2 | - Sources 3 | variable_name: 4 | excluded: 5 | - m 6 | line_length: 120 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | 2 | language: generic 3 | sudo: required 4 | 5 | install: 6 | - eval "$(curl -sL https://gist.githubusercontent.com/kylef/5c0475ff02b7c7671d2a/raw/9f442512a46d7a2af7b850d65a7e9bd31edfb09b/swiftenv-install.sh)" 7 | - bundle install 8 | script: 9 | - set -o pipefail 10 | - swift build 11 | - ./.build/debug/rock install 12 | - ./.build/debug/rock test 13 | - ./.build/debug/rock run lint 14 | - bundle exec danger 15 | cache: 16 | - bundler 17 | - directories: 18 | - .rock 19 | - Packages 20 | - $HOME/.swiftenv 21 | - $HOME/.rock 22 | notifications: 23 | email: false 24 | 25 | matrix: 26 | include: 27 | - os: osx 28 | osx_image: xcode8 29 | - os: linux 30 | dist: trusty 31 | sudo: required 32 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Rock 2 | 3 | ## 0.3.1 4 | 5 | ### Non Breaking Changes 6 | 7 | - **[Dependencies]** Upgraded `PathKit`, `Stencil` and `PromptLine` which fixes Linux issues 8 | 9 | ## 0.3.0 10 | 11 | ### Additions 12 | 13 | - **[Rock]** Added pre- and post-hooks for all scripts 14 | - **[Rock]** Added `rock lint` as an alias 15 | - **[Rock]** Added `rock publish` as an alias 16 | - **[Rock]** Added `rock init` generates improved data 17 | - **[Rock]** Added `rock init` now tells you that it has succeeded 18 | - **[Rockfile]** Reintroduces `clean` phase 19 | 20 | ### Non Breaking Changes 21 | 22 | - **[Rockfile]** Rockfiles now interpret `"const", "constant"` keys first 23 | - **[Rock]** Fixes second name line on `rock init` 24 | - **[Rock]** Reduces output noise of rock itself 25 | - **[Rock]** Clones are not recursive anymore 26 | 27 | ## 0.2.3 28 | 29 | ### Non Breaking Changes 30 | 31 | - **[Rock]** Improved `rock init` 32 | 33 | ## 0.2.2 34 | 35 | ### Additions 36 | 37 | - **[Rockfile]** Now supports more complex bash commands including && 38 | 39 | ### Non Breaking Changes 40 | 41 | - **[Dependencies]** Updated PromptLine for shell execution 42 | 43 | ## 0.2.1 44 | 45 | ### Additions 46 | 47 | - **[Rock]** Now supports `rock version` - @vknabel 48 | - **[Rock]** Now supports `rock run script` - @vknabel 49 | - **[Rock]** Added convenience scripts for `rock build`, `rock test` and `rock archive` - @vknabel 50 | - **[Rock]** Derived scripts that will be executed will be highlighted now - @vknabel 51 | - **[Rockfile]** Every value in `Rockfile` will now be interpreted as Stencil template - @vknabel 52 | - **[Rockfile]** Overriding single properties in `Rockfile` is now supported - @vknabel 53 | - **[Other]** Provides experimental `Install.generated.swift` Installation Script - @vknabel 54 | 55 | ### Non Breaking Changes 56 | 57 | - **[Project]** Now uses krzysztofzablocki/Sourcery - @vknabel 58 | - **[Project]** Added Swiftlint - @vknabel 59 | - **[Project]** Added Travis CI - @vknabel 60 | - **[Project]** Added Danger - @vknabel 61 | - **[Dependencies]** Uses ColorizeSwift for terminal colors instead of Swiftline - @vknabel 62 | 63 | ## 0.2.0 64 | 65 | ### Breaking Changes 66 | 67 | - **[Rock]** Dropped RockSet support in `~/.rock` - @vknabel 68 | - **[Rock]** Dropped `rock update` - @vknabel 69 | - **[Rock]** Dropped `rock self-update` - @vknabel 70 | - **[Rock]** Dropped `rock reinstall` - @vknabel 71 | - **[Rock]** Dropped `rock clean` - @vknabel 72 | - **[Rock]** Dropped `rock list` - @vknabel 73 | 74 | ### Additions 75 | 76 | - **[Rock]** Adds support for `Rockfile` for projects - @vknabel 77 | - **[RockLib]** Created Module `RockLib` - @vknabel 78 | - **[RockLib]** Added support for `Rockfile` - @vknabel 79 | 80 | ### Non Breaking Changes 81 | 82 | - **[Dependencies]** Uses `.git` for all direct dependencies - @vknabel 83 | - **[Dependencies]** Replaced Commander with Commandant - @vknabel 84 | - **[Dependencies]** Result instead of plain do-try-catch - @vknabel 85 | - **[Dependencies]** Uses PromptLine for shell execution instead of Swiftline - @vknabel 86 | - **[RockSpecs]** Deprecated `install`. Use `build` and `link` instead - @vknabel 87 | - **[RockSpecs]** Deprecated `uninstall`. Use `unlink` instead - @vknabel 88 | 89 | ### Migration instructions 90 | 91 | - Run `rock install rock@0.2.0` 92 | - In your `.bashrc`, `.bash_profile` or `.zshrc` replace `export PATH="$PATH:$ROCK_PATH/rocksets/global/bin"` with `export PATH="$PATH:$ROCK_PATH/bin"`. 93 | - Delete `$ROCK_PATH/rockspecs` 94 | 95 | ## 0.1.0 96 | 97 | - Initial Release - @vknabel 98 | -------------------------------------------------------------------------------- /Dangerfile: -------------------------------------------------------------------------------- 1 | # Sometimes it's a README fix, or something like that - which isn't relevant for 2 | # including in a project's CHANGELOG for example 3 | not_declared_trivial = !github.pr_title.include?("#trivial") 4 | has_app_changes = !git.modified_files.grep(/Sources/).empty? 5 | 6 | # Make it more obvious that a PR is a work in progress and shouldn't be merged yet 7 | warn("PR is classed as Work in Progress") if github.pr_title.include? "[WIP]" 8 | 9 | # Warn when there is a big PR 10 | warn("Big PR") if git.lines_of_code > 500 11 | 12 | # Don't let testing shortcuts get into master by accident 13 | fail("fdescribe left in tests") if `grep -r fdescribe Tests/ `.length > 1 14 | fail("fit left in tests") if `grep -r fit Tests/ `.length > 1 15 | 16 | # Changelog entries are required for changes to library files. 17 | no_changelog_entry = !git.modified_files.include?("CHANGELOG.md") 18 | if has_app_changes && no_changelog_entry && not_declared_trivial 19 | fail("Any changes to library code need a summary in the Changelog.") 20 | end 21 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'cocoapods', '1.2.0.beta.1' 4 | gem 'danger' 5 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | CFPropertyList (2.3.4) 5 | activesupport (4.2.7.1) 6 | i18n (~> 0.7) 7 | json (~> 1.7, >= 1.7.7) 8 | minitest (~> 5.1) 9 | thread_safe (~> 0.3, >= 0.3.4) 10 | tzinfo (~> 1.1) 11 | addressable (2.5.0) 12 | public_suffix (~> 2.0, >= 2.0.2) 13 | claide (1.0.1) 14 | claide-plugins (0.9.2) 15 | cork 16 | nap 17 | open4 (~> 1.3) 18 | cocoapods (1.2.0.beta.1) 19 | activesupport (>= 4.0.2, < 5) 20 | claide (>= 1.0.1, < 2.0) 21 | cocoapods-core (= 1.2.0.beta.1) 22 | cocoapods-deintegrate (>= 1.0.1, < 2.0) 23 | cocoapods-downloader (>= 1.1.2, < 2.0) 24 | cocoapods-plugins (>= 1.0.0, < 2.0) 25 | cocoapods-search (>= 1.0.0, < 2.0) 26 | cocoapods-stats (>= 1.0.0, < 2.0) 27 | cocoapods-trunk (>= 1.1.1, < 2.0) 28 | cocoapods-try (>= 1.1.0, < 2.0) 29 | colored (~> 1.2) 30 | escape (~> 0.0.4) 31 | fourflusher (~> 2.0.1) 32 | gh_inspector (~> 1.0) 33 | molinillo (~> 0.5.3) 34 | nap (~> 1.0) 35 | ruby-macho (~> 0.2.5) 36 | xcodeproj (>= 1.4.0, < 2.0) 37 | cocoapods-core (1.2.0.beta.1) 38 | activesupport (>= 4.0.2, < 5) 39 | fuzzy_match (~> 2.0.4) 40 | nap (~> 1.0) 41 | cocoapods-deintegrate (1.0.1) 42 | cocoapods-downloader (1.1.3) 43 | cocoapods-plugins (1.0.0) 44 | nap 45 | cocoapods-search (1.0.0) 46 | cocoapods-stats (1.0.0) 47 | cocoapods-trunk (1.1.2) 48 | nap (>= 0.8, < 2.0) 49 | netrc (= 0.7.8) 50 | cocoapods-try (1.1.0) 51 | colored (1.2) 52 | cork (0.2.0) 53 | colored (~> 1.2) 54 | danger (4.0.4) 55 | claide (~> 1.0) 56 | claide-plugins (>= 0.9.2) 57 | colored (~> 1.2) 58 | cork (~> 0.1) 59 | faraday (~> 0.9) 60 | faraday-http-cache (~> 1.0) 61 | git (~> 1) 62 | kramdown (~> 1.5) 63 | octokit (~> 4.2) 64 | terminal-table (~> 1) 65 | escape (0.0.4) 66 | faraday (0.10.1) 67 | multipart-post (>= 1.2, < 3) 68 | faraday-http-cache (1.3.1) 69 | faraday (~> 0.8) 70 | fourflusher (2.0.1) 71 | fuzzy_match (2.0.4) 72 | gh_inspector (1.0.2) 73 | git (1.3.0) 74 | i18n (0.7.0) 75 | json (1.8.3) 76 | kramdown (1.13.2) 77 | minitest (5.10.1) 78 | molinillo (0.5.5) 79 | multipart-post (2.0.0) 80 | nanaimo (0.2.3) 81 | nap (1.1.0) 82 | netrc (0.7.8) 83 | octokit (4.6.2) 84 | sawyer (~> 0.8.0, >= 0.5.3) 85 | open4 (1.3.4) 86 | public_suffix (2.0.5) 87 | ruby-macho (0.2.6) 88 | sawyer (0.8.1) 89 | addressable (>= 2.3.5, < 2.6) 90 | faraday (~> 0.8, < 1.0) 91 | terminal-table (1.7.3) 92 | unicode-display_width (~> 1.1.1) 93 | thread_safe (0.3.5) 94 | tzinfo (1.2.2) 95 | thread_safe (~> 0.1) 96 | unicode-display_width (1.1.2) 97 | xcodeproj (1.4.2) 98 | CFPropertyList (~> 2.3.3) 99 | activesupport (>= 3) 100 | claide (>= 1.0.1, < 2.0) 101 | colored (~> 1.2) 102 | nanaimo (~> 0.2.3) 103 | 104 | PLATFORMS 105 | ruby 106 | 107 | DEPENDENCIES 108 | cocoapods (= 1.2.0.beta.1) 109 | danger 110 | 111 | BUNDLED WITH 112 | 1.13.6 113 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Valentin Knabel 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 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | import PackageDescription 2 | 3 | let package = Package( 4 | name: "Rock", 5 | targets: [ 6 | Target(name: "rock", dependencies: ["RockLib"]), 7 | Target(name: "RockLib", dependencies: []), 8 | ], 9 | dependencies: [ 10 | .Package(url: "https://github.com/kylef/PathKit.git", Version(0, 8, 0)), 11 | .Package(url: "https://github.com/kylef/Stencil.git", Version(0, 8, 0)), 12 | .Package(url: "https://github.com/Carthage/Commandant.git", majorVersion: 0, minor: 11), 13 | .Package(url: "https://github.com/mtynior/ColorizeSwift.git", majorVersion: 1, minor: 1), 14 | .Package(url: "https://github.com/behrang/YamlSwift.git", majorVersion: 3, minor: 3), 15 | .Package(url: "https://github.com/antitypical/Result.git", majorVersion: 3), 16 | .Package(url: "https://github.com/vknabel/Lens.git", majorVersion: 0, minor: 1), 17 | .Package(url: "https://github.com/vknabel/PromptLine.git", Version(0, 6, 1)), 18 | ] 19 | ) 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rock 2 | 3 | With Rock you can easily manage your Project, metadata and Swift CLI dependencies. Additionally you can install Swift CLIs globally. 4 | The index of all supported libraries can be found on the [RockSpecs](https://github.com/vknabel/RockSpecs) repository, 5 | but you can declare your own at your `Rockfile`. 6 | 7 | ## Overview 8 | 9 | Dependencies are called Rockets (aka 🚀). If they can be build with the Swift Package Manager, they should already be compatible out of the box. 10 | Each 🚀 has a [RocketSpec](https://github.com/vknabel/RockSpecs/blob/master/default.yaml) which defines the Git url and how it has to be installed. 11 | 12 | ### Project 13 | 14 | You can create a project by simply running: 15 | 16 | ```bash 17 | $ rock init 18 | ``` 19 | 20 | This will create you an empty `Rockfile` which declares all your dependencies and metadata. 21 | 22 | ```yaml 23 | name: YourProject 24 | dependencies: 25 | - empty # Just an empty dependency that installs fast 26 | # Insert your dependencies here 27 | # - sourcery@0.5.2 28 | # - name: owncli 29 | # url: https://github.com/your/owncli 30 | ``` 31 | 32 | Additionally you may create any additional tags. 33 | Each value will be interpreted as a Stencil template, 34 | that will be executed in a context of all previously declared values. 35 | The tags `constants`, `version`, `license`, `name` and `url` will have precedence. 36 | 37 | After you have set up your metadata and dependencies, you can install those by running: 38 | 39 | ```bash 40 | $ rock install 41 | ``` 42 | 43 | Now you can work with all your CLIs. If you want to create your own custom scripts, you can add the script tag: 44 | 45 | ```yaml 46 | name: YourProject 47 | dependencies: [] # your dependencies 48 | author: 49 | name: Valentin Knabel 50 | email: dev@vknabel.com 51 | scripts: 52 | hello: echo Hello {{ author.name }} 53 | xcodeproj: 54 | - swift package generate-xcodeproj 55 | - open {{ name }}.xcodeproj 56 | ``` 57 | 58 | Thereafter you are able to run all your scripts with ease: 59 | 60 | ```bash 61 | $ rock run hello 62 | Hello Valentin Knabel 63 | ``` 64 | 65 | Additionally there are convenience commands with default scripts made for the Swift Package Manager: 66 | 67 | ```bash 68 | $ rock build 69 | 🏃 swift build 70 | $ rock test 71 | 🏃 swift test 72 | $ rock archive 73 | 🏃 swift build -c release 74 | ``` 75 | 76 | ### Global 77 | 78 | Installing dependencies globally is currently only supported for Rockets that can be found in the [RockSpecs](https://github.com/vknabel/RockSpecs) repository. 79 | You may install them by simply running: 80 | 81 | ```bash 82 | # With a fixed version/tag/branch 83 | $ rock install sourcery@0.5.2 swiftlint@0.16.0 84 | # Or using the default (e.g. master) 85 | $ rock install swiftgen 86 | ``` 87 | 88 | If you want to uninstall specific Rockets, just run: 89 | 90 | ```bash 91 | $ rock uninstall sourcery swiftlint swiftgen 92 | ``` 93 | 94 | ## Installation 95 | 96 | First add the rock-bin to your `$PATH` variable to your `.bashrc`, `.bash_profile` or `.zshrc`. 97 | 98 | ```bash 99 | export ROCK_PATH="$HOME/.rock" # default 100 | export PATH="$PATH:./.rock/bin:$ROCK_PATH/bin" 101 | ``` 102 | 103 | Thereafter start 🎸ing your 🚀s by simply cloning the repository, building the swift module and installing rock itself. 104 | 105 | ```bash 106 | $ git clone https://github.com/vknabel/rock $ROCK_PATH/sources/rock 107 | $ cd $ROCK_PATH/sources/rock 108 | $ swift build 109 | $ mkdir $ROCK_PATH/bin 110 | $ cp $ROCK_PATH/sources/rock/.build/debug/rock $ROCK_PATH/bin 111 | ``` 112 | 113 | Alternatively you may try out our Swift Installer (you still need to set up your `$PATH` and `ROCK_PATH`): 114 | 115 | ```bash 116 | $ curl -sL https://raw.githubusercontent.com/vknabel/Rock/master/Scripts/Install.generated.swift | swift - 117 | ``` 118 | 119 | ### Updates 120 | 121 | Rock can be updated by simply running installing itself with a version specified. 122 | ```bash 123 | $ rock install rock@0.3.1 124 | ``` 125 | 126 | ## Limitations 127 | 128 | - Rock downloads and compiles all of your dependencies isolated and therefore installations may take a while. 129 | - Currently there is no version handling. Instead only the `master` branch will be checked out by default. 130 | 131 | ## Author 132 | 133 | Valentin Knabel, [@vknabel](https://twitter.com/vknabel), dev@vknabel.com 134 | 135 | ## License 136 | 137 | Rock is available under the [MIT](LICENSE) license. 138 | -------------------------------------------------------------------------------- /Rockfile: -------------------------------------------------------------------------------- 1 | name: Rock 2 | url: https://github.com/vknabel/Rock 3 | version: 0.3.1 4 | # Dependencies may be installed locally by running `rock install` 5 | # If you like you can even delcare Specs inline or override specific scripts. 6 | dependencies: 7 | - swiftlint@0.15.0 8 | - sourcery@0.5.2 9 | 10 | # Scripts can be run as `rock run hello` 11 | # If you are using a SwiftPM Project, the scripts build, test, install and clean will be inferred. 12 | # Like everywhere in this Rockfile, you can use Stencil syntax inside your script values. 13 | # Before each script is executed, it's pre variant will be executed. 14 | # Once a script finshes, it's post variant is called. 15 | # Searching for script ideas? Why not try write docs or publish scripts? 16 | scripts: 17 | # build, test, install and clean are inferred 18 | xcode: 19 | # Generate Project. Optionally: customize 20 | - swift package generate-xcodeproj --enable-code-coverage 21 | postxcode: # Will automatically run after xcode 22 | - open {{name}}.xcodeproj 23 | generate: 24 | # Injects version into `RockLib` and `rock` 25 | - sourcery Sources/RockLib/Generating Templates/RockLib Sources/RockLib/Generated/Version.generated.swift --args version={{version}} 26 | # Sets the version for the Swift Installer 27 | - sourcery Sources/RockLib/Generating Templates/Install Scripts/Install.generated.swift --args version={{version}} 28 | lint: 29 | - swiftlint autocorrect 30 | - swiftlint 31 | prepublish: 32 | - rock run generate 33 | # Run your linter before publishing 34 | - rock lint 35 | # Releases may only be made on branch master 36 | - | 37 | if [ $(git branch | grep \* | cut -d ' ' -f2) != "master" ]; then 38 | echo Releases must be created from branch master >&2 39 | exit 1 40 | fi 41 | publish: # Will automatically run prepublish 42 | # Assert clean working directory 43 | - git diff --exit-code > /dev/null 44 | # Set git tag 45 | - git tag -a {{version}} -m {{version}} 46 | - git push --tags 47 | postpublish: 48 | # Copy latest Changelog 49 | - sed -n /'^## {{version}}$'/,/'^## '/p CHANGELOG.md | sed -e '$ d' | pbcopy 50 | # Create new release on Github 51 | - open {{url}}/releases/new?tag={{version}} 52 | # Publish to CocoaPods 53 | - | 54 | if [ -a {{name}}.podspec ]; then 55 | bundle exec pod trunk push {{name}}.podspec --allow-warnings --swift-version=3.0 56 | fi 57 | -------------------------------------------------------------------------------- /Scripts/Install.generated.swift: -------------------------------------------------------------------------------- 1 | // Generated using Sourcery 0.5.3 — https://github.com/krzysztofzablocki/Sourcery 2 | // DO NOT EDIT 3 | 4 | 5 | import Foundation 6 | 7 | #if os(Linux) 8 | typealias Process = Task 9 | #endif 10 | 11 | let rockPath = ProcessInfo.processInfo.environment["ROCK_PATH"] ?? "~/.rock" 12 | let version = ProcessInfo.processInfo.environment["ROCK_VERSION"] ?? "0.3.1" 13 | 14 | func run(workingDir: String? = nil, _ args: String...) { 15 | let process = Process() 16 | process.launchPath = "/usr/bin/env" 17 | process.currentDirectoryPath = workingDir ?? process.currentDirectoryPath 18 | process.arguments = args 19 | process.launch() 20 | process.waitUntilExit() 21 | } 22 | 23 | let wd = "\(rockPath)/sources/rock/" 24 | run( 25 | "git", "clone", 26 | "https://github.com/vknabel/Rock", 27 | wd, 28 | "--depth", "1", 29 | "--branch", version 30 | ) 31 | run(workingDir: wd, "bash", "-c", "cd \"\(wd)\" && swift build -c release") 32 | run("mkdir", "-p", "\(rockPath)/bin") 33 | run("cp", "\(rockPath)/sources/rock/.build/release/rock", "\(rockPath)/bin") 34 | 35 | print("\n\nDon't forget to add $PATH and $ROCK_PATH to your profile:") 36 | print(" export ROCK_PATH=\"$HOME/.rock\"") 37 | print(" export PATH=\"$PATH:./.rock/bin:$ROCK_PATH/bin\"") 38 | -------------------------------------------------------------------------------- /Sources/RockLib/Error.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import PromptLine 3 | import PathKit 4 | 5 | public enum RockError: Error { 6 | case notImplemented(String) 7 | 8 | case rockfileCustomScriptFailed(String, PromptError) 9 | case rockfileCustomScriptNotFound(String) 10 | 11 | case rockfileAlreadyExists 12 | case rockfileCouldNotBeCreated(Error) 13 | case rockfileMustContainDictionary 14 | case rockfileMustHaveAName 15 | case rockfileMustHaveDependencies 16 | case rockfileHasInvalidDependency 17 | case rockfileIsNotValidYaml(String?) 18 | case rockfileCouldNotBeRead(Path, Error) 19 | 20 | case couldNotCreateSourcesDirectory(PromptError) 21 | 22 | case specsJsonHasInvalidFormat 23 | case specsJsonHasNoName 24 | case specsJsonHasNoUrl(name: String) 25 | 26 | case rocketsCouldNotBeUninstalled(Error) 27 | 28 | case allSpecsCouldNotBeDetermined 29 | 30 | case rocketBinaryCouldNotBeUnlinked(RocketSpec) 31 | 32 | case rocketSourceCouldNotBeCloned(RocketSpec, PromptError) 33 | case rocketSourceCouldNotBeUpdated(RocketSpec, PromptError) 34 | case rocketSourceCouldNotBeFetched(RocketSpec, PromptError) 35 | 36 | case specsRepoNotFound(name: String) 37 | case specsRepoCouldNotBeUpdated(RockSpec, PromptError) 38 | case specsRepoCouldNotBeCloned(RockSpec, PromptError) 39 | 40 | case rocketSpecIsNotValidYaml(String?) 41 | case rocketSpecCouldNotBeRead(Path, Error) 42 | case rocketSpecRequiresAnUrl(name: String) 43 | case rocketSpecCouldNotBeFound(name: String) 44 | case rocketSpecCouldNotBeBuilt(PromptError) 45 | case rocketSpecCouldNotBeLinked(PromptError) 46 | case rocketSpecCouldNotBeUnlinked(PromptError) 47 | case rocketSpecCouldNotBeCleaned(PromptError) 48 | } 49 | -------------------------------------------------------------------------------- /Sources/RockLib/Generated/Version.generated.swift: -------------------------------------------------------------------------------- 1 | // Generated using Sourcery 0.5.3 — https://github.com/krzysztofzablocki/Sourcery 2 | // DO NOT EDIT 3 | 4 | 5 | public extension RockConfig { 6 | public static let version = "0.3.1" 7 | } 8 | -------------------------------------------------------------------------------- /Sources/RockLib/IO/Reporter.swift: -------------------------------------------------------------------------------- 1 | import PromptLine 2 | 3 | public typealias PromptReporter = PromptRunner 4 | -------------------------------------------------------------------------------- /Sources/RockLib/IO/Theme.swift: -------------------------------------------------------------------------------- 1 | import ColorizeSwift 2 | 3 | public extension CustomStringConvertible { 4 | public var theme: ThemedString { 5 | return ThemedString(string: self.description) 6 | } 7 | } 8 | 9 | public struct ThemedString { 10 | fileprivate var string: String 11 | 12 | public var coded: String { 13 | return string.magenta() 14 | } 15 | 16 | public var input: String { 17 | return string.blue() 18 | } 19 | 20 | public var derived: String { 21 | return string.cyan() 22 | } 23 | 24 | public var error: String { 25 | return string.red() 26 | } 27 | 28 | public var warn: String { 29 | return string.yellow() 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Sources/RockLib/Model/RockSpec.swift: -------------------------------------------------------------------------------- 1 | import PathKit 2 | 3 | public struct RockSpec { 4 | public var name: String 5 | public var url: String 6 | 7 | public init(name: String = "global", url: String = "https://github.com/vknabel/RockSpecs") { 8 | self.name = name 9 | self.url = url 10 | } 11 | 12 | public var path: Path { 13 | return RockConfig.rockConfig.rockSpecsPath + name 14 | } 15 | 16 | public var specsPath: Path { 17 | return path + "Specs" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/RockLib/Model/RocketSpec.swift: -------------------------------------------------------------------------------- 1 | import PromptLine 2 | import Result 3 | import PathKit 4 | import Yaml 5 | 6 | internal extension Yaml { 7 | var stringArray: [String]? { 8 | if let s = string { 9 | return [s] 10 | } else { 11 | return array?.flatMap({ $0.string }) 12 | } 13 | } 14 | } 15 | 16 | fileprivate func nilIfEmpty(_ array: [E]) -> [E]? { 17 | if array.isEmpty { 18 | return nil 19 | } else { 20 | return array 21 | } 22 | } 23 | 24 | internal func runnerFromStrings(_ raw: [String], or fallback: [String]) -> PromptRunner { 25 | return (nilIfEmpty(raw) ?? fallback).map({ report($0.theme.derived, format: .script) %& >-$0 }) 26 | .reduce(zeroRunner, { $0 %& $1 }) 27 | } 28 | 29 | public func runnerFromStrings(_ raw: [String]) -> PromptRunner? { 30 | return nilIfEmpty(raw)?.map({ report($0.theme.derived, format: .script) %& >-$0 }) 31 | .reduce(zeroRunner, { $0 %& $1 }) 32 | } 33 | 34 | public struct RocketSpec { 35 | public typealias Runner = PromptRunner 36 | public let name: String 37 | public let url: String 38 | public let buildRunner: Runner 39 | public let linkRunner: Runner 40 | public let unlinkRunner: Runner 41 | public let cleanRunner: Runner 42 | 43 | public init( 44 | name: String, 45 | url: String, 46 | build: @escaping PromptRunner, 47 | link: @escaping PromptRunner, 48 | unlink: @escaping PromptRunner, 49 | clean: @escaping PromptRunner 50 | ) { 51 | self.name = name 52 | self.url = url 53 | self.buildRunner = build %? RockError.rocketSpecCouldNotBeBuilt 54 | self.linkRunner = link %? RockError.rocketSpecCouldNotBeLinked 55 | self.unlinkRunner = unlink %? RockError.rocketSpecCouldNotBeUnlinked 56 | self.cleanRunner = clean %? RockError.rocketSpecCouldNotBeCleaned 57 | } 58 | 59 | public init( 60 | name: String, 61 | url: String, 62 | buildRunner: @escaping Runner, 63 | linkRunner: @escaping Runner, 64 | unlinkRunner: @escaping Runner, 65 | cleanRunner: @escaping Runner 66 | ) { 67 | self.name = name 68 | self.url = url 69 | self.buildRunner = buildRunner 70 | self.linkRunner = linkRunner 71 | self.unlinkRunner = unlinkRunner 72 | self.cleanRunner = cleanRunner 73 | } 74 | } 75 | 76 | public extension RocketSpec { 77 | public init( 78 | name: String, 79 | url: String, 80 | build: [String] = [], 81 | link: [String] = [], 82 | unlink: [String] = [], 83 | clean: [String] = [] 84 | ) { 85 | self.init( 86 | name: name, 87 | url: url, 88 | build: runnerFromStrings(build, or: RockConfig.rockConfig.archiveScript), 89 | link: runnerFromStrings(link, or: RockConfig.rockConfig.linkScript), 90 | unlink: runnerFromStrings(unlink, or: RockConfig.rockConfig.unlinkScript), 91 | clean: runnerFromStrings(clean, or: RockConfig.rockConfig.cleanScript) 92 | ) 93 | } 94 | } 95 | 96 | public extension RocketSpec { 97 | public func overriding(with yaml: Yaml) -> RocketSpec { 98 | let build: Runner = runnerFromStrings(yaml["build"].stringArray ?? []) 99 | .map({ $0 %? RockError.rocketSpecCouldNotBeBuilt }) 100 | ?? buildRunner 101 | let link: Runner = runnerFromStrings(yaml["link"].stringArray ?? []) 102 | .map({ $0 %? RockError.rocketSpecCouldNotBeLinked }) 103 | ?? linkRunner 104 | let unlink: Runner = runnerFromStrings(yaml["unlink"].stringArray ?? []) 105 | .map({ $0 %? RockError.rocketSpecCouldNotBeUnlinked }) 106 | ?? unlinkRunner 107 | let clean: Runner = runnerFromStrings(yaml["clean"].stringArray ?? []) 108 | .map({ $0 %? RockError.rocketSpecCouldNotBeCleaned }) 109 | ?? cleanRunner 110 | return RocketSpec( 111 | name: yaml["name"].string ?? name, 112 | url: yaml["url"].string ?? url, 113 | buildRunner: build, 114 | linkRunner: link, 115 | unlinkRunner: unlink, 116 | cleanRunner: clean 117 | ) 118 | } 119 | 120 | public static func fromYaml(_ yaml: Yaml, named name: String) -> Result { 121 | guard let url = yaml["url"].string else { return .failure(RockError.rocketSpecRequiresAnUrl(name: name)) } 122 | return .success(RocketSpec( 123 | name: name, 124 | url: url, 125 | build: yaml["build"].stringArray ?? [], 126 | link: yaml["link"].stringArray ?? [], 127 | unlink: yaml["unlink"].stringArray ?? [], 128 | clean: yaml["clean"].stringArray ?? [] 129 | )) 130 | } 131 | 132 | public static func fromPath(_ path: Path, named name: String) -> Result { 133 | let yaml = Result(attempt: { 134 | do { 135 | let text: String = try path.read() 136 | return try Yaml.rendering(text) 137 | } catch { 138 | throw AnyError(error) 139 | } 140 | }).mapError { anyError -> RockError in 141 | if case let Yaml.ResultError.message(message) = anyError.error { 142 | return RockError.rocketSpecIsNotValidYaml(message) 143 | } else { 144 | return RockError.rocketSpecCouldNotBeRead(path, anyError.error) 145 | } 146 | } 147 | return yaml.flatMap { RocketSpec.fromYaml($0, named: name) } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /Sources/RockLib/RockConfig.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import PromptLine 3 | import PathKit 4 | import Result 5 | 6 | /// ``` 7 | /// ~ 8 | /// ├── .rock <-- rockPath = $ROCK_PATH 9 | /// │   ├── bin 10 | /// │   ├── sources 11 | /// │   └── specs 12 | /// └── Project <-- projectPath = $(pwd) 13 | ///    ├── Rockfile.yaml 14 | ///    └── .rock 15 | /// ├── bin 16 | /// └── sources 17 | /// ``` 18 | public struct RockConfig { 19 | internal static let rockPathEnvVar: String = "ROCK_PATH" 20 | public static let rockConfig = RockConfig() 21 | 22 | public let rockPath: Path 23 | 24 | public let archiveScript = ["swift build -c release"] 25 | public let debugBuildScript = ["swift build"] 26 | public let testScript = ["swift test"] 27 | public let unlinkScript = ["rm -f $ROCK_PATH/bin/$ROCKET_SPEC_NAME"] 28 | public let linkScript = ["cp .build/release/$ROCKET_SPEC_NAME $ROCK_PATH/bin"] 29 | public let cleanScript = ["git checkout -- ."] 30 | 31 | public init( 32 | rockPath: Path = Path(ProcessInfo.processInfo.environment["ROCK_PATH"] ?? "~/.rock").absolute() 33 | ) { 34 | self.rockPath = rockPath 35 | } 36 | 37 | public var rockSpecsPath: Path { 38 | return rockPath + "specs" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Sources/RockLib/RockProject.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import PathKit 3 | import PromptLine 4 | import Result 5 | 6 | public struct RockProject { 7 | public let rockPath: Path 8 | public let specsPath: Path 9 | public let rockfile: Rockfile 10 | 11 | public init(rockfile: Rockfile, rockPath: Path, specsPath: Path = RockConfig.rockConfig.rockSpecsPath) { 12 | self.rockfile = rockfile 13 | self.rockPath = rockPath + ".rock" 14 | self.specsPath = specsPath 15 | } 16 | } 17 | 18 | public extension RockProject { 19 | public var binariesPath: Path { 20 | return rockPath + "bin" 21 | } 22 | 23 | public func binaryPath(for spec: RocketSpec) -> Path { 24 | return binariesPath + spec.name 25 | } 26 | 27 | public var sourcesPath: Path { 28 | return rockPath + "sources" 29 | } 30 | 31 | public func sourcePath(for spec: RocketSpec) -> Path { 32 | return sourcesPath + spec.name 33 | } 34 | 35 | public func repository(for spec: RocketSpec) -> Repository { 36 | return Repository(path: sourcePath(for: spec)) 37 | } 38 | 39 | public func mkSources() -> PromptRunner { 40 | return Prompt.mkpath(self.sourcesPath) 41 | %? RockError.couldNotCreateSourcesDirectory 42 | } 43 | } 44 | 45 | public extension RockProject { 46 | public var prompt: Prompt { 47 | return Prompt(workingDirectory: rockPath, environment: ProcessInfo.processInfo.environment) 48 | .declare("ROCK_PATH", as: rockPath.description) 49 | .declare("ROCK_SPECS_PATH", as: specsPath.description) 50 | } 51 | 52 | public func rocketSpec(named name: String) -> Result { 53 | return RockSpec().rocket(named: name) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Sources/RockLib/Rockfile.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Yaml 3 | import Result 4 | import PathKit 5 | import PromptLine 6 | 7 | public extension Array { 8 | func flatMap( 9 | _ transform: (Element) -> Result 10 | ) -> Result<[ElementOfResult], ErrorOfResult> { 11 | return self.reduce(.success([]), { (previousResult, element) -> Result<[ElementOfResult], ErrorOfResult> in 12 | return previousResult.flatMap({ p in transform(element).map({ p + [$0] })}) 13 | }) 14 | } 15 | } 16 | 17 | public enum Dependency { 18 | public typealias Name = String 19 | public typealias Version = String 20 | 21 | case named(Name, Version?) 22 | case inlined(RocketSpec, Version?) 23 | case overriding(Name, Yaml, Version?) 24 | 25 | public static func from(string: String) -> Dependency? { 26 | let fragments = string.components(separatedBy: "@") 27 | guard let head = fragments.first else { return nil } 28 | let version = fragments.dropFirst().first 29 | return Dependency.named(head, version ?? "master") 30 | } 31 | } 32 | 33 | public extension Dependency { 34 | public static func fromYaml(_ yaml: Yaml) -> Result { 35 | switch yaml { 36 | case let .dictionary(root): 37 | if case .some(.string(_)) = root["url"], case let .some(.string(name)) = root["name"] { 38 | return RocketSpec.fromYaml(yaml, named: name) 39 | .map { 40 | Dependency.inlined($0, root["version"]?.string) 41 | } 42 | } else if case .some(.string(_)) = root["name"] { 43 | return .failure(.notImplemented("Dependencies by name are not supported yet")) 44 | } 45 | return .failure(.notImplemented("Cannot handle dictionary dependencies without url and name")) 46 | case let .string(name): 47 | return .success(Dependency.from(string: name) ?? .named(name, "master")) 48 | default: 49 | return .failure(.rockfileHasInvalidDependency) 50 | } 51 | } 52 | } 53 | 54 | /// Represents a Rockfile.yaml file. 55 | /// 56 | /// ```yaml 57 | /// swift-version: 3.0.1 58 | /// dependencies: 59 | /// - needless 60 | /// - name: sourcery 61 | /// version: 0.5.0 62 | /// - name: langserver-swift 63 | /// swift-version: DEVELOPMENT-SNAPSHOT-2016-12-01-a 64 | /// - name: xcopen 65 | /// url: https://github.com/vknabel/xcopen 66 | /// ``` 67 | public struct Rockfile { 68 | public typealias Runner = PromptRunner 69 | 70 | public let name: String 71 | public let dependencies: [Dependency] 72 | public let scriptRunners: [String: Runner] 73 | 74 | public static func global(with dependencies: [Dependency]) -> Rockfile { 75 | return Rockfile(name: "global", dependencies: dependencies, scriptRunners: [:]) 76 | } 77 | } 78 | 79 | public extension Rockfile { 80 | public static func fromPath(_ path: Path) -> Result { 81 | let yaml = Result(attempt: { 82 | do { 83 | let text: String = try (path + "Rockfile").read() 84 | return try Yaml.rendering(text) 85 | } catch { 86 | throw AnyError(error) 87 | } 88 | }).mapError { anyError -> RockError in 89 | if case let Yaml.ResultError.message(message) = anyError.error { 90 | return RockError.rockfileIsNotValidYaml(message) 91 | } else { 92 | return RockError.rockfileCouldNotBeRead(path, anyError.error) 93 | } 94 | } 95 | return yaml.flatMap(Rockfile.fromYaml) 96 | } 97 | 98 | public static func fromYaml(_ yaml: Yaml) -> Result { 99 | guard case let .dictionary(root) = yaml 100 | else { return .failure(.rockfileMustContainDictionary) } 101 | guard case let .some(.string(name)) = root["name"] 102 | else { return .failure(.rockfileMustHaveAName) } 103 | guard case let .some(.array(rawDependencies)) = root["dependencies"] 104 | else { return .failure(.rockfileMustHaveDependencies) } 105 | return rawDependencies.flatMap(Dependency.fromYaml).map { 106 | let defaultScripts = [ 107 | "build": runnerFromStrings(yaml["build"].stringArray ?? [], or: RockConfig.rockConfig.debugBuildScript) 108 | %? { RockError.rockfileCustomScriptFailed("build", $0) }, 109 | "test": runnerFromStrings(yaml["test"].stringArray ?? [], or: RockConfig.rockConfig.testScript) 110 | %? { RockError.rockfileCustomScriptFailed("test", $0) }, 111 | "archive": runnerFromStrings(yaml["archive"].stringArray ?? [], or: RockConfig.rockConfig.archiveScript) 112 | %? { RockError.rockfileCustomScriptFailed("archive", $0) } 113 | ] 114 | let scripts = (yaml.dictionary?["scripts"]?.dictionary ?? [:]) 115 | .reduce(defaultScripts, { (scripts, element) in 116 | if let name = element.key.string, let shell = element.value.stringArray { 117 | var scripts = scripts 118 | scripts[name] = runnerFromStrings(shell, or: []) %? { RockError.rockfileCustomScriptFailed(name, $0) } 119 | return scripts 120 | } else { 121 | return scripts 122 | } 123 | }) 124 | 125 | return Rockfile(name: name, dependencies: $0, scriptRunners: scripts) 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /Sources/RockLib/Runner/Repository.swift: -------------------------------------------------------------------------------- 1 | import Result 2 | import PromptLine 3 | import PathKit 4 | 5 | public struct Repository { 6 | public let path: Path 7 | 8 | public func create() -> PromptRunner { 9 | return Prompt.mkpath(path) 10 | %& Prompt.chdir(self.path, run: >-"git init > /dev/null") 11 | } 12 | 13 | public func clone(url: String, branch: String = "master") -> PromptRunner { 14 | return Prompt.mkpath(path.parent()) 15 | %& Prompt.chdir( 16 | self.path.parent(), 17 | run: >-["git", "clone", "--depth", "1", url, "--branch", branch, self.path.description] 18 | ) 19 | } 20 | 21 | public func checkout(branch: String) -> PromptRunner { 22 | return Prompt.mkpath(path.parent()) 23 | %& Prompt.chdir( 24 | self.path, 25 | run: >-"git checkout \"\(branch)\" > /dev/null" 26 | ) 27 | } 28 | 29 | public func pull() -> PromptRunner { 30 | return Prompt.mkpath(path) 31 | %& Prompt.chdir( 32 | self.path, 33 | run: >-"git pull > /dev/null" 34 | ) 35 | } 36 | 37 | public func fetch(tags: Bool = false) -> PromptRunner { 38 | return Prompt.mkpath(path) 39 | %& Prompt.chdir( 40 | self.path, 41 | run: >-(tags ? "git fetch --tags > /dev/null" : "git fetch > /dev/null") 42 | ) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Sources/RockLib/Runner/RockSpecRunner.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import PromptLine 3 | import PathKit 4 | import Result 5 | 6 | public extension RockSpec { 7 | private var repository: Repository { 8 | return Repository(path: path) 9 | } 10 | 11 | public func clone() -> PromptRunner { 12 | if url == "" { 13 | return repository.create() 14 | %? { RockError.specsRepoCouldNotBeCloned(self, $0) } 15 | } else { 16 | return repository.clone(url: url) 17 | %? { RockError.specsRepoCouldNotBeCloned(self, $0) } 18 | } 19 | } 20 | 21 | public func update() -> PromptRunner { 22 | return repository.pull() 23 | %? { RockError.specsRepoCouldNotBeUpdated(self, $0) } 24 | } 25 | 26 | public func rocket(named name: String) -> Result { 27 | let targetPath = specsPath + "\(name).yaml" 28 | return RocketSpec.fromPath(targetPath, named: name) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Sources/RockLib/Runner/RocketBinRunner.swift: -------------------------------------------------------------------------------- 1 | import PathKit 2 | import Result 3 | import PromptLine 4 | 5 | public extension RocketSpec { 6 | public func unlink(for project: RockProject) -> (_ value: V) -> Result { 7 | return { value in 8 | do { 9 | try project.binaryPath(for: self).delete() 10 | return .success(value) 11 | } catch { 12 | return .failure(RockError.rocketBinaryCouldNotBeUnlinked(self)) 13 | } 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Sources/RockLib/Runner/RocketSrcRunner.swift: -------------------------------------------------------------------------------- 1 | import PromptLine 2 | import PathKit 3 | import Result 4 | 5 | public extension RocketSpec { 6 | public func clone(branch: String = "master", for project: RockProject) -> PromptRunner { 7 | return project.mkSources() 8 | %& ( 9 | project.repository(for: self).clone(url: self.url, branch: branch) 10 | %? { RockError.rocketSourceCouldNotBeCloned(self, $0) } 11 | ) 12 | } 13 | 14 | public func checkout(branch: String, for project: RockProject) -> PromptRunner { 15 | return project.mkSources() 16 | %& ( 17 | project.repository(for: self).checkout(branch: branch) 18 | %? { RockError.rocketSourceCouldNotBeUpdated(self, $0) } 19 | ) 20 | } 21 | 22 | public func pull(for project: RockProject) -> PromptRunner { 23 | return project.mkSources() 24 | %& ( 25 | project.repository(for: self).pull() 26 | %? { RockError.rocketSourceCouldNotBeUpdated(self, $0) } 27 | ) 28 | } 29 | 30 | public func fetch(for project: RockProject) -> PromptRunner { 31 | return project.mkSources() 32 | %& ( 33 | project.repository(for: self).fetch(tags: true) 34 | %? { RockError.rocketSourceCouldNotBeFetched(self, $0) } 35 | ) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Sources/RockLib/ScriptReporter.swift: -------------------------------------------------------------------------------- 1 | import PromptLine 2 | 3 | extension ReporterFormat { 4 | static var script = ReporterFormat(prefix: "🏃") 5 | } 6 | -------------------------------------------------------------------------------- /Sources/RockLib/YamlAndStencil.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import PathKit 3 | import Stencil 4 | import Yaml 5 | 6 | typealias ContextLiteral = [String: Any] 7 | 8 | private extension String { 9 | func render(_ context: ContextLiteral? = nil) throws -> String { 10 | let template = Template(templateString: self) 11 | return try template.render(context) 12 | } 13 | } 14 | private func sortDescriptor(by order: [Yaml]) -> Yaml.YamlSortDescriptor { 15 | func priority(_ yaml: Yaml) -> Int? { 16 | return order.index(of: yaml) 17 | } 18 | return { (lhs: (Yaml, Yaml), rhs: (Yaml, Yaml)) -> Bool in 19 | switch (priority(lhs.0), priority(rhs.0)) { 20 | case let (.some(lhs), .some(rhs)): 21 | return lhs < rhs 22 | case (.some(_), _): 23 | return true 24 | case (_, .some(_)): 25 | return false 26 | default: 27 | return true 28 | } 29 | } 30 | } 31 | 32 | private extension Yaml { 33 | /// Dict: only strings 34 | var rawValue: Any { 35 | switch self { 36 | case .null: 37 | return () 38 | case let .bool(value): 39 | return value 40 | case let .int(value): 41 | return value 42 | case let .double(value): 43 | return value 44 | case let .string(value): 45 | return value 46 | case let .array(value): 47 | return value.map { $0.rawValue } 48 | case let .dictionary(dict): 49 | var rawDict = [String: Any]() 50 | for (key, value) in dict { 51 | guard let key = key.string else { continue } 52 | rawDict[key] = value.rawValue 53 | } 54 | return rawDict 55 | } 56 | } 57 | 58 | func render( 59 | _ dictionary: [String: Any], 60 | descriptor: Yaml.YamlSortDescriptor 61 | ) throws -> (Any, Yaml) { 62 | let context = dictionary 63 | switch self { 64 | case let .string(value): 65 | let rendered = try value.render(context) 66 | return (rendered, .string(rendered)) 67 | case let .array(values): 68 | var contextList = [Any]() 69 | var vs = [(Any, Yaml)]() 70 | for v in values { 71 | let result = try v.render(dictionary, descriptor: descriptor) 72 | contextList.append(result.0) 73 | vs.append(result) 74 | } 75 | return (vs.map { $0.0 }, .array(vs.map { $0.1 })) 76 | case let .dictionary(value): 77 | var dict = [Yaml: Yaml]() 78 | var dictionary = dictionary 79 | var localDictionary: [String: Any] = [:] 80 | 81 | try value.sorted(by: descriptor).forEach { key, value in 82 | let renderedKey = try key.render(dictionary, descriptor: descriptor) 83 | let renderedValue = try value.render(dictionary, descriptor: descriptor) 84 | dict[renderedKey.1] = renderedValue.1 85 | 86 | if let renderedKey = renderedKey.0 as? String { 87 | localDictionary[renderedKey] = renderedValue.0 88 | dictionary[renderedKey] = renderedValue.0 89 | } 90 | } 91 | return (localDictionary, .dictionary(dict)) 92 | default: 93 | return (self.rawValue, self) 94 | } 95 | } 96 | } 97 | 98 | public extension Yaml { 99 | public typealias YamlSortDescriptor = ((Yaml, Yaml), (Yaml, Yaml)) -> Bool 100 | 101 | public static func rendering( 102 | _ text: String, 103 | orderedBy order: [Yaml] = ["const", "constant", "constants", "version", "license", "name", "url"] 104 | ) throws -> Yaml { 105 | return try rendering(text, sort: sortDescriptor(by: order)) 106 | } 107 | 108 | public static func rendering( 109 | _ text: String, 110 | sort descriptor: YamlSortDescriptor 111 | ) throws -> Yaml { 112 | return try Yaml.load(text).render([:], descriptor: descriptor).1 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /Sources/rock/Arguments.swift: -------------------------------------------------------------------------------- 1 | import Commandant 2 | import PathKit 3 | import RockLib 4 | import Result 5 | 6 | extension Path: ArgumentProtocol { 7 | public static var name = "path" 8 | 9 | public static func from(string: String) -> Path? { 10 | return Path.current + string 11 | } 12 | } 13 | 14 | extension Dependency: ArgumentProtocol { 15 | public static var name = "rocket[@version]" 16 | } 17 | 18 | let projectOption = Option( 19 | key: "project", 20 | defaultValue: Path.current, 21 | usage: "The target project. It should contain the Rockfile" 22 | ) 23 | let rocketArguments = Argument<[Dependency.Name]>(usage: "A list of rockets") 24 | let dependencyArguments = Argument<[Dependency]>(usage: "A list of rockets, optionally including a version") 25 | 26 | extension RockProject { 27 | static func fromOptions(_ m: CommandMode) -> Result> { 28 | let path: Result> = m <| projectOption 29 | return path.flatMap({ path in 30 | Rockfile.fromPath(path).mapError(CommandantError.commandError) 31 | .map { 32 | RockProject(rockfile: $0, rockPath: path) 33 | } 34 | }) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Sources/rock/Commandant.swift: -------------------------------------------------------------------------------- 1 | import Commandant 2 | import Result 3 | 4 | public func <| ( 5 | mode: CommandMode, 6 | argument: (CommandMode) -> Result> 7 | ) -> Result> { 8 | return argument(mode) 9 | } 10 | -------------------------------------------------------------------------------- /Sources/rock/Either.swift: -------------------------------------------------------------------------------- 1 | import Result 2 | import Commandant 3 | 4 | enum Either { 5 | case left(A) 6 | case right(B) 7 | 8 | static func options( 9 | _ lhs: @escaping (CommandMode) -> Result>, 10 | _ rhs: @escaping (CommandMode) -> Result> 11 | ) -> (CommandMode) -> Result, CommandantError> { 12 | return { m in 13 | switch m { 14 | case .arguments(_): 15 | return (m <| lhs).map(Either.left).flatMapError({ _ in 16 | (m <| rhs).map(Either.right) 17 | }) 18 | case .usage: 19 | let leftUsage = (.usage <| lhs).error?.description ?? "left" 20 | let rightUsage = (.usage <| rhs).error?.description ?? "right" 21 | return .failure(CommandantError.usageError(description: "Either \n \(leftUsage)\n or\n \(rightUsage)")) 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Sources/rock/InitCommand.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Commandant 3 | import Result 4 | import PathKit 5 | import RockLib 6 | import PromptLine 7 | 8 | struct InitCommand: CommandProtocol { 9 | let verb: String = "init" 10 | let function: String = "Initializes an empty Rockfile" 11 | 12 | func run(_ options: NoOptions) -> Result<(), RockError> { 13 | let prompt = Prompt() 14 | let targetPath = Path.current 15 | let rockfilePath = targetPath + "Rockfile" 16 | if rockfilePath.exists { 17 | return Result(error: .rockfileAlreadyExists) 18 | } 19 | return Result<(), NSError>(attempt: { 20 | let rockfile = [ 21 | // name defined in reduce 22 | "url: https://github.com/\(targetPath.parent().lastComponentWithoutExtension)/\(targetPath.lastComponentWithoutExtension)", 23 | "version: 0.1.0", 24 | "# Dependencies may be installed locally by running `rock install`", 25 | "# If you like you can even delcare Specs inline or override specific scripts.", 26 | "dependencies:", 27 | " - swiftlint@0.15.0", 28 | "", 29 | "# Scripts can be run as `rock run hello`", 30 | "# If you are using a SwiftPM Project, the scripts build, test, install and clean will be inferred.", 31 | "# Like everywhere in this Rockfile, you can use Stencil syntax inside your script values.", 32 | "# Before each script is executed, it's pre variant will be executed.", 33 | "# Once a script finshes, it's post variant is called.", 34 | "# Searching for script ideas? Why not try write docs or publish scripts?", 35 | "scripts:", 36 | " # build, test, install and clean are inferred", 37 | " xcode:", 38 | " # Generate Project. Optionally: customize", 39 | " - swift package generate-xcodeproj --enable-code-coverage", 40 | " postxcode: # Will automatically run after xcode", 41 | " - open {{name}}.xcodeproj", 42 | " lint:", 43 | " - swiftlint autocorrect", 44 | " - swiftlint", 45 | " prepublish:", 46 | " # Run your linter before publishing", 47 | " - rock lint", 48 | " # Releases may only be made on branch master", 49 | " - |", 50 | " if [ $(git branch | grep \\* | cut -d ' ' -f2) != \"master\" ]; then", 51 | " echo Releases must be created from branch master >&2", 52 | " exit 1", 53 | " fi", 54 | " publish: # Will automatically run prepublish", 55 | " # Assert clean working directory", 56 | " - git diff --exit-code > /dev/null", 57 | " # Set git tag", 58 | " - git tag -a {{version}} -m {{version}}", 59 | " - git push --tags", 60 | " # Copy latest Changelog", 61 | " - sed -n /'^## {{version}}$'/,/'^## '/p CHANGELOG.md | sed -e '$ d' | pbcopy", 62 | " # Create new release on Github", 63 | " - open {{url}}/releases/new?tag={{version}}", 64 | " # Publish to CocoaPods", 65 | " - |", 66 | " if [ -a {{name}}.podspec ]; then ", 67 | " bundle exec pod trunk push {{name}}.podspec --allow-warnings --swift-version=3.0", 68 | " fi", 69 | "" 70 | ].reduce("name: \(targetPath.lastComponentWithoutExtension)") { "\($0)\n\($1)"} 71 | try rockfilePath.write(rockfile) 72 | }).mapError(RockError.rockfileCouldNotBeCreated) 73 | .flatMap { _ in prompt >- report("Successfully created your", "Rockfile".theme.coded + "!", format: .success) } 74 | .map { _ in () } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Sources/rock/InstallCommand.swift: -------------------------------------------------------------------------------- 1 | import Commandant 2 | import PromptLine 3 | import Result 4 | import RockLib 5 | import PathKit 6 | 7 | struct InstallCommand: CommandProtocol { 8 | let verb: String = "install" 9 | let function: String = "Installs rockets. If no rockets are given, a project is assumed." 10 | 11 | func run(_ options: ProjectDependencyOptions) -> Result<(), RockError> { 12 | // Always update 13 | let defaultSpecs = RockSpec() 14 | let rockSpecs = defaultSpecs.path.exists 15 | ? report("Updating specs repository", defaultSpecs.name.theme.coded, format: .step) 16 | %& defaultSpecs.update() 17 | : report( 18 | "Cloning specs repository", 19 | defaultSpecs.name.theme.coded, 20 | "from", 21 | defaultSpecs.url.theme.coded, 22 | format: .step 23 | ) 24 | %& defaultSpecs.clone() 25 | _ = rockSpecs(Prompt()) 26 | 27 | switch options.inputs { 28 | case let .left(dependencies): 29 | return runGlobal(dependencies) 30 | case let .right(project): 31 | return runProject(project) 32 | } 33 | } 34 | 35 | func runGlobal(_ dependencies: [Dependency]) -> Result<(), RockError> { 36 | let project = RockProject( 37 | rockfile: Rockfile.global(with: dependencies), 38 | rockPath: RockConfig.rockConfig.rockPath.parent() 39 | ) 40 | return runProject(project) 41 | } 42 | 43 | func rocketSpecs(for project: RockProject) -> Result<[(RocketSpec, Dependency.Version)], RockError> { 44 | return project.rockfile.dependencies.flatMap { 45 | switch $0 { // into extension method 46 | case let .inlined(spec, version): 47 | let v: Dependency.Version = version ?? "master" 48 | return Result.success((spec, v)) 49 | case let .named(name, version): 50 | return project.rocketSpec(named: name).map { ($0, version ?? "master") } 51 | case let .overriding(name, yaml, version): 52 | return project.rocketSpec(named: name) 53 | .map { $0.overriding(with: yaml) } 54 | .map { ($0, version ?? "master") } 55 | } 56 | } 57 | } 58 | 59 | func runProject(_ project: RockProject) -> Result<(), RockError> { 60 | let rocketSpecs = self.rocketSpecs(for: project) 61 | 62 | if let specs = rocketSpecs.value { 63 | let specDescriptions = specs.map({ "\($0.0.name.theme.input)@\($0.1.theme.derived)" }) 64 | let log = report("Installing", specDescriptions.joined(separator: ", "), format: .step) as PromptReporter 65 | _ = log(project.prompt) 66 | } 67 | 68 | let install: Result<(), RockError> = rocketSpecs.flatMap({ rocketSpecs in 69 | rocketSpecs.flatMap({ (spec: (RocketSpec, Dependency.Version)) -> Result in 70 | let cloneOrFetch = project.sourcePath(for: spec.0).exists 71 | ? report("Updating", spec.1.theme.derived, "of", spec.0.name.theme.input, format: .step) 72 | %& Prompt.cd(project.sourcePath(for: spec.0)) 73 | %& spec.0.fetch(for: project) 74 | %& spec.0.checkout(branch: spec.1, for: project) 75 | %& spec.0.pull(for: project) 76 | : report( 77 | "Cloning", 78 | spec.0.name.theme.input, 79 | "from", 80 | spec.0.url.theme.derived, 81 | "at", 82 | spec.1.theme.derived, 83 | format: .step 84 | ) 85 | %& spec.0.clone(branch: spec.1, for: project) 86 | %& Prompt.cd(project.sourcePath(for: spec.0)) 87 | let runner: PromptRunner = cloneOrFetch 88 | %& Prompt.cd(project.sourcePath(for: spec.0)) 89 | %& report("Building", spec.0.name.theme.input, format: .step) 90 | %& spec.0.buildRunner 91 | %& (Prompt.mkpath(project.binariesPath) 92 | %? { _ in RockError.notImplemented("No error handler for mkpath binaries") }) 93 | %& report("Linking", spec.0.name.theme.input, format: .step) 94 | %& spec.0.linkRunner 95 | %& report("Cleaning", spec.0.name.theme.input, format: .step) 96 | %& spec.0.cleanRunner 97 | %& report("Successfully installed", spec.0.name.theme.input, format: .success) 98 | return runner(project.prompt 99 | .declare("ROCK_SPEC_VERSION", as: spec.1) 100 | .declare("ROCKET_SPEC_NAME", as: spec.0.name) 101 | ) 102 | }).map({ _ in () }) 103 | }) 104 | return install 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /Sources/rock/ProjectDependencyOptions.swift: -------------------------------------------------------------------------------- 1 | import Commandant 2 | import Result 3 | import RockLib 4 | 5 | struct ProjectDependencyOptions: OptionsProtocol { 6 | let inputs: Either<[Dependency], RockProject> 7 | 8 | static func evaluate(_ m: CommandMode) -> Result> { 9 | return ProjectDependencyOptions.init 10 | <*> m <| Either<[Dependency], RockProject>.options({ $0 <| dependencyArguments }, RockProject.fromOptions) 11 | } 12 | } 13 | 14 | struct ProjectRocketOptions: OptionsProtocol { 15 | let inputs: Either<[Dependency.Name], RockProject> 16 | 17 | static func evaluate(_ m: CommandMode) -> Result> { 18 | return ProjectRocketOptions.init 19 | <*> m <| Either<[Dependency.Name], RockProject>.options({ $0 <| rocketArguments }, RockProject.fromOptions) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/rock/Reporter.swift: -------------------------------------------------------------------------------- 1 | import RockLib 2 | import Result 3 | import PromptLine 4 | 5 | internal func >- (prompt: Prompt, runner: PromptRunner) -> Result { 6 | return runner(prompt) 7 | } 8 | 9 | extension ReporterFormat { 10 | static var phase = ReporterFormat(prefix: "🏎") 11 | static var step = ReporterFormat(prefix: "👉") 12 | static var success = ReporterFormat(prefix: "✅") 13 | static var question = ReporterFormat(prefix: "❓", terminator: "") 14 | static var error = ReporterFormat(prefix: "🚫 failed:".theme.error) 15 | } 16 | 17 | extension RockSpec { 18 | public var cloneReporter: PromptRunner { 19 | return report("Cloning", url.theme.derived, "to", path.theme.derived, format: .step) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/rock/RunCommand.swift: -------------------------------------------------------------------------------- 1 | import Commandant 2 | import PromptLine 3 | import Result 4 | import RockLib 5 | 6 | struct RunProjectOptions: OptionsProtocol { 7 | let project: RockProject 8 | let script: String 9 | 10 | static func evaluate(_ m: CommandMode) -> Result> { 11 | return (m <| RockProject.fromOptions) 12 | .map({ project in { RunProjectOptions(project: project, script: $0) } }) 13 | <*> m <| Argument(usage: "Script name") 14 | } 15 | } 16 | 17 | struct RunCommand: CommandProtocol { 18 | let verb: String = "run" 19 | let function: String = "Executes a script defined in your Rockfile." 20 | 21 | func run(_ options: RunProjectOptions) -> Result<(), RockError> { 22 | let runners = options.project.rockfile.scriptRunners 23 | if let runner = runners[options.script] { 24 | let prompt = options.project.prompt.lensWorkingDirectory(to: ".") 25 | let preRunner = runners["pre" + options.script] ?? Result.success 26 | let postRunner = runners["post" + options.script] ?? Result.success 27 | return (prompt >- report("Running", "pre\(options.script)".theme.derived, format: .phase) %& preRunner 28 | %& report("Running", options.script.theme.input, format: .phase) %& runner 29 | %& report("Running", "post\(options.script)".theme.derived, format: .phase) %& postRunner 30 | ) 31 | .map({ _ in () }) 32 | } else { 33 | return .failure(RockError.rockfileCustomScriptNotFound(options.script)) 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Sources/rock/ScriptCommand.swift: -------------------------------------------------------------------------------- 1 | import Commandant 2 | import PromptLine 3 | import Result 4 | import RockLib 5 | 6 | struct ProjectOptions: OptionsProtocol { 7 | let project: RockProject 8 | 9 | static func evaluate(_ m: CommandMode) -> Result> { 10 | return (m <| RockProject.fromOptions) 11 | .map(ProjectOptions.init) 12 | } 13 | } 14 | 15 | struct ScriptCommand: CommandProtocol { 16 | let verb: String 17 | let script: String 18 | let function: String 19 | 20 | init(verb: String, script: String? = nil, function: String) { 21 | self.verb = verb 22 | self.script = script ?? verb 23 | self.function = function 24 | } 25 | 26 | func run(_ options: ProjectOptions) -> Result<(), RockError> { 27 | return RunCommand().run( 28 | RunProjectOptions(project: options.project, script: script) 29 | ) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Sources/rock/UninstallCommand.swift: -------------------------------------------------------------------------------- 1 | import Commandant 2 | import PromptLine 3 | import Result 4 | import RockLib 5 | import PathKit 6 | import Foundation 7 | 8 | struct UninstallCommand: CommandProtocol { 9 | let verb: String = "uninstall" 10 | let function: String = "Uninstalls rockets. If no rockets are given, a project is assumed." 11 | 12 | func run(_ options: ProjectRocketOptions) -> Result<(), RockError> { 13 | switch options.inputs { 14 | case let .left(rockets): 15 | return rockets.flatMap({ name in 16 | Result<(), NSError>(attempt: (RockConfig.rockConfig.rockPath + "bin" + name).delete) 17 | .flatMap({ _ in Result<(), NSError>(attempt: (RockConfig.rockConfig.rockPath + "sources" + name).delete) }) 18 | .mapError(RockError.rocketsCouldNotBeUninstalled) 19 | }).map({ _ in () }) 20 | case let .right(project): 21 | return Result<(), NSError>(attempt: project.rockPath.delete) 22 | .mapError(RockError.rocketsCouldNotBeUninstalled) 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Sources/rock/VersionCommand.swift: -------------------------------------------------------------------------------- 1 | import Commandant 2 | import PromptLine 3 | import Result 4 | import RockLib 5 | 6 | struct VersionCommand: CommandProtocol { 7 | let verb: String = "version" 8 | let function: String = "Prints out the current version of rock: \(RockConfig.version)" 9 | 10 | func run(_ options: NoOptions) -> Result<(), RockError> { 11 | print(RockConfig.version) 12 | return .success() 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Sources/rock/curry.swift: -------------------------------------------------------------------------------- 1 | func curry(_ function: @escaping (A, B) -> C) -> (A) -> (B) -> C { 2 | return { first in { second in function(first, second) } } 3 | } 4 | -------------------------------------------------------------------------------- /Sources/rock/main.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import PathKit 3 | import Yaml 4 | import RockLib 5 | import Commandant 6 | import Result 7 | import PromptLine 8 | 9 | extension RocketSpec: CustomDebugStringConvertible { 10 | public var debugDescription: String { 11 | return "RocketSpec(name: \(name.debugDescription), url: \(url.debugDescription))" 12 | } 13 | } 14 | 15 | // # global install with rocket, otherwise local 16 | // % rock install [--project path] | [rocket[@tag-or-version]]* 17 | // # global update with rocket, otherwise local 18 | // % rock update [--project path] [rocket[@tag-or-version]]* 19 | // % rock self-update // no: rock install rock 20 | // % rock uninstall [rocket]* 21 | // % rock search [--project path] [rocket] 22 | 23 | infix operator <*> : LogicalDisjunctionPrecedence 24 | infix operator % : MultiplicationPrecedence 25 | infix operator <| : MultiplicationPrecedence 26 | 27 | let main = CommandRegistry() 28 | main.register(InstallCommand()) 29 | main.register(UninstallCommand()) 30 | main.register(InitCommand()) 31 | main.register(VersionCommand()) 32 | main.register(RunCommand()) 33 | main.register(ScriptCommand( 34 | verb: "build", 35 | function: "Builds the your project." 36 | )) 37 | main.register(ScriptCommand( 38 | verb: "test", 39 | function: "Runs all your tests." 40 | )) 41 | main.register(ScriptCommand( 42 | verb: "archive", 43 | function: "Builds a release for your project." 44 | )) 45 | main.register(ScriptCommand( 46 | verb: "lint", 47 | function: "Lints your projects." 48 | )) 49 | main.register(ScriptCommand( 50 | verb: "publish", 51 | function: "Publishes a new release of your project." 52 | )) 53 | main.register(HelpCommand(registry: main)) 54 | 55 | main.main(defaultVerb: "help") { error in 56 | let reporter: PromptReporter = report("\(error)", format: .error) 57 | _ = reporter(Prompt()) 58 | } 59 | -------------------------------------------------------------------------------- /Templates/Install/Install.swift.stencil: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | #if os(Linux) 4 | typealias Process = Task 5 | #endif 6 | 7 | let rockPath = ProcessInfo.processInfo.environment["ROCK_PATH"] ?? "~/.rock" 8 | let version = ProcessInfo.processInfo.environment["ROCK_VERSION"] ?? "{{ argument.version }}" 9 | 10 | func run(workingDir: String? = nil, _ args: String...) { 11 | let process = Process() 12 | process.launchPath = "/usr/bin/env" 13 | process.currentDirectoryPath = workingDir ?? process.currentDirectoryPath 14 | process.arguments = args 15 | process.launch() 16 | process.waitUntilExit() 17 | } 18 | 19 | let wd = "\(rockPath)/sources/rock/" 20 | run( 21 | "git", "clone", 22 | "https://github.com/vknabel/Rock", 23 | wd, 24 | "--depth", "1", 25 | "--branch", version 26 | ) 27 | run(workingDir: wd, "bash", "-c", "cd \"\(wd)\" && swift build -c release") 28 | run("mkdir", "-p", "\(rockPath)/bin") 29 | run("cp", "\(rockPath)/sources/rock/.build/release/rock", "\(rockPath)/bin") 30 | 31 | print("\n\nDon't forget to add $PATH and $ROCK_PATH to your profile:") 32 | print(" export ROCK_PATH=\"$HOME/.rock\"") 33 | print(" export PATH=\"$PATH:./.rock/bin:$ROCK_PATH/bin\"") 34 | -------------------------------------------------------------------------------- /Templates/RockLib/Version.swift.stencil: -------------------------------------------------------------------------------- 1 | public extension RockConfig { 2 | public static let version = "{{ argument.version }}" 3 | } 4 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import RockTests 3 | 4 | XCTMain([ 5 | testCase(RockTests.allTests), 6 | ]) 7 | -------------------------------------------------------------------------------- /Tests/RockTests/RockTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import RockLib 3 | 4 | class RockTests: XCTestCase { 5 | func testExample() { 6 | // This is an example of a functional test case. 7 | // Use XCTAssert and related functions to verify your tests produce the correct results. 8 | // XCTAssertEqual(Rock().text, "Hello, World!") 9 | } 10 | 11 | static var allTests : [(String, (RockTests) -> () throws -> Void)] { 12 | return [ 13 | ("testExample", testExample), 14 | ] 15 | } 16 | } 17 | --------------------------------------------------------------------------------