├── .github ├── CODEOWNERS ├── actions │ └── attach-release-assets │ │ ├── Dockerfile │ │ ├── action.yml │ │ └── run.sh ├── pull_request_template.md └── workflows │ ├── ci.yml │ ├── create-release.yml │ └── release.yml ├── .gitignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── mask-parser ├── Cargo.toml ├── README.md └── src │ ├── lib.rs │ ├── maskfile.rs │ └── parser.rs ├── mask ├── Cargo.toml ├── src │ ├── executor.rs │ ├── loader.rs │ └── main.rs └── tests │ ├── arguments_and_flags_test.rs │ ├── common │ └── mod.rs │ ├── env_vars_test.rs │ ├── integration_test.rs │ ├── introspect_test.rs │ ├── subcommands_test.rs │ └── supported_runtimes_test.rs └── maskfile.md /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @jacobdeichert 2 | -------------------------------------------------------------------------------- /.github/actions/attach-release-assets/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.10 2 | 3 | RUN apk add --no-cache bash curl ca-certificates jq 4 | 5 | COPY run.sh /run.sh 6 | 7 | ENTRYPOINT ["/run.sh"] 8 | -------------------------------------------------------------------------------- /.github/actions/attach-release-assets/action.yml: -------------------------------------------------------------------------------- 1 | name: attach-release-assets 2 | description: Attaches files/assets/binaries to a GitHub Release 3 | author: Jacob Deichert 4 | branding: 5 | icon: file-plus 6 | color: orange 7 | inputs: 8 | assets: 9 | description: A file glob of assets to be attached to the release 10 | required: true 11 | runs: 12 | using: docker 13 | image: Dockerfile 14 | args: 15 | - ${{ inputs.assets }} 16 | -------------------------------------------------------------------------------- /.github/actions/attach-release-assets/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | if [[ -z "$GITHUB_TOKEN" ]]; then 3 | echo "ERROR: the GITHUB_TOKEN env variable wasn't set" 4 | exit 1 5 | fi 6 | 7 | # A file glob of assets to upload. The docker entrypoint arg is "inputs.assets". 8 | ASSET_GLOBS=($1) 9 | AUTH_HEADER="Authorization: token ${GITHUB_TOKEN}" 10 | RELEASE_ID=$(jq --raw-output '.release.id' "$GITHUB_EVENT_PATH") 11 | 12 | # Upload each asset file to the GitHub Release 13 | for asset_file in "${ASSET_GLOBS[@]}"; do 14 | filename=$(basename "$asset_file") 15 | upload_url="https://uploads.github.com/repos/${GITHUB_REPOSITORY}/releases/${RELEASE_ID}/assets?name=${filename}" 16 | 17 | echo "Uploading asset: $asset_file" 18 | 19 | touch curl_log 20 | response_code=$(curl \ 21 | -sSL \ 22 | -XPOST \ 23 | -H "${AUTH_HEADER}" \ 24 | --upload-file "${asset_file}" \ 25 | --header "Content-Type:application/octet-stream" \ 26 | --write-out "%{http_code}" \ 27 | --output curl_log \ 28 | "$upload_url") 29 | 30 | if [ $response_code -ge 400 ]; then 31 | echo "ERROR: curl upload failed with status code $response_code" 32 | cat curl_log && rm curl_log 33 | exit 1 34 | fi 35 | done 36 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 4 | 5 | ### Which issue does this fix? 6 | 7 | 8 | Closes #{ISSUE} 9 | 10 | 11 | 12 | ### Describe the solution 13 | 14 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | name: ${{ matrix.platform }}-build 8 | runs-on: ${{ matrix.platform }} 9 | strategy: 10 | fail-fast: false 11 | matrix: 12 | platform: [ubuntu-latest, windows-latest, macos-latest] 13 | steps: 14 | - uses: actions/checkout@v4 15 | - name: Fetch dependencies 16 | run: cargo fetch 17 | - name: Build in release mode 18 | run: cargo build --release --frozen 19 | 20 | test: 21 | name: ${{ matrix.platform }}-test 22 | runs-on: ${{ matrix.platform }} 23 | strategy: 24 | fail-fast: false 25 | matrix: 26 | # Test on macos-13 because the macos-14 runner doesn't come with php installed. 27 | platform: [ubuntu-latest, windows-latest, macos-13] 28 | env: 29 | CLICOLOR_FORCE: 1 30 | steps: 31 | - uses: actions/checkout@v4 32 | - name: Add Ruby for a test that requires it 33 | uses: ruby/setup-ruby@v1 34 | with: 35 | ruby-version: 2.6 36 | - name: Fetch dependencies 37 | run: cargo fetch 38 | - name: Build in test mode 39 | run: cargo build --tests --frozen 40 | - name: Make mask available globally (windows) 41 | run: copy ./target/debug/mask.exe ~/.cargo/bin/ 42 | if: matrix.platform == 'windows-latest' 43 | - name: Make mask available globally (linux / mac) 44 | run: cp ./target/debug/mask ~/.cargo/bin 45 | if: matrix.platform != 'windows-latest' 46 | - name: Run tests 47 | run: cargo test --frozen 48 | 49 | format: 50 | runs-on: ubuntu-latest 51 | steps: 52 | - uses: actions/checkout@v4 53 | - name: Verify formatting is correct 54 | run: cargo fmt --all -- --check 55 | -------------------------------------------------------------------------------- /.github/workflows/create-release.yml: -------------------------------------------------------------------------------- 1 | name: Create Release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | crateName: 7 | description: 'Crate to Release' 8 | required: true 9 | default: 'mask' 10 | type: choice 11 | options: 12 | - mask 13 | - mask-parser 14 | releaseVersion: 15 | description: 'Version' 16 | required: true 17 | changelogUpdated: 18 | description: 'Is the CHANGELOG up to date?' 19 | required: true 20 | type: boolean 21 | 22 | permissions: 23 | contents: write 24 | 25 | env: 26 | VERSION: ${{ github.event.inputs.releaseVersion }} 27 | 28 | jobs: 29 | release-mask: 30 | if: ${{ github.event.inputs.crateName == 'mask' }} 31 | runs-on: ubuntu-latest 32 | steps: 33 | - uses: actions/checkout@v4 34 | with: 35 | # Must use a PAT to bypass the branch protection rule (allows pushing commits without requiring PRs) 36 | token: ${{ secrets.GH_PAT_TO_TRIGGER_RELEASE_WORKFLOW }} 37 | - name: Validate version number input 38 | run: | 39 | if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then 40 | echo "ERROR: invalid version number supplied '$VERSION'" 41 | exit 1 42 | fi 43 | - name: Verify CHANGELOG was updated 44 | run: | 45 | if [[ "${{ github.event.inputs.changelogUpdated }}" != "true" ]]; then 46 | echo "ERROR: you must update CHANGELOG before creating a new release" 47 | exit 1 48 | fi 49 | 50 | UNRELEASED_CHANGES=$(sed -n '/## UNRELEASED/,/## v/{//b;p}' CHANGELOG.md) 51 | if [[ "$UNRELEASED_CHANGES" == "" ]]; then 52 | echo "ERROR: CHANGELOG is missing release notes" 53 | exit 1 54 | fi 55 | # Write the release notes to a temp file we'll use below 56 | echo "$UNRELEASED_CHANGES" > ../RELEASE_NOTES.txt 57 | - name: Set up git user 58 | run: | 59 | git config user.name github-actions 60 | git config user.email github-actions@github.com 61 | - name: Commit version bumps 62 | run: | 63 | # Bump the version in the changelog 64 | sed -i "s/## UNRELEASED/## UNRELEASED\\n\\n\\n## v$VERSION ($(date '+%Y-%m-%d'))/" "CHANGELOG.md" 65 | # Bump the crate version 66 | sed -i "3s/.*/version = \"$VERSION\"/" "mask/Cargo.toml" 67 | # Let cargo bump the version in the lockfile 68 | cargo check 69 | git add -A && git commit -m "Publish mask v$VERSION" 70 | git push 71 | - name: Create a new Release 72 | env: 73 | # Must use a PAT to ensure the Release workflow is triggered 74 | # https://docs.github.com/en/actions/using-workflows/triggering-a-workflow#triggering-a-workflow-from-a-workflow 75 | GH_TOKEN: ${{ secrets.GH_PAT_TO_TRIGGER_RELEASE_WORKFLOW }} 76 | run: | 77 | gh release create "mask/$VERSION" --title "mask v$VERSION" --notes-file ../RELEASE_NOTES.txt 78 | 79 | release-mask-parser: 80 | if: ${{ github.event.inputs.crateName == 'mask-parser' }} 81 | runs-on: ubuntu-latest 82 | steps: 83 | - uses: actions/checkout@v4 84 | with: 85 | # Must use a PAT to bypass the branch protection rule (allows pushing commits without requiring PRs) 86 | token: ${{ secrets.GH_PAT_TO_TRIGGER_RELEASE_WORKFLOW }} 87 | - name: Validate version number input 88 | run: | 89 | if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then 90 | echo "ERROR: invalid version number supplied '$VERSION'" 91 | exit 1 92 | fi 93 | - name: Set up git user 94 | run: | 95 | git config user.name github-actions 96 | git config user.email github-actions@github.com 97 | - name: Commit version bumps 98 | run: | 99 | # Bump the crate version 100 | sed -i "3s/.*/version = \"$VERSION\"/" "mask-parser/Cargo.toml" 101 | # Let cargo bump the version in the lockfile 102 | cargo check 103 | git add -A && git commit -m "Publish mask-parser v$VERSION" 104 | git push 105 | - name: Create a new Release 106 | env: 107 | # Must use a PAT to ensure the Release workflow is triggered 108 | # https://docs.github.com/en/actions/using-workflows/triggering-a-workflow#triggering-a-workflow-from-a-workflow 109 | GH_TOKEN: ${{ secrets.GH_PAT_TO_TRIGGER_RELEASE_WORKFLOW }} 110 | run: | 111 | gh release create "mask-parser/$VERSION" --title "mask-parser v$VERSION" 112 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: New Release 2 | 3 | on: 4 | release: 5 | types: [released] 6 | 7 | permissions: 8 | contents: write 9 | 10 | jobs: 11 | release-crate-mask: 12 | if: ${{ startsWith(github.ref_name, 'mask/') }} 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | - name: "Publish crate" 17 | env: 18 | CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} 19 | run: cargo publish -p mask 20 | 21 | release-crate-mask-parser: 22 | if: ${{ startsWith(github.ref_name, 'mask-parser/') }} 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: actions/checkout@v4 26 | - name: "Publish crate" 27 | env: 28 | CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} 29 | run: cargo publish -p mask-parser 30 | 31 | release-linux: 32 | if: ${{ startsWith(github.ref_name, 'mask/') }} 33 | runs-on: ubuntu-latest 34 | steps: 35 | - uses: actions/checkout@v4 36 | - name: Fetch dependencies 37 | run: cargo fetch 38 | - name: Build in release mode 39 | run: cargo build --release --frozen --target x86_64-unknown-linux-gnu 40 | - name: Add the version tag to the binary name 41 | run: | 42 | VERSION_TAG="$(echo ${{ github.ref_name }} | cut -d/ -f2)" 43 | TARGET=x86_64-unknown-linux-gnu 44 | NAME=mask-$VERSION_TAG-$TARGET 45 | mkdir $NAME 46 | mv ./target/$TARGET/release/mask ./$NAME/mask 47 | chmod +x ./$NAME/mask 48 | zip -r $NAME.zip $NAME 49 | shasum -a 256 $NAME.zip > $NAME.zip.sha256 50 | # TODO: replace with https://cli.github.com/manual/gh_release_upload 51 | - name: Attach the binary to the release 52 | uses: ./.github/actions/attach-release-assets 53 | env: 54 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 55 | with: 56 | assets: ./*.zip ./*.sha256 57 | 58 | release-linux-musl: 59 | if: ${{ startsWith(github.ref_name, 'mask/') }} 60 | runs-on: ubuntu-latest 61 | steps: 62 | - uses: actions/checkout@v4 63 | - name: Fetch dependencies 64 | run: cargo fetch 65 | - name: Build in release mode 66 | run: | 67 | rustup target add x86_64-unknown-linux-musl 68 | cargo build --release --frozen --target x86_64-unknown-linux-musl 69 | - name: Add the version tag to the binary name 70 | run: | 71 | VERSION_TAG="$(echo ${{ github.ref_name }} | cut -d/ -f2)" 72 | TARGET=x86_64-unknown-linux-musl 73 | NAME=mask-$VERSION_TAG-$TARGET 74 | mkdir $NAME 75 | mv ./target/$TARGET/release/mask ./$NAME/mask 76 | chmod +x ./$NAME/mask 77 | zip -r $NAME.zip $NAME 78 | shasum -a 256 $NAME.zip > $NAME.zip.sha256 79 | # TODO: replace with https://cli.github.com/manual/gh_release_upload 80 | - name: Attach the binary to the release 81 | uses: ./.github/actions/attach-release-assets 82 | env: 83 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 84 | with: 85 | assets: ./*.zip ./*.sha256 86 | 87 | release-macos-aarch64: 88 | if: ${{ startsWith(github.ref_name, 'mask/') }} 89 | runs-on: macos-latest 90 | steps: 91 | - uses: actions/checkout@v4 92 | - name: Fetch dependencies 93 | run: cargo fetch 94 | - name: Build in release mode 95 | run: | 96 | SDKROOT=$(xcrun -sdk macosx --show-sdk-path) 97 | MACOSX_DEPLOYMENT_TARGET=$(xcrun -sdk macosx --show-sdk-platform-version) 98 | rustup target add aarch64-apple-darwin 99 | cargo build --release --frozen --target aarch64-apple-darwin 100 | - name: Add the version tag to the binary name 101 | run: | 102 | VERSION_TAG="$(echo ${{ github.ref_name }} | cut -d/ -f2)" 103 | TARGET=aarch64-apple-darwin 104 | NAME=mask-$VERSION_TAG-$TARGET 105 | mkdir $NAME 106 | mv ./target/$TARGET/release/mask ./$NAME/mask 107 | chmod +x ./$NAME/mask 108 | zip -r $NAME.zip $NAME 109 | shasum -a 256 $NAME.zip > $NAME.zip.sha256 110 | # TODO: replace with https://cli.github.com/manual/gh_release_upload 111 | # NOTE: cannot use the attach-release-assets action because macOS doesn't support docker-based actions. 112 | # Luckily we can just run the bash script directly. 113 | - name: Attach the binary to the release 114 | run: ./.github/actions/attach-release-assets/run.sh "./*.zip ./*.sha256" 115 | env: 116 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 117 | 118 | release-macos-x86-64: 119 | if: ${{ startsWith(github.ref_name, 'mask/') }} 120 | runs-on: macos-latest 121 | steps: 122 | - uses: actions/checkout@v4 123 | - name: Fetch dependencies 124 | run: cargo fetch 125 | - name: Build in release mode 126 | run: | 127 | rustup target add x86_64-apple-darwin 128 | cargo build --release --frozen --target x86_64-apple-darwin 129 | - name: Add the version tag to the binary name 130 | run: | 131 | VERSION_TAG="$(echo ${{ github.ref_name }} | cut -d/ -f2)" 132 | TARGET=x86_64-apple-darwin 133 | NAME=mask-$VERSION_TAG-$TARGET 134 | mkdir $NAME 135 | mv ./target/$TARGET/release/mask ./$NAME/mask 136 | chmod +x ./$NAME/mask 137 | zip -r $NAME.zip $NAME 138 | shasum -a 256 $NAME.zip > $NAME.zip.sha256 139 | # TODO: replace with https://cli.github.com/manual/gh_release_upload 140 | # NOTE: cannot use the attach-release-assets action because macOS doesn't support docker-based actions. 141 | # Luckily we can just run the bash script directly. 142 | - name: Attach the binary to the release 143 | run: ./.github/actions/attach-release-assets/run.sh "./*.zip ./*.sha256" 144 | env: 145 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 146 | 147 | release-homebrew: 148 | if: ${{ startsWith(github.ref_name, 'mask/') }} 149 | needs: release-macos-x86-64 150 | runs-on: ubuntu-latest 151 | steps: 152 | - name: Bump homebrew-core formula 153 | uses: mislav/bump-homebrew-formula-action@v3 154 | env: 155 | COMMITTER_TOKEN: ${{ secrets.HOMEBREW_GITHUB_API_TOKEN }} 156 | with: 157 | # Sends a PR to homebrew-core to update the formula 158 | formula-name: mask 159 | 160 | release-windows: 161 | if: ${{ startsWith(github.ref_name, 'mask/') }} 162 | runs-on: windows-latest 163 | steps: 164 | - uses: actions/checkout@v4 165 | - name: Fetch dependencies 166 | run: cargo fetch 167 | - name: Build in release mode 168 | run: cargo build --release --frozen --target x86_64-pc-windows-msvc 169 | - name: Add the version tag to the binary name 170 | shell: bash 171 | run: | 172 | VERSION_TAG="$(echo ${{ github.ref_name }} | cut -d/ -f2)" 173 | TARGET=x86_64-pc-windows-msvc 174 | NAME=mask-$VERSION_TAG-$TARGET 175 | mkdir $NAME 176 | mv ./target/$TARGET/release/mask.exe ./$NAME/mask.exe 177 | 7z a $NAME.zip $NAME 178 | certutil -hashfile $NAME.zip sha256 | grep -E [A-Fa-f0-9]{64} > $NAME.zip.sha256 179 | # TODO: replace with https://cli.github.com/manual/gh_release_upload 180 | # NOTE: cannot use the attach-release-assets action because windows doesn't support docker-based actions. 181 | # Luckily we can just run the bash script directly. 182 | - name: Attach the binary to the release 183 | shell: bash 184 | run: ./.github/actions/attach-release-assets/run.sh "./*.zip ./*.sha256" 185 | env: 186 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 187 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ####################################### 2 | # system 3 | ####################################### 4 | *.DS_Store 5 | *.DS_Store? 6 | *._* 7 | *.Spotlight-V100 8 | *.Trashes 9 | Icon? 10 | ehthumbs.db 11 | Thumbs.db 12 | Desktop.ini 13 | $RECYCLE.BIN/ 14 | 15 | ####################################### 16 | # rust/cargo 17 | ####################################### 18 | /target 19 | **/*.rs.bk 20 | 21 | ####################################### 22 | # node 23 | ####################################### 24 | package-lock.json 25 | 26 | # Logs 27 | logs 28 | *.log 29 | 30 | # Runtime data 31 | pids 32 | *.pid 33 | *.seed 34 | 35 | # Dependency directory 36 | node_modules 37 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | 4 | ## UNRELEASED 5 | 6 | 7 | ## v0.11.6 (2024-10-13) 8 | 9 | * ci: Fix apple x86_64 release process 10 | 11 | 12 | 13 | ## v0.11.5 (2024-10-13) 14 | 15 | * ci: Fix php tests on macos actions runner [#110](https://github.com/jacobdeichert/mask/pull/110) 16 | 17 | * Add choices list for flags [#111](https://github.com/jacobdeichert/mask/pull/111) ([@lovejia2022](https://github.com/lovejia2022)) 18 | 19 | * Optional arguments [#109](https://github.com/jacobdeichert/mask/pull/109) ([@jpal91](https://github.com/jpal91)) 20 | 21 | * Bump dependencies [#116](https://github.com/jacobdeichert/mask/pull/116) 22 | 23 | ## v0.11.4 (2023-10-15) 24 | 25 | ### Added 26 | 27 | * Improve error message for invalid executors [#99](https://github.com/jacobdeichert/mask/pull/99) ([@brumhard](https://github.com/brumhard)) 28 | 29 | * Publish sha256 checksums with each release [#103](https://github.com/jacobdeichert/mask/pull/103) ([@simonsan](https://github.com/simonsan)) 30 | 31 | * ci: Automate publishing of crates 32 | 33 | * ci: Add workflow for creating new releases 34 | 35 | 36 | 37 | ## v0.11.3 (2023-01-08) 38 | 39 | ### Added 40 | 41 | * Add musl linux binary to releases [#95](https://github.com/jacobdeichert/mask/pull/95) ([@8Mobius8](https://github.com/8Mobius8)) 42 | 43 | 44 | 45 | ## v0.11.2 (2022-05-25) 46 | 47 | ### Added 48 | 49 | * Add Windows binary to releases [#89](https://github.com/jacobdeichert/mask/pull/89) 50 | 51 | 52 | 53 | ## v0.11.1 (2022-02-14) 54 | 55 | ### Added 56 | 57 | * Add Apple Silicon (M1) binary to releases [#88](https://github.com/jacobdeichert/mask/pull/88) 58 | 59 | 60 | 61 | ## v0.11.0 (2021-04-15) 62 | 63 | ### Added 64 | 65 | * Split parsing logic into separate crate (`mask-parser`) [#83](https://github.com/jacobdeichert/mask/pull/83) 66 | 67 | * Add `--maskfile-introspect` flag to print out the maskfile command structure as json [#85](https://github.com/jacobdeichert/mask/pull/85) 68 | 69 | ### Fixed 70 | 71 | * Rename option_flags to named_flags [#84](https://github.com/jacobdeichert/mask/pull/84) 72 | 73 | * Rename `--maskfile-introspect` flag to `--introspect` [#86](https://github.com/jacobdeichert/mask/pull/86) 74 | 75 | 76 | 77 | ## v0.10.0 (2020-12-29) 78 | 79 | ### Added 80 | 81 | * Allow marking named flags (previously called optional flags) as required [#74](https://github.com/jacobdeichert/mask/pull/74) ([@cherusk](https://github.com/cherusk)) 82 | 83 | * Automatically bump Homebrew formula on new release [#74](https://github.com/jacobdeichert/mask/pull/64) ([@vladimyr](https://github.com/vladimyr)) 84 | 85 | ### Fixed 86 | 87 | * Fix issue where command before level 1 heading was ignored [#77](https://github.com/jacobdeichert/mask/pull/77) 88 | 89 | 90 | 91 | 92 | ## v0.9.0 (2020-08-10) 93 | 94 | ### Added 95 | 96 | * Allow absolute naming for headings [#71](https://github.com/jacobdeichert/mask/pull/71) ([@lsampras](https://github.com/lsampras)) 97 | 98 | ### Fixed 99 | 100 | * Remove commands without code blocks [#70](https://github.com/jacobdeichert/mask/pull/70) ([@twitu](https://github.com/twitu)) 101 | 102 | 103 | 104 | 105 | ## v0.8.0 (2020-04-26) 106 | 107 | ### Added 108 | 109 | * Windows powershell and batch support [#58](https://github.com/jacobdeichert/mask/pull/58) ([@tensor-programming](https://github.com/tensor-programming)) 110 | 111 | ### Fixed 112 | 113 | * Re-add formatting CI [#60](https://github.com/jacobdeichert/mask/pull/60) 114 | 115 | 116 | 117 | 118 | ## v0.7.1 (2019-12-13) 119 | 120 | ### Fixed 121 | 122 | * Fix typo in README [#39](https://github.com/jacobdeichert/mask/pull/39) ([@nicoder](https://github.com/nicoder)) 123 | 124 | ### Added 125 | 126 | * Automated GitHub Releases and attached precompiled binaries for macOS and linux 127 | 128 | 129 | 130 | 131 | 132 | ## v0.7.0 (2019-10-13) 133 | 134 | ### Breaking Changes 135 | 136 | * Allow any shell executor that supports -c evaluation (sh, bash, zsh, fish, dash, etc...) [#37](https://github.com/jacobdeichert/mask/pull/37) 137 | * Error when chosen command doesn't have a script [#37](https://github.com/jacobdeichert/mask/pull/37) 138 | * Error when chosen command script doesn't have a lang code to determine the executor [#37](https://github.com/jacobdeichert/mask/pull/37) 139 | * Remove the `ON::INIT` script idea [#38](https://github.com/jacobdeichert/mask/pull/38) 140 | 141 | 142 | 143 | 144 | 145 | ## v0.6.0 (2019-10-06) 146 | 147 | ### Breaking Changes 148 | 149 | * Add support for an `ON::INIT` script which initializes subshell environments [#36](https://github.com/jacobdeichert/mask/pull/36) 150 | 151 | 152 | 153 | 154 | 155 | ## v0.5.2 (2019-09-26) 156 | 157 | ### Added 158 | 159 | * Add support for type=number in option flags for numerical validation [#35](https://github.com/jacobdeichert/mask/pull/35) 160 | 161 | ### Fixed 162 | 163 | * Allow entering negative numbers as arg values [#34](https://github.com/jacobdeichert/mask/pull/34) 164 | 165 | 166 | 167 | 168 | 169 | ## v0.5.1 (2019-09-24) 170 | 171 | ### Added 172 | 173 | * Colored help output and text wrapping [#30](https://github.com/jacobdeichert/mask/pull/30) ([@DrSensor](https://github.com/DrSensor)) 174 | 175 | ### Fixed 176 | 177 | * No need to show mask's author and description in help output [#32](https://github.com/jacobdeichert/mask/pull/32) 178 | 179 | 180 | 181 | 182 | 183 | ## v0.5.0 (2019-07-28) 184 | 185 | ### Added 186 | 187 | * Add `$MASK` and `$MASKFILE_DIR` utility env variables [#26](https://github.com/jacobdeichert/mask/pull/26) 188 | 189 | ### Fixed 190 | 191 | * Error when command has no script and missing subcommand [#27](https://github.com/jacobdeichert/mask/pull/27) 192 | * Remove needless version flag from all subcommands [#27](https://github.com/jacobdeichert/mask/pull/27) 193 | 194 | 195 | 196 | 197 | 198 | ## v0.4.0 (2019-07-26) 199 | 200 | ### Fixed 201 | 202 | * Prevent adding needless verbose flag to commands with no script [#21](https://github.com/jacobdeichert/mask/pull/21) 203 | 204 | * Propagate exit status of child process to main process [#22](https://github.com/jacobdeichert/mask/pull/22) ([@atty303](https://github.com/atty303)) 205 | 206 | * Allow --version and --help to be used even when missing a maskfile [#23](https://github.com/jacobdeichert/mask/pull/23) 207 | 208 | * Exit with an error message and status code 1 when subcommand is missing [#23](https://github.com/jacobdeichert/mask/pull/23) 209 | 210 | * Always exit with error when custom maskfile is not found [#25](https://github.com/jacobdeichert/mask/pull/25) 211 | 212 | 213 | 214 | 215 | 216 | ## v0.3.1 (2019-07-21) 217 | 218 | ### Added 219 | 220 | * Allow specifying an external maskfile.md to use [#15](https://github.com/jacobdeichert/mask/pull/19) ([@felipesere](https://github.com/felipesere)) 221 | 222 | 223 | 224 | 225 | 226 | ## v0.3.0 (2019-07-19) 227 | 228 | ### Breaking Changes 229 | 230 | * Changed required arg syntax from `` to `(arg)` to prevent markdown renderers from breaking [#16](https://github.com/jacobdeichert/mask/pull/16) 231 | 232 | ### Fixed 233 | 234 | * Using `<>` for required args causes breakage in certain markdown renderers [#15](https://github.com/jacobdeichert/mask/issues/15) 235 | * Using `inline code` in a command description doesn't get output with `-h` [#9](https://github.com/jacobdeichert/mask/issues/9) 236 | 237 | 238 | 239 | 240 | 241 | ## v0.2.1 (2019-07-17) 242 | 243 | ### Added 244 | 245 | * bash, zsh, and fish executors 246 | 247 | 248 | 249 | 250 | 251 | ## v0.2.0 (2019-07-16) 252 | 253 | Initial release 🎉 254 | -------------------------------------------------------------------------------- /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 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at git@jakedeichert.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | 4 | 5 | 6 | 7 | ## Reporting Bugs or Suggesting Features 8 | 9 | Please file an issue for discussion of new features or bugs. If an existing issue already exists, please comment there instead if you have more details to provide. 10 | 11 | If you're logging a bug, please be as detailed as possible. It's highly recommended that you post a minimum `maskfile.md` that reproduces the problem so we can debug it more easily. Issues may be closed if you don't provide enough info. 12 | 13 | 14 | 15 | 16 | 17 | ## Running the test suite 18 | 19 | After you clone `mask` you will need to [install](https://github.com/jacobdeichert/mask#installation) it to successfully run the test suite. 20 | 21 | Once you have `mask` installed, you can then run `mask link` and `mask test` to run the entire test suite. This will ensure all test cases are running against the latest modifications you've made to the `mask` source. 22 | 23 | 24 | 25 | 26 | 27 | ## Submitting Pull Requests 28 | 29 | If you're tackling a larger feature or bug, please leave a comment on the corresponding issue unless someone before you already has indicated so. If there is no issue yet, please create one so it can be discussed, before you start working on a PR. This is to save you time and energy, and to ensure we're all on the same page with the chosen solution! 🙂 30 | 31 | Adding tests is highly recommended, though not always necessary depending on the scope of changes made. 32 | 33 | 34 | 35 | 36 | 37 | ## Code of Conduct 38 | 39 | Please review and follow the rules within our [Code of Conduct](CODE_OF_CONDUCT.md). 40 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "1.1.3" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "ansi_term" 16 | version = "0.12.1" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" 19 | dependencies = [ 20 | "winapi", 21 | ] 22 | 23 | [[package]] 24 | name = "anstyle" 25 | version = "1.0.8" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" 28 | 29 | [[package]] 30 | name = "assert_cmd" 31 | version = "1.0.8" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "c98233c6673d8601ab23e77eb38f999c51100d46c5703b17288c57fddf3a1ffe" 34 | dependencies = [ 35 | "bstr 0.2.17", 36 | "doc-comment", 37 | "predicates 2.1.5", 38 | "predicates-core", 39 | "predicates-tree", 40 | "wait-timeout", 41 | ] 42 | 43 | [[package]] 44 | name = "assert_fs" 45 | version = "1.1.2" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "7efdb1fdb47602827a342857666feb372712cbc64b414172bd6b167a02927674" 48 | dependencies = [ 49 | "anstyle", 50 | "doc-comment", 51 | "globwalk", 52 | "predicates 3.1.2", 53 | "predicates-core", 54 | "predicates-tree", 55 | "tempfile", 56 | ] 57 | 58 | [[package]] 59 | name = "atty" 60 | version = "0.2.14" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 63 | dependencies = [ 64 | "hermit-abi", 65 | "libc", 66 | "winapi", 67 | ] 68 | 69 | [[package]] 70 | name = "autocfg" 71 | version = "1.4.0" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 74 | 75 | [[package]] 76 | name = "bitflags" 77 | version = "1.3.2" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 80 | 81 | [[package]] 82 | name = "bitflags" 83 | version = "2.6.0" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" 86 | 87 | [[package]] 88 | name = "bstr" 89 | version = "0.2.17" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" 92 | dependencies = [ 93 | "lazy_static", 94 | "memchr", 95 | "regex-automata 0.1.10", 96 | ] 97 | 98 | [[package]] 99 | name = "bstr" 100 | version = "1.10.0" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" 103 | dependencies = [ 104 | "memchr", 105 | "serde", 106 | ] 107 | 108 | [[package]] 109 | name = "cfg-if" 110 | version = "1.0.0" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 113 | 114 | [[package]] 115 | name = "clap" 116 | version = "2.34.0" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" 119 | dependencies = [ 120 | "ansi_term", 121 | "atty", 122 | "bitflags 1.3.2", 123 | "strsim", 124 | "term_size", 125 | "textwrap", 126 | "unicode-width", 127 | "vec_map", 128 | ] 129 | 130 | [[package]] 131 | name = "colored" 132 | version = "2.1.0" 133 | source = "registry+https://github.com/rust-lang/crates.io-index" 134 | checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" 135 | dependencies = [ 136 | "lazy_static", 137 | "windows-sys 0.48.0", 138 | ] 139 | 140 | [[package]] 141 | name = "crossbeam-deque" 142 | version = "0.8.5" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" 145 | dependencies = [ 146 | "crossbeam-epoch", 147 | "crossbeam-utils", 148 | ] 149 | 150 | [[package]] 151 | name = "crossbeam-epoch" 152 | version = "0.9.18" 153 | source = "registry+https://github.com/rust-lang/crates.io-index" 154 | checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" 155 | dependencies = [ 156 | "crossbeam-utils", 157 | ] 158 | 159 | [[package]] 160 | name = "crossbeam-utils" 161 | version = "0.8.20" 162 | source = "registry+https://github.com/rust-lang/crates.io-index" 163 | checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" 164 | 165 | [[package]] 166 | name = "difference" 167 | version = "2.0.0" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" 170 | 171 | [[package]] 172 | name = "difflib" 173 | version = "0.4.0" 174 | source = "registry+https://github.com/rust-lang/crates.io-index" 175 | checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" 176 | 177 | [[package]] 178 | name = "doc-comment" 179 | version = "0.3.3" 180 | source = "registry+https://github.com/rust-lang/crates.io-index" 181 | checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" 182 | 183 | [[package]] 184 | name = "either" 185 | version = "1.13.0" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" 188 | 189 | [[package]] 190 | name = "errno" 191 | version = "0.3.9" 192 | source = "registry+https://github.com/rust-lang/crates.io-index" 193 | checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" 194 | dependencies = [ 195 | "libc", 196 | "windows-sys 0.52.0", 197 | ] 198 | 199 | [[package]] 200 | name = "fastrand" 201 | version = "2.1.1" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" 204 | 205 | [[package]] 206 | name = "float-cmp" 207 | version = "0.8.0" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | checksum = "e1267f4ac4f343772758f7b1bdcbe767c218bbab93bb432acbf5162bbf85a6c4" 210 | dependencies = [ 211 | "num-traits", 212 | ] 213 | 214 | [[package]] 215 | name = "globset" 216 | version = "0.4.15" 217 | source = "registry+https://github.com/rust-lang/crates.io-index" 218 | checksum = "15f1ce686646e7f1e19bf7d5533fe443a45dbfb990e00629110797578b42fb19" 219 | dependencies = [ 220 | "aho-corasick", 221 | "bstr 1.10.0", 222 | "log", 223 | "regex-automata 0.4.8", 224 | "regex-syntax", 225 | ] 226 | 227 | [[package]] 228 | name = "globwalk" 229 | version = "0.9.1" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757" 232 | dependencies = [ 233 | "bitflags 2.6.0", 234 | "ignore", 235 | "walkdir", 236 | ] 237 | 238 | [[package]] 239 | name = "hermit-abi" 240 | version = "0.1.19" 241 | source = "registry+https://github.com/rust-lang/crates.io-index" 242 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 243 | dependencies = [ 244 | "libc", 245 | ] 246 | 247 | [[package]] 248 | name = "ignore" 249 | version = "0.4.23" 250 | source = "registry+https://github.com/rust-lang/crates.io-index" 251 | checksum = "6d89fd380afde86567dfba715db065673989d6253f42b88179abd3eae47bda4b" 252 | dependencies = [ 253 | "crossbeam-deque", 254 | "globset", 255 | "log", 256 | "memchr", 257 | "regex-automata 0.4.8", 258 | "same-file", 259 | "walkdir", 260 | "winapi-util", 261 | ] 262 | 263 | [[package]] 264 | name = "itertools" 265 | version = "0.10.5" 266 | source = "registry+https://github.com/rust-lang/crates.io-index" 267 | checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" 268 | dependencies = [ 269 | "either", 270 | ] 271 | 272 | [[package]] 273 | name = "itoa" 274 | version = "1.0.11" 275 | source = "registry+https://github.com/rust-lang/crates.io-index" 276 | checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" 277 | 278 | [[package]] 279 | name = "lazy_static" 280 | version = "1.5.0" 281 | source = "registry+https://github.com/rust-lang/crates.io-index" 282 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 283 | 284 | [[package]] 285 | name = "libc" 286 | version = "0.2.159" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" 289 | 290 | [[package]] 291 | name = "linux-raw-sys" 292 | version = "0.4.14" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" 295 | 296 | [[package]] 297 | name = "log" 298 | version = "0.4.22" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 301 | 302 | [[package]] 303 | name = "mask" 304 | version = "0.11.6" 305 | dependencies = [ 306 | "assert_cmd", 307 | "assert_fs", 308 | "clap", 309 | "colored", 310 | "mask-parser", 311 | "predicates 1.0.8", 312 | "serde_json", 313 | ] 314 | 315 | [[package]] 316 | name = "mask-parser" 317 | version = "0.2.2" 318 | dependencies = [ 319 | "pulldown-cmark", 320 | "serde", 321 | "serde_json", 322 | ] 323 | 324 | [[package]] 325 | name = "memchr" 326 | version = "2.7.4" 327 | source = "registry+https://github.com/rust-lang/crates.io-index" 328 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 329 | 330 | [[package]] 331 | name = "normalize-line-endings" 332 | version = "0.3.0" 333 | source = "registry+https://github.com/rust-lang/crates.io-index" 334 | checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" 335 | 336 | [[package]] 337 | name = "num-traits" 338 | version = "0.2.19" 339 | source = "registry+https://github.com/rust-lang/crates.io-index" 340 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 341 | dependencies = [ 342 | "autocfg", 343 | ] 344 | 345 | [[package]] 346 | name = "once_cell" 347 | version = "1.20.2" 348 | source = "registry+https://github.com/rust-lang/crates.io-index" 349 | checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" 350 | 351 | [[package]] 352 | name = "predicates" 353 | version = "1.0.8" 354 | source = "registry+https://github.com/rust-lang/crates.io-index" 355 | checksum = "f49cfaf7fdaa3bfacc6fa3e7054e65148878354a5cfddcf661df4c851f8021df" 356 | dependencies = [ 357 | "difference", 358 | "float-cmp", 359 | "normalize-line-endings", 360 | "predicates-core", 361 | "regex", 362 | ] 363 | 364 | [[package]] 365 | name = "predicates" 366 | version = "2.1.5" 367 | source = "registry+https://github.com/rust-lang/crates.io-index" 368 | checksum = "59230a63c37f3e18569bdb90e4a89cbf5bf8b06fea0b84e65ea10cc4df47addd" 369 | dependencies = [ 370 | "difflib", 371 | "itertools", 372 | "predicates-core", 373 | ] 374 | 375 | [[package]] 376 | name = "predicates" 377 | version = "3.1.2" 378 | source = "registry+https://github.com/rust-lang/crates.io-index" 379 | checksum = "7e9086cc7640c29a356d1a29fd134380bee9d8f79a17410aa76e7ad295f42c97" 380 | dependencies = [ 381 | "anstyle", 382 | "difflib", 383 | "predicates-core", 384 | ] 385 | 386 | [[package]] 387 | name = "predicates-core" 388 | version = "1.0.8" 389 | source = "registry+https://github.com/rust-lang/crates.io-index" 390 | checksum = "ae8177bee8e75d6846599c6b9ff679ed51e882816914eec639944d7c9aa11931" 391 | 392 | [[package]] 393 | name = "predicates-tree" 394 | version = "1.0.11" 395 | source = "registry+https://github.com/rust-lang/crates.io-index" 396 | checksum = "41b740d195ed3166cd147c8047ec98db0e22ec019eb8eeb76d343b795304fb13" 397 | dependencies = [ 398 | "predicates-core", 399 | "termtree", 400 | ] 401 | 402 | [[package]] 403 | name = "proc-macro2" 404 | version = "1.0.87" 405 | source = "registry+https://github.com/rust-lang/crates.io-index" 406 | checksum = "b3e4daa0dcf6feba26f985457cdf104d4b4256fc5a09547140f3631bb076b19a" 407 | dependencies = [ 408 | "unicode-ident", 409 | ] 410 | 411 | [[package]] 412 | name = "pulldown-cmark" 413 | version = "0.5.3" 414 | source = "registry+https://github.com/rust-lang/crates.io-index" 415 | checksum = "77043da1282374688ee212dc44b3f37ff929431de9c9adc3053bd3cee5630357" 416 | dependencies = [ 417 | "bitflags 1.3.2", 418 | "memchr", 419 | "unicase", 420 | ] 421 | 422 | [[package]] 423 | name = "quote" 424 | version = "1.0.37" 425 | source = "registry+https://github.com/rust-lang/crates.io-index" 426 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 427 | dependencies = [ 428 | "proc-macro2", 429 | ] 430 | 431 | [[package]] 432 | name = "regex" 433 | version = "1.11.0" 434 | source = "registry+https://github.com/rust-lang/crates.io-index" 435 | checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" 436 | dependencies = [ 437 | "aho-corasick", 438 | "memchr", 439 | "regex-automata 0.4.8", 440 | "regex-syntax", 441 | ] 442 | 443 | [[package]] 444 | name = "regex-automata" 445 | version = "0.1.10" 446 | source = "registry+https://github.com/rust-lang/crates.io-index" 447 | checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" 448 | 449 | [[package]] 450 | name = "regex-automata" 451 | version = "0.4.8" 452 | source = "registry+https://github.com/rust-lang/crates.io-index" 453 | checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" 454 | dependencies = [ 455 | "aho-corasick", 456 | "memchr", 457 | "regex-syntax", 458 | ] 459 | 460 | [[package]] 461 | name = "regex-syntax" 462 | version = "0.8.5" 463 | source = "registry+https://github.com/rust-lang/crates.io-index" 464 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 465 | 466 | [[package]] 467 | name = "rustix" 468 | version = "0.38.37" 469 | source = "registry+https://github.com/rust-lang/crates.io-index" 470 | checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" 471 | dependencies = [ 472 | "bitflags 2.6.0", 473 | "errno", 474 | "libc", 475 | "linux-raw-sys", 476 | "windows-sys 0.52.0", 477 | ] 478 | 479 | [[package]] 480 | name = "ryu" 481 | version = "1.0.18" 482 | source = "registry+https://github.com/rust-lang/crates.io-index" 483 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 484 | 485 | [[package]] 486 | name = "same-file" 487 | version = "1.0.6" 488 | source = "registry+https://github.com/rust-lang/crates.io-index" 489 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 490 | dependencies = [ 491 | "winapi-util", 492 | ] 493 | 494 | [[package]] 495 | name = "serde" 496 | version = "1.0.210" 497 | source = "registry+https://github.com/rust-lang/crates.io-index" 498 | checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" 499 | dependencies = [ 500 | "serde_derive", 501 | ] 502 | 503 | [[package]] 504 | name = "serde_derive" 505 | version = "1.0.210" 506 | source = "registry+https://github.com/rust-lang/crates.io-index" 507 | checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" 508 | dependencies = [ 509 | "proc-macro2", 510 | "quote", 511 | "syn", 512 | ] 513 | 514 | [[package]] 515 | name = "serde_json" 516 | version = "1.0.128" 517 | source = "registry+https://github.com/rust-lang/crates.io-index" 518 | checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" 519 | dependencies = [ 520 | "itoa", 521 | "memchr", 522 | "ryu", 523 | "serde", 524 | ] 525 | 526 | [[package]] 527 | name = "strsim" 528 | version = "0.8.0" 529 | source = "registry+https://github.com/rust-lang/crates.io-index" 530 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 531 | 532 | [[package]] 533 | name = "syn" 534 | version = "2.0.79" 535 | source = "registry+https://github.com/rust-lang/crates.io-index" 536 | checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" 537 | dependencies = [ 538 | "proc-macro2", 539 | "quote", 540 | "unicode-ident", 541 | ] 542 | 543 | [[package]] 544 | name = "tempfile" 545 | version = "3.13.0" 546 | source = "registry+https://github.com/rust-lang/crates.io-index" 547 | checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" 548 | dependencies = [ 549 | "cfg-if", 550 | "fastrand", 551 | "once_cell", 552 | "rustix", 553 | "windows-sys 0.59.0", 554 | ] 555 | 556 | [[package]] 557 | name = "term_size" 558 | version = "0.3.2" 559 | source = "registry+https://github.com/rust-lang/crates.io-index" 560 | checksum = "1e4129646ca0ed8f45d09b929036bafad5377103edd06e50bf574b353d2b08d9" 561 | dependencies = [ 562 | "libc", 563 | "winapi", 564 | ] 565 | 566 | [[package]] 567 | name = "termtree" 568 | version = "0.4.1" 569 | source = "registry+https://github.com/rust-lang/crates.io-index" 570 | checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" 571 | 572 | [[package]] 573 | name = "textwrap" 574 | version = "0.11.0" 575 | source = "registry+https://github.com/rust-lang/crates.io-index" 576 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 577 | dependencies = [ 578 | "term_size", 579 | "unicode-width", 580 | ] 581 | 582 | [[package]] 583 | name = "unicase" 584 | version = "2.7.0" 585 | source = "registry+https://github.com/rust-lang/crates.io-index" 586 | checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" 587 | dependencies = [ 588 | "version_check", 589 | ] 590 | 591 | [[package]] 592 | name = "unicode-ident" 593 | version = "1.0.13" 594 | source = "registry+https://github.com/rust-lang/crates.io-index" 595 | checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" 596 | 597 | [[package]] 598 | name = "unicode-width" 599 | version = "0.1.14" 600 | source = "registry+https://github.com/rust-lang/crates.io-index" 601 | checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" 602 | 603 | [[package]] 604 | name = "vec_map" 605 | version = "0.8.2" 606 | source = "registry+https://github.com/rust-lang/crates.io-index" 607 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 608 | 609 | [[package]] 610 | name = "version_check" 611 | version = "0.9.5" 612 | source = "registry+https://github.com/rust-lang/crates.io-index" 613 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 614 | 615 | [[package]] 616 | name = "wait-timeout" 617 | version = "0.2.0" 618 | source = "registry+https://github.com/rust-lang/crates.io-index" 619 | checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" 620 | dependencies = [ 621 | "libc", 622 | ] 623 | 624 | [[package]] 625 | name = "walkdir" 626 | version = "2.5.0" 627 | source = "registry+https://github.com/rust-lang/crates.io-index" 628 | checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" 629 | dependencies = [ 630 | "same-file", 631 | "winapi-util", 632 | ] 633 | 634 | [[package]] 635 | name = "winapi" 636 | version = "0.3.9" 637 | source = "registry+https://github.com/rust-lang/crates.io-index" 638 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 639 | dependencies = [ 640 | "winapi-i686-pc-windows-gnu", 641 | "winapi-x86_64-pc-windows-gnu", 642 | ] 643 | 644 | [[package]] 645 | name = "winapi-i686-pc-windows-gnu" 646 | version = "0.4.0" 647 | source = "registry+https://github.com/rust-lang/crates.io-index" 648 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 649 | 650 | [[package]] 651 | name = "winapi-util" 652 | version = "0.1.9" 653 | source = "registry+https://github.com/rust-lang/crates.io-index" 654 | checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" 655 | dependencies = [ 656 | "windows-sys 0.59.0", 657 | ] 658 | 659 | [[package]] 660 | name = "winapi-x86_64-pc-windows-gnu" 661 | version = "0.4.0" 662 | source = "registry+https://github.com/rust-lang/crates.io-index" 663 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 664 | 665 | [[package]] 666 | name = "windows-sys" 667 | version = "0.48.0" 668 | source = "registry+https://github.com/rust-lang/crates.io-index" 669 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 670 | dependencies = [ 671 | "windows-targets 0.48.5", 672 | ] 673 | 674 | [[package]] 675 | name = "windows-sys" 676 | version = "0.52.0" 677 | source = "registry+https://github.com/rust-lang/crates.io-index" 678 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 679 | dependencies = [ 680 | "windows-targets 0.52.6", 681 | ] 682 | 683 | [[package]] 684 | name = "windows-sys" 685 | version = "0.59.0" 686 | source = "registry+https://github.com/rust-lang/crates.io-index" 687 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 688 | dependencies = [ 689 | "windows-targets 0.52.6", 690 | ] 691 | 692 | [[package]] 693 | name = "windows-targets" 694 | version = "0.48.5" 695 | source = "registry+https://github.com/rust-lang/crates.io-index" 696 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 697 | dependencies = [ 698 | "windows_aarch64_gnullvm 0.48.5", 699 | "windows_aarch64_msvc 0.48.5", 700 | "windows_i686_gnu 0.48.5", 701 | "windows_i686_msvc 0.48.5", 702 | "windows_x86_64_gnu 0.48.5", 703 | "windows_x86_64_gnullvm 0.48.5", 704 | "windows_x86_64_msvc 0.48.5", 705 | ] 706 | 707 | [[package]] 708 | name = "windows-targets" 709 | version = "0.52.6" 710 | source = "registry+https://github.com/rust-lang/crates.io-index" 711 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 712 | dependencies = [ 713 | "windows_aarch64_gnullvm 0.52.6", 714 | "windows_aarch64_msvc 0.52.6", 715 | "windows_i686_gnu 0.52.6", 716 | "windows_i686_gnullvm", 717 | "windows_i686_msvc 0.52.6", 718 | "windows_x86_64_gnu 0.52.6", 719 | "windows_x86_64_gnullvm 0.52.6", 720 | "windows_x86_64_msvc 0.52.6", 721 | ] 722 | 723 | [[package]] 724 | name = "windows_aarch64_gnullvm" 725 | version = "0.48.5" 726 | source = "registry+https://github.com/rust-lang/crates.io-index" 727 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 728 | 729 | [[package]] 730 | name = "windows_aarch64_gnullvm" 731 | version = "0.52.6" 732 | source = "registry+https://github.com/rust-lang/crates.io-index" 733 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 734 | 735 | [[package]] 736 | name = "windows_aarch64_msvc" 737 | version = "0.48.5" 738 | source = "registry+https://github.com/rust-lang/crates.io-index" 739 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 740 | 741 | [[package]] 742 | name = "windows_aarch64_msvc" 743 | version = "0.52.6" 744 | source = "registry+https://github.com/rust-lang/crates.io-index" 745 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 746 | 747 | [[package]] 748 | name = "windows_i686_gnu" 749 | version = "0.48.5" 750 | source = "registry+https://github.com/rust-lang/crates.io-index" 751 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 752 | 753 | [[package]] 754 | name = "windows_i686_gnu" 755 | version = "0.52.6" 756 | source = "registry+https://github.com/rust-lang/crates.io-index" 757 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 758 | 759 | [[package]] 760 | name = "windows_i686_gnullvm" 761 | version = "0.52.6" 762 | source = "registry+https://github.com/rust-lang/crates.io-index" 763 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 764 | 765 | [[package]] 766 | name = "windows_i686_msvc" 767 | version = "0.48.5" 768 | source = "registry+https://github.com/rust-lang/crates.io-index" 769 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 770 | 771 | [[package]] 772 | name = "windows_i686_msvc" 773 | version = "0.52.6" 774 | source = "registry+https://github.com/rust-lang/crates.io-index" 775 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 776 | 777 | [[package]] 778 | name = "windows_x86_64_gnu" 779 | version = "0.48.5" 780 | source = "registry+https://github.com/rust-lang/crates.io-index" 781 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 782 | 783 | [[package]] 784 | name = "windows_x86_64_gnu" 785 | version = "0.52.6" 786 | source = "registry+https://github.com/rust-lang/crates.io-index" 787 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 788 | 789 | [[package]] 790 | name = "windows_x86_64_gnullvm" 791 | version = "0.48.5" 792 | source = "registry+https://github.com/rust-lang/crates.io-index" 793 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 794 | 795 | [[package]] 796 | name = "windows_x86_64_gnullvm" 797 | version = "0.52.6" 798 | source = "registry+https://github.com/rust-lang/crates.io-index" 799 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 800 | 801 | [[package]] 802 | name = "windows_x86_64_msvc" 803 | version = "0.48.5" 804 | source = "registry+https://github.com/rust-lang/crates.io-index" 805 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 806 | 807 | [[package]] 808 | name = "windows_x86_64_msvc" 809 | version = "0.52.6" 810 | source = "registry+https://github.com/rust-lang/crates.io-index" 811 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 812 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "mask", 4 | "mask-parser", 5 | ] 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Jacob Deichert 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 14 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 15 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 16 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 17 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 18 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE 19 | OR OTHER DEALINGS IN THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | 6 | [![build status](https://github.com/jacobdeichert/mask/actions/workflows/ci.yml/badge.svg?branch=master)][github_ci] 7 | [![mask version](https://img.shields.io/crates/v/mask.svg)][crate] 8 | [![mask crate downloads](https://img.shields.io/crates/d/mask.svg)][crate] 9 | 10 | `mask` is a CLI task runner which is defined by a simple markdown file. It searches for a `maskfile.md` in the current directory which it then parses for commands and arguments. 11 | 12 | A `maskfile.md` is both a **human-readable document** and a **command definition**! Being documentation focused allows others to easily get started with your project's development setup by simply reading your `maskfile.md`. A nice advantage of using markdown is that syntax highlighting for code blocks is built-in to many editors and renderers like GitHub itself. 13 | 14 | Here's the [maskfile.md](/maskfile.md) that `mask` itself uses as an example! 15 | 16 | To get started, follow the guide below or check out the more [advanced features](#features) `mask` has like **positional args**, **optional flags**, **subcommands**, other **scripting runtimes** and more! 17 | 18 | 19 | 20 | 21 | 22 | ## Installation 23 | 24 | ### Precompiled binaries 25 | 26 | Head to the [Releases page][releases] and look for the latest published version. Under **Assets** you'll see zips available for download for linux, macOS and Windows. Once downloaded, you can unzip them and then move the `mask` binary to somewhere accessible in your `$PATH` like `mv mask /usr/local/bin`. 27 | 28 | ### Homebrew 29 | 30 | `mask` is available in [Homebrew][homebrew] which allows you to install it via `brew install mask`. 31 | 32 | ### Cargo 33 | 34 | `mask` is published to [crates.io][crate] which allows you to install it via `cargo install mask`. 35 | 36 | ### From source 37 | 38 | If you prefer to build from source, clone this repo and then run `cargo build --release` 39 | 40 | 41 | 42 | 43 | 44 | ## Getting started 45 | 46 | First, define a simple `maskfile.md` in your project. 47 | 48 | ```markdown 49 | # Tasks For My Project 50 | 51 | 52 | 53 | ## build 54 | 55 | 56 | > Builds my project 57 | 58 | 59 | ~~~sh 60 | echo "building project..." 61 | ~~~ 62 | 63 | 64 | ## test 65 | 66 | > Tests my project 67 | 68 | You can also write documentation anywhere you want. Only certain types of markdown patterns 69 | are parsed to determine the command structure. 70 | 71 | This code block below is defined as js which means it will be ran with node. Mask also 72 | supports other scripting runtimes including python, ruby and php! 73 | 74 | ~~~js 75 | console.log("running tests...") 76 | ~~~ 77 | ``` 78 | 79 | Then, try running one of your commands! 80 | 81 | ~~~sh 82 | mask build 83 | mask test 84 | ~~~ 85 | 86 | 87 | 88 | 89 | 90 | ## Features 91 | 92 | ### Positional arguments 93 | 94 | These are defined beside the command name within `(round_brackets)`. They are required arguments that must be supplied for the command to run. The argument name is injected into the script's scope as an environment variable. 95 | 96 | **Example:** 97 | 98 | ```markdown 99 | ## test (file) (test_case) 100 | 101 | > Run tests 102 | 103 | ~~~bash 104 | echo "Testing $test_case in $file" 105 | ~~~ 106 | ``` 107 | 108 | Optional arguments are defined within `[square_brackets]`. 109 | 110 | **Example:** 111 | 112 | ```markdown 113 | ## test [test_file] 114 | 115 | > Run tests 116 | 117 | ~~~bash 118 | if [[ -n "$test_file" ]]; then 119 | echo "Run tests in $test_file..." 120 | else 121 | echo "Running all tests...." 122 | fi 123 | ~~~ 124 | ``` 125 | 126 | ### Named flags 127 | 128 | You can define a list of named flags for your commands. The flag name is injected into the script's scope as an environment variable. 129 | 130 | **Example:** 131 | 132 | ```markdown 133 | ## serve 134 | 135 | > Serve this directory 136 | 137 | 138 | **OPTIONS** 139 | * port 140 | * flags: -p --port 141 | * type: string 142 | * desc: Which port to serve on 143 | 144 | ~~~sh 145 | PORT=${port:-8080} # Set a fallback port if not supplied 146 | 147 | if [[ "$verbose" == "true" ]]; then 148 | echo "Starting an http server on PORT: $PORT" 149 | fi 150 | python -m SimpleHTTPServer $PORT 151 | ~~~ 152 | ``` 153 | 154 | You can also make your flag expect a numerical value by setting its `type` to `number`. This means `mask` will automatically validate it as a number for you. If it fails to validate, `mask` will exit with a helpful error message. 155 | 156 | **Example:** 157 | 158 | ```markdown 159 | ## purchase (price) 160 | 161 | > Calculate the total price of something. 162 | 163 | **OPTIONS** 164 | * tax 165 | * flags: -t --tax 166 | * type: number 167 | * desc: What's the tax? 168 | 169 | ~~~sh 170 | TAX=${tax:-1} # Fallback to 1 if not supplied 171 | echo "Total: $(($price * $TAX))" 172 | ~~~ 173 | ``` 174 | 175 | If you add a `choices` list, `mask` will validate if the flag value is one of them. 176 | 177 | **Example:** 178 | 179 | ```markdown 180 | ## color 181 | 182 | **OPTIONS** 183 | * color 184 | * flags: -c --color 185 | * type: string 186 | * choices: RED, BLUE, GREEN 187 | 188 | ~~~bash 189 | COLOR=${color:-RED} # Fallback to RED if not supplied 190 | echo "Color selected = '$COLOR'" 191 | ~~~ 192 | ``` 193 | 194 | If you exclude the `type` field, `mask` will treat it as a `boolean` flag. If the flag is passed, its environment variable will be `"true"`, otherwise it will be unset/non-existent. 195 | 196 | Important to note that `mask` auto injects a very common `boolean` flag called `verbose` into every single command even if it's not used, which saves a bit of typing for you. This means every command implicitly has a `-v` and `--verbose` flag already. 197 | 198 | **Example:** 199 | 200 | ```markdown 201 | ## test 202 | 203 | > Run the test suite 204 | 205 | **OPTIONS** 206 | * watch 207 | * flags: -w --watch 208 | * desc: Run tests on file change 209 | 210 | ~~~bash 211 | [[ "$watch" == "true" ]] && echo "Starting in watch mode..." 212 | [[ "$verbose" == "true" ]] && echo "Running with extra logs..." 213 | ~~~ 214 | ``` 215 | 216 | Flags are optional by default. If you add `required` to your flag definition, `mask` will error if it isn't supplied by the user. 217 | 218 | **Example:** 219 | 220 | ```markdown 221 | ## ping 222 | 223 | **OPTIONS** 224 | * domain 225 | * flags: -d --domain 226 | * type: string 227 | * desc: Which domain to ping 228 | * required 229 | 230 | ~~~sh 231 | ping $domain 232 | ~~~ 233 | ``` 234 | 235 | ### Subcommands 236 | 237 | Nested command structures can easily be created since they are simply defined by the level of markdown heading. H2 (`##`) is where you define your top-level commands. Every level after that is a subcommand. 238 | 239 | **Example:** 240 | ```markdown 241 | ## services 242 | 243 | > Commands related to starting and stopping services 244 | 245 | ### services start (service_name) 246 | 247 | > Start a service. 248 | 249 | ~~~bash 250 | echo "Starting service $service_name" 251 | ~~~ 252 | 253 | ### services stop (service_name) 254 | 255 | > Stop a service. 256 | 257 | ~~~bash 258 | echo "Stopping service $service_name" 259 | ~~~ 260 | ``` 261 | 262 | You may notice above that the `start` and `stop` commands are prefixed with their parent command `services`. Prefixing subcommands with their ancestor commands may help readability in some cases, however, it is completely optional. The example below is the same as above, but without prefixing. 263 | 264 | **Example:** 265 | ```markdown 266 | ## services 267 | 268 | > Commands related to starting and stopping services 269 | 270 | ### start (service_name) 271 | 272 | > Start a service. 273 | 274 | ~~~bash 275 | echo "Starting service $service_name" 276 | ~~~ 277 | 278 | ### stop (service_name) 279 | 280 | > Stop a service. 281 | 282 | ~~~bash 283 | echo "Stopping service $service_name" 284 | ~~~ 285 | ``` 286 | 287 | ### Support for other scripting runtimes 288 | 289 | On top of shell/bash scripts, `mask` also supports using node, python, ruby and php as scripting runtimes. This gives you the freedom to choose the right tool for the specific task at hand. For example, let's say you have a `serve` command and a `snapshot` command. You could choose python to `serve` a simple directory and maybe node to run a puppeteer script that generates a png `snapshot` of each page. 290 | 291 | **Example:** 292 | 293 | ```markdown 294 | ## shell (name) 295 | 296 | > An example shell script 297 | 298 | Valid lang codes: sh, bash, zsh, fish... any shell that supports -c 299 | 300 | ~~~zsh 301 | echo "Hello, $name!" 302 | ~~~ 303 | 304 | 305 | ## node (name) 306 | 307 | > An example node script 308 | 309 | Valid lang codes: js, javascript 310 | 311 | ~~~js 312 | const { name } = process.env; 313 | console.log(`Hello, ${name}!`); 314 | ~~~ 315 | 316 | 317 | ## python (name) 318 | 319 | > An example python script 320 | 321 | Valid lang codes: py, python 322 | 323 | ~~~python 324 | import os 325 | name = os.getenv("name", "WORLD") 326 | print("Hello, " + name + "!") 327 | ~~~ 328 | 329 | 330 | ## ruby (name) 331 | 332 | > An example ruby script 333 | 334 | Valid lang codes: rb, ruby 335 | 336 | ~~~ruby 337 | name = ENV["name"] || "WORLD" 338 | puts "Hello, #{name}!" 339 | ~~~ 340 | 341 | 342 | ## php (name) 343 | 344 | > An example php script 345 | 346 | ~~~php 347 | $name = getenv("name") ?: "WORLD"; 348 | echo "Hello, " . $name . "!\n"; 349 | ~~~ 350 | ``` 351 | 352 | #### Windows support 353 | 354 | You can even add powershell or batch code blocks alongside linux/macOS ones. Depending on which platform this runs on, the correct code block will be executed. 355 | 356 | **Example:** 357 | 358 | ```markdown 359 | ## link 360 | 361 | > Build and link the binary globally 362 | 363 | ~~~bash 364 | cargo install --force --path . 365 | ~~~ 366 | 367 | ~~~powershell 368 | [Diagnostics.Process]::Start("cargo", "install --force --path .").WaitForExit() 369 | ~~~ 370 | ``` 371 | 372 | ### Automatic help and usage output 373 | 374 | You don't have to spend time writing out help info manually. `mask` uses your command descriptions and options to automatically generate help output. For every command, it adds `-h, --help` flags and an alternative `help ` command. 375 | 376 | **Example:** 377 | ~~~sh 378 | mask services start -h 379 | mask services start --help 380 | mask services help start 381 | mask help services start 382 | ~~~ 383 | 384 | All output the same help info: 385 | 386 | ~~~txt 387 | mask-services-start 388 | Start or restart a service. 389 | 390 | USAGE: 391 | mask services start [FLAGS] 392 | 393 | FLAGS: 394 | -h, --help Prints help information 395 | -V, --version Prints version information 396 | -v, --verbose Sets the level of verbosity 397 | -r, --restart Restart this service if it's already running 398 | -w, --watch Restart a service on file change 399 | 400 | ARGS: 401 | 402 | ~~~ 403 | 404 | ### Running mask from within a script 405 | 406 | You can easily call `mask` within scripts if you need to chain commands together. However, if you plan on [running mask with a different maskfile](#running-mask-with-a-different-maskfile), you should consider using the `$MASK` utility instead which allows your scripts to be location-agnostic. 407 | 408 | **Example:** 409 | 410 | ```markdown 411 | ## bootstrap 412 | 413 | > Installs deps, builds, links, migrates the db and then starts the app 414 | 415 | ~~~sh 416 | mask install 417 | mask build 418 | mask link 419 | # $MASK also works. It's an alias variable for `mask --maskfile ` 420 | # which guarantees your scripts will still work even if they are called from 421 | # another directory. 422 | $MASK db migrate 423 | $MASK start 424 | ~~~ 425 | ``` 426 | 427 | ### Inherits the script's exit code 428 | 429 | If your command exits with an error, `mask` will exit with its status code. This allows you to chain commands which will exit on the first error. 430 | 431 | **Example:** 432 | 433 | ```markdown 434 | ## ci 435 | 436 | > Runs tests and checks for lint and formatting errors 437 | 438 | ~~~sh 439 | mask test \ 440 | && mask lint \ 441 | && mask format --check 442 | ~~~ 443 | ``` 444 | 445 | ### Running mask with a different maskfile 446 | 447 | If you're in a directory that doesn't have a `maskfile.md` but you want to reference one somewhere else, you can with the `--maskfile ` option. 448 | 449 | **Example:** 450 | 451 | ~~~sh 452 | mask --maskfile ~/maskfile.md 453 | ~~~ 454 | 455 | **Tip:** Make a bash alias for this so you can call it anywhere easily 456 | 457 | ~~~bash 458 | # Call it something fun 459 | alias wask="mask --maskfile ~/maskfile.md" 460 | 461 | # You can run this from anywhere 462 | wask 463 | ~~~ 464 | 465 | ### Environment variable utilities 466 | 467 | Inside of each script's execution environment, `mask` injects a few environment variable helpers that might come in handy. 468 | 469 | **`$MASK`** 470 | 471 | This is useful when [running mask within a script](#running-mask-from-within-a-script). This variable allows us to call `$MASK command` instead of `mask --maskfile command` inside scripts so that they can be location-agnostic (not care where they are called from). This is especially handy for global maskfiles which you may call from anywhere. 472 | 473 | **`$MASKFILE_DIR`** 474 | 475 | This variable is an absolute path to the maskfile's parent directory. Having the parent directory available allows us to load files relative to the maskfile itself which can be useful when you have commands that depend on other external files. 476 | 477 | ### Documentation sections 478 | 479 | If a heading doesn't have a code block, it will be treated as documentation and completely ignored. 480 | 481 | **Example:** 482 | 483 | ~~~markdown 484 | ## This is a heading with no script 485 | 486 | It's useful as a place to document things like a setup guide or required dependencies 487 | or tools that your commands may rely on. 488 | ~~~ 489 | 490 | 491 | 492 | 493 | 494 | ## Use cases 495 | 496 | Here's some example scenarios where `mask` might be handy. 497 | 498 | ### Project specific tasks 499 | 500 | You have a project with a bunch of random build and development scripts or an unwieldy `Makefile`. You want to simplify by having a single, readable file for your team members to add and modify existing tasks. 501 | 502 | 503 | ### Global system utility 504 | 505 | You want a global utility CLI for a variety of system tasks such as backing up directories or renaming a bunch of files. This is easily possible by making a bash alias for `mask --maskfile ~/my-global-maskfile.md`. 506 | 507 | 508 | 509 | 510 | 511 | ## FAQ 512 | 513 | ### Is `mask` available as a lib? 514 | 515 | The [mask-parser][mask_parser] crate is available. However, it's not yet documented nor considered stable. 516 | 517 | ### Where did the inspiration come from? 518 | 519 | I'm definitely not the first to come up with this idea of using markdown as a CLI structure definition. 520 | 521 | My frustrations with `make`'s syntax is what led me to search for other options. I landed on [just][just] for awhile which was a pretty nice improvement. My favourite feature of `just` is its support for other language runtimes, which is why `mask` also has this ability! However, it still didn't have some features I wanted like nested subcommands and multiple optional flags. 522 | 523 | At some point in my searching, I came across [maid][maid] which is where most of the inspiration for `mask` comes from. I thought it was brilliant that markdown could be used as a command definition format while still being so readable. 524 | 525 | So why did I choose to rebuild the wheel instead of using `maid`? For one, I preferred installing a single binary, like `just` is, rather than installing an npm package with hundreds of deps. I also had a few ideas on how I could improve upon `maid` which is why `mask` supports multiple levels of nested subcommands as well as optional flags and positional args. Also... I just really wanted to build another thing with Rust :) 526 | 527 | I also need to mention [clap][clap] and [pulldown-cmark][cmark] which are really the core parts of `mask` that made it so easy to create. 528 | 529 | 530 | 531 | 532 | 533 | ## Contributing 534 | 535 | Check out our [Contribution Guidelines](CONTRIBUTING.md) before creating an issue or submitting a PR 🙌 536 | 537 | Also, please review and follow the rules within our [Code of Conduct](CODE_OF_CONDUCT.md) 🙂 538 | 539 | 540 | 541 | 542 | 543 | ## Author 544 | 545 | Jacob Deichert with the help of contributors. 546 | 547 | 548 | 549 | 550 | 551 | [github_ci]: https://github.com/jacobdeichert/mask/actions?query=workflow%3ACI 552 | [mask_parser]: https://crates.io/crates/mask-parser 553 | [homebrew]: https://formulae.brew.sh/formula/mask 554 | [crate]: https://crates.io/crates/mask 555 | [releases]: https://github.com/jacobdeichert/mask/releases 556 | [2]: https://github.com/jacobdeichert/mask/issues/5 557 | [maid]: https://github.com/egoist/maid 558 | [just]: https://github.com/casey/just 559 | [clap]: https://github.com/clap-rs/clap 560 | [cmark]: https://github.com/raphlinus/pulldown-cmark 561 | -------------------------------------------------------------------------------- /mask-parser/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mask-parser" 3 | version = "0.2.2" 4 | description = "A parser for the maskfile.md format" 5 | authors = ["Jacob Deichert "] 6 | repository = "https://github.com/jacobdeichert/mask" 7 | keywords = ["cli", "task", "command", "maskfile", "markdown"] 8 | categories = ["command-line-interface", "command-line-utilities", "development-tools::build-utils", "parser-implementations"] 9 | edition = "2018" 10 | license = "MIT" 11 | 12 | [dependencies] 13 | pulldown-cmark = { version = "0.5", default-features = false } # https://github.com/raphlinus/pulldown-cmark 14 | serde = { version = "1.0", features = ["derive"] } # https://github.com/serde-rs/serde 15 | serde_json = "1.0" # https://github.com/serde-rs/json 16 | -------------------------------------------------------------------------------- /mask-parser/README.md: -------------------------------------------------------------------------------- 1 | # mask-parser 2 | 3 | A parser for the [maskfile.md][mask] format. 4 | 5 | This library is not yet stable and subject to change. 6 | 7 | [mask]: https://github.com/jacobdeichert/mask 8 | -------------------------------------------------------------------------------- /mask-parser/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod maskfile; 2 | mod parser; 3 | 4 | pub use parser::parse; 5 | -------------------------------------------------------------------------------- /mask-parser/src/maskfile.rs: -------------------------------------------------------------------------------- 1 | use serde::Serialize; 2 | use serde_json::Value; 3 | 4 | #[derive(Debug, Serialize, Clone)] 5 | pub struct Maskfile { 6 | pub title: String, 7 | pub description: String, 8 | pub commands: Vec, 9 | } 10 | 11 | impl Maskfile { 12 | pub fn to_json(&self) -> Result { 13 | serde_json::to_value(&self) 14 | } 15 | } 16 | 17 | #[derive(Debug, Serialize, Clone)] 18 | pub struct Command { 19 | pub level: u8, 20 | pub name: String, 21 | pub description: String, 22 | pub script: Option