├── .github ├── dependabot.yml └── workflows │ ├── release.yml │ └── rust.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── License.md ├── Readme.md ├── dist-workspace.toml └── src ├── ansi_coloured_line.rs ├── base_line.rs ├── ecoji_line.rs ├── lib.rs ├── main.rs └── onepassword_line.rs /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "cargo" 4 | directory: "/" 5 | schedule: 6 | interval: "monthly" 7 | 8 | - package-ecosystem: "github-actions" 9 | directory: "/" 10 | schedule: 11 | # Check for updates to GitHub Actions every weekday 12 | interval: "monthly" 13 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # This file was autogenerated by dist: https://opensource.axo.dev/cargo-dist/ 2 | # 3 | # Copyright 2022-2024, axodotdev 4 | # SPDX-License-Identifier: MIT or Apache-2.0 5 | # 6 | # CI that: 7 | # 8 | # * checks for a Git Tag that looks like a release 9 | # * builds artifacts with dist (archives, installers, hashes) 10 | # * uploads those artifacts to temporary workflow zip 11 | # * on success, uploads the artifacts to a GitHub Release 12 | # 13 | # Note that the GitHub Release will be created with a generated 14 | # title/body based on your changelogs. 15 | 16 | name: Release 17 | permissions: 18 | "contents": "write" 19 | 20 | # This task will run whenever you push a git tag that looks like a version 21 | # like "1.0.0", "v0.1.0-prerelease.1", "my-app/0.1.0", "releases/v1.0.0", etc. 22 | # Various formats will be parsed into a VERSION and an optional PACKAGE_NAME, where 23 | # PACKAGE_NAME must be the name of a Cargo package in your workspace, and VERSION 24 | # must be a Cargo-style SemVer Version (must have at least major.minor.patch). 25 | # 26 | # If PACKAGE_NAME is specified, then the announcement will be for that 27 | # package (erroring out if it doesn't have the given version or isn't dist-able). 28 | # 29 | # If PACKAGE_NAME isn't specified, then the announcement will be for all 30 | # (dist-able) packages in the workspace with that version (this mode is 31 | # intended for workspaces with only one dist-able package, or with all dist-able 32 | # packages versioned/released in lockstep). 33 | # 34 | # If you push multiple tags at once, separate instances of this workflow will 35 | # spin up, creating an independent announcement for each one. However, GitHub 36 | # will hard limit this to 3 tags per commit, as it will assume more tags is a 37 | # mistake. 38 | # 39 | # If there's a prerelease-style suffix to the version, then the release(s) 40 | # will be marked as a prerelease. 41 | on: 42 | pull_request: 43 | push: 44 | tags: 45 | - '**[0-9]+.[0-9]+.[0-9]+*' 46 | 47 | jobs: 48 | # Run 'dist plan' (or host) to determine what tasks we need to do 49 | plan: 50 | runs-on: "ubuntu-22.04" 51 | outputs: 52 | val: ${{ steps.plan.outputs.manifest }} 53 | tag: ${{ !github.event.pull_request && github.ref_name || '' }} 54 | tag-flag: ${{ !github.event.pull_request && format('--tag={0}', github.ref_name) || '' }} 55 | publishing: ${{ !github.event.pull_request }} 56 | env: 57 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 58 | steps: 59 | - uses: actions/checkout@v4 60 | with: 61 | submodules: recursive 62 | - name: Install Rust 63 | run: rustup update "1.67.1" --no-self-update && rustup default "1.67.1" 64 | - name: Install dist 65 | # we specify bash to get pipefail; it guards against the `curl` command 66 | # failing. otherwise `sh` won't catch that `curl` returned non-0 67 | shell: bash 68 | run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.28.0/cargo-dist-installer.sh | sh" 69 | - name: Cache dist 70 | uses: actions/upload-artifact@v4 71 | with: 72 | name: cargo-dist-cache 73 | path: ~/.cargo/bin/dist 74 | # sure would be cool if github gave us proper conditionals... 75 | # so here's a doubly-nested ternary-via-truthiness to try to provide the best possible 76 | # functionality based on whether this is a pull_request, and whether it's from a fork. 77 | # (PRs run on the *source* but secrets are usually on the *target* -- that's *good* 78 | # but also really annoying to build CI around when it needs secrets to work right.) 79 | - id: plan 80 | run: | 81 | dist ${{ (!github.event.pull_request && format('host --steps=create --tag={0}', github.ref_name)) || 'plan' }} --output-format=json > plan-dist-manifest.json 82 | echo "dist ran successfully" 83 | cat plan-dist-manifest.json 84 | echo "manifest=$(jq -c "." plan-dist-manifest.json)" >> "$GITHUB_OUTPUT" 85 | - name: "Upload dist-manifest.json" 86 | uses: actions/upload-artifact@v4 87 | with: 88 | name: artifacts-plan-dist-manifest 89 | path: plan-dist-manifest.json 90 | 91 | # Build and packages all the platform-specific things 92 | build-local-artifacts: 93 | name: build-local-artifacts (${{ join(matrix.targets, ', ') }}) 94 | # Let the initial task tell us to not run (currently very blunt) 95 | needs: 96 | - plan 97 | if: ${{ fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix.include != null && (needs.plan.outputs.publishing == 'true' || fromJson(needs.plan.outputs.val).ci.github.pr_run_mode == 'upload') }} 98 | strategy: 99 | fail-fast: false 100 | # Target platforms/runners are computed by dist in create-release. 101 | # Each member of the matrix has the following arguments: 102 | # 103 | # - runner: the github runner 104 | # - dist-args: cli flags to pass to dist 105 | # - install-dist: expression to run to install dist on the runner 106 | # 107 | # Typically there will be: 108 | # - 1 "global" task that builds universal installers 109 | # - N "local" tasks that build each platform's binaries and platform-specific installers 110 | matrix: ${{ fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix }} 111 | runs-on: ${{ matrix.runner }} 112 | container: ${{ matrix.container && matrix.container.image || null }} 113 | env: 114 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 115 | BUILD_MANIFEST_NAME: target/distrib/${{ join(matrix.targets, '-') }}-dist-manifest.json 116 | steps: 117 | - name: enable windows longpaths 118 | run: | 119 | git config --global core.longpaths true 120 | - uses: actions/checkout@v4 121 | with: 122 | submodules: recursive 123 | - name: Install Rust non-interactively if not already installed 124 | if: ${{ matrix.container }} 125 | run: | 126 | if ! command -v cargo > /dev/null 2>&1; then 127 | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y 128 | echo "$HOME/.cargo/bin" >> $GITHUB_PATH 129 | fi 130 | - name: Use rustup to set correct Rust version 131 | run: rustup update "1.67.1" --no-self-update && rustup default "1.67.1" 132 | - name: Install dist 133 | run: ${{ matrix.install_dist.run }} 134 | # Get the dist-manifest 135 | - name: Fetch local artifacts 136 | uses: actions/download-artifact@v4 137 | with: 138 | pattern: artifacts-* 139 | path: target/distrib/ 140 | merge-multiple: true 141 | - name: Install dependencies 142 | run: | 143 | ${{ matrix.packages_install }} 144 | - name: Build artifacts 145 | run: | 146 | # Actually do builds and make zips and whatnot 147 | dist build ${{ needs.plan.outputs.tag-flag }} --print=linkage --output-format=json ${{ matrix.dist_args }} > dist-manifest.json 148 | echo "dist ran successfully" 149 | - id: cargo-dist 150 | name: Post-build 151 | # We force bash here just because github makes it really hard to get values up 152 | # to "real" actions without writing to env-vars, and writing to env-vars has 153 | # inconsistent syntax between shell and powershell. 154 | shell: bash 155 | run: | 156 | # Parse out what we just built and upload it to scratch storage 157 | echo "paths<> "$GITHUB_OUTPUT" 158 | dist print-upload-files-from-manifest --manifest dist-manifest.json >> "$GITHUB_OUTPUT" 159 | echo "EOF" >> "$GITHUB_OUTPUT" 160 | 161 | cp dist-manifest.json "$BUILD_MANIFEST_NAME" 162 | - name: "Upload artifacts" 163 | uses: actions/upload-artifact@v4 164 | with: 165 | name: artifacts-build-local-${{ join(matrix.targets, '_') }} 166 | path: | 167 | ${{ steps.cargo-dist.outputs.paths }} 168 | ${{ env.BUILD_MANIFEST_NAME }} 169 | 170 | # Build and package all the platform-agnostic(ish) things 171 | build-global-artifacts: 172 | needs: 173 | - plan 174 | - build-local-artifacts 175 | runs-on: "ubuntu-22.04" 176 | env: 177 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 178 | BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json 179 | steps: 180 | - uses: actions/checkout@v4 181 | with: 182 | submodules: recursive 183 | - name: Install Rust 184 | run: rustup update "1.67.1" --no-self-update && rustup default "1.67.1" 185 | - name: Install cached dist 186 | uses: actions/download-artifact@v4 187 | with: 188 | name: cargo-dist-cache 189 | path: ~/.cargo/bin/ 190 | - run: chmod +x ~/.cargo/bin/dist 191 | # Get all the local artifacts for the global tasks to use (for e.g. checksums) 192 | - name: Fetch local artifacts 193 | uses: actions/download-artifact@v4 194 | with: 195 | pattern: artifacts-* 196 | path: target/distrib/ 197 | merge-multiple: true 198 | - id: cargo-dist 199 | shell: bash 200 | run: | 201 | dist build ${{ needs.plan.outputs.tag-flag }} --output-format=json "--artifacts=global" > dist-manifest.json 202 | echo "dist ran successfully" 203 | 204 | # Parse out what we just built and upload it to scratch storage 205 | echo "paths<> "$GITHUB_OUTPUT" 206 | jq --raw-output ".upload_files[]" dist-manifest.json >> "$GITHUB_OUTPUT" 207 | echo "EOF" >> "$GITHUB_OUTPUT" 208 | 209 | cp dist-manifest.json "$BUILD_MANIFEST_NAME" 210 | - name: "Upload artifacts" 211 | uses: actions/upload-artifact@v4 212 | with: 213 | name: artifacts-build-global 214 | path: | 215 | ${{ steps.cargo-dist.outputs.paths }} 216 | ${{ env.BUILD_MANIFEST_NAME }} 217 | # Determines if we should publish/announce 218 | host: 219 | needs: 220 | - plan 221 | - build-local-artifacts 222 | - build-global-artifacts 223 | # Only run if we're "publishing", and only if local and global didn't fail (skipped is fine) 224 | if: ${{ always() && needs.plan.outputs.publishing == 'true' && (needs.build-global-artifacts.result == 'skipped' || needs.build-global-artifacts.result == 'success') && (needs.build-local-artifacts.result == 'skipped' || needs.build-local-artifacts.result == 'success') }} 225 | env: 226 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 227 | runs-on: "ubuntu-22.04" 228 | outputs: 229 | val: ${{ steps.host.outputs.manifest }} 230 | steps: 231 | - uses: actions/checkout@v4 232 | with: 233 | submodules: recursive 234 | - name: Install Rust 235 | run: rustup update "1.67.1" --no-self-update && rustup default "1.67.1" 236 | - name: Install cached dist 237 | uses: actions/download-artifact@v4 238 | with: 239 | name: cargo-dist-cache 240 | path: ~/.cargo/bin/ 241 | - run: chmod +x ~/.cargo/bin/dist 242 | # Fetch artifacts from scratch-storage 243 | - name: Fetch artifacts 244 | uses: actions/download-artifact@v4 245 | with: 246 | pattern: artifacts-* 247 | path: target/distrib/ 248 | merge-multiple: true 249 | - id: host 250 | shell: bash 251 | run: | 252 | dist host ${{ needs.plan.outputs.tag-flag }} --steps=upload --steps=release --output-format=json > dist-manifest.json 253 | echo "artifacts uploaded and released successfully" 254 | cat dist-manifest.json 255 | echo "manifest=$(jq -c "." dist-manifest.json)" >> "$GITHUB_OUTPUT" 256 | - name: "Upload dist-manifest.json" 257 | uses: actions/upload-artifact@v4 258 | with: 259 | # Overwrite the previous copy 260 | name: artifacts-dist-manifest 261 | path: dist-manifest.json 262 | # Create a GitHub Release while uploading all files to it 263 | - name: "Download GitHub Artifacts" 264 | uses: actions/download-artifact@v4 265 | with: 266 | pattern: artifacts-* 267 | path: artifacts 268 | merge-multiple: true 269 | - name: Cleanup 270 | run: | 271 | # Remove the granular manifests 272 | rm -f artifacts/*-dist-manifest.json 273 | - name: Create GitHub Release 274 | env: 275 | PRERELEASE_FLAG: "${{ fromJson(steps.host.outputs.manifest).announcement_is_prerelease && '--prerelease' || '' }}" 276 | ANNOUNCEMENT_TITLE: "${{ fromJson(steps.host.outputs.manifest).announcement_title }}" 277 | ANNOUNCEMENT_BODY: "${{ fromJson(steps.host.outputs.manifest).announcement_github_body }}" 278 | RELEASE_COMMIT: "${{ github.sha }}" 279 | run: | 280 | # Write and read notes from a file to avoid quoting breaking things 281 | echo "$ANNOUNCEMENT_BODY" > $RUNNER_TEMP/notes.txt 282 | 283 | gh release create "${{ needs.plan.outputs.tag }}" --target "$RELEASE_COMMIT" $PRERELEASE_FLAG --title "$ANNOUNCEMENT_TITLE" --notes-file "$RUNNER_TEMP/notes.txt" artifacts/* 284 | 285 | announce: 286 | needs: 287 | - plan 288 | - host 289 | # use "always() && ..." to allow us to wait for all publish jobs while 290 | # still allowing individual publish jobs to skip themselves (for prereleases). 291 | # "host" however must run to completion, no skipping allowed! 292 | if: ${{ always() && needs.host.result == 'success' }} 293 | runs-on: "ubuntu-22.04" 294 | env: 295 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 296 | steps: 297 | - uses: actions/checkout@v4 298 | with: 299 | submodules: recursive 300 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ develop ] 6 | pull_request: 7 | branches: [ develop ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v3 17 | - name: Build 18 | run: cargo build --verbose 19 | - name: Run tests 20 | run: cargo test --verbose 21 | - name: Install Clippy 22 | run: rustup component add clippy 23 | - name: Clippy 24 | uses: actions-rs/clippy-check@v1 25 | with: 26 | token: ${{ secrets.GITHUB_TOKEN }} 27 | args: --all-features 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | -------------------------------------------------------------------------------- /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 = "ansi_term" 7 | version = "0.12.1" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" 10 | dependencies = [ 11 | "winapi", 12 | ] 13 | 14 | [[package]] 15 | name = "anstream" 16 | version = "0.3.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" 19 | dependencies = [ 20 | "anstyle", 21 | "anstyle-parse", 22 | "anstyle-query", 23 | "anstyle-wincon", 24 | "colorchoice", 25 | "is-terminal", 26 | "utf8parse", 27 | ] 28 | 29 | [[package]] 30 | name = "anstyle" 31 | version = "1.0.1" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd" 34 | 35 | [[package]] 36 | name = "anstyle-parse" 37 | version = "0.2.1" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" 40 | dependencies = [ 41 | "utf8parse", 42 | ] 43 | 44 | [[package]] 45 | name = "anstyle-query" 46 | version = "1.0.0" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" 49 | dependencies = [ 50 | "windows-sys", 51 | ] 52 | 53 | [[package]] 54 | name = "anstyle-wincon" 55 | version = "1.0.1" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" 58 | dependencies = [ 59 | "anstyle", 60 | "windows-sys", 61 | ] 62 | 63 | [[package]] 64 | name = "autocfg" 65 | version = "0.1.2" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "a6d640bee2da49f60a4068a7fae53acde8982514ab7bae8b8cea9e88cbcfd799" 68 | 69 | [[package]] 70 | name = "autocfg" 71 | version = "1.1.0" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 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.3.3" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" 86 | 87 | [[package]] 88 | name = "cc" 89 | version = "1.0.81" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "6c6b2562119bf28c3439f7f02db99faf0aa1a8cdfe5772a2ee155d32227239f0" 92 | dependencies = [ 93 | "libc", 94 | ] 95 | 96 | [[package]] 97 | name = "cfg-if" 98 | version = "0.1.10" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 101 | 102 | [[package]] 103 | name = "cfg-if" 104 | version = "1.0.0" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 107 | 108 | [[package]] 109 | name = "clap" 110 | version = "4.3.19" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "5fd304a20bff958a57f04c4e96a2e7594cc4490a0e809cbd48bb6437edaa452d" 113 | dependencies = [ 114 | "clap_builder", 115 | "clap_derive", 116 | "once_cell", 117 | ] 118 | 119 | [[package]] 120 | name = "clap_builder" 121 | version = "4.3.19" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "01c6a3f08f1fe5662a35cfe393aec09c4df95f60ee93b7556505260f75eee9e1" 124 | dependencies = [ 125 | "anstream", 126 | "anstyle", 127 | "clap_lex", 128 | "strsim", 129 | ] 130 | 131 | [[package]] 132 | name = "clap_derive" 133 | version = "4.3.12" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "54a9bb5758fc5dfe728d1019941681eccaf0cf8a4189b692a0ee2f2ecf90a050" 136 | dependencies = [ 137 | "heck", 138 | "proc-macro2", 139 | "quote", 140 | "syn", 141 | ] 142 | 143 | [[package]] 144 | name = "clap_lex" 145 | version = "0.5.0" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" 148 | 149 | [[package]] 150 | name = "cloudabi" 151 | version = "0.0.3" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" 154 | dependencies = [ 155 | "bitflags 1.3.2", 156 | ] 157 | 158 | [[package]] 159 | name = "colorchoice" 160 | version = "1.0.0" 161 | source = "registry+https://github.com/rust-lang/crates.io-index" 162 | checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" 163 | 164 | [[package]] 165 | name = "coloursum" 166 | version = "0.3.0" 167 | dependencies = [ 168 | "ansi_term", 169 | "clap", 170 | "ecoji", 171 | "indoc", 172 | "itertools", 173 | "sysinfo", 174 | "which", 175 | ] 176 | 177 | [[package]] 178 | name = "crossbeam-channel" 179 | version = "0.5.8" 180 | source = "registry+https://github.com/rust-lang/crates.io-index" 181 | checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" 182 | dependencies = [ 183 | "cfg-if 1.0.0", 184 | "crossbeam-utils", 185 | ] 186 | 187 | [[package]] 188 | name = "crossbeam-deque" 189 | version = "0.8.3" 190 | source = "registry+https://github.com/rust-lang/crates.io-index" 191 | checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" 192 | dependencies = [ 193 | "cfg-if 1.0.0", 194 | "crossbeam-epoch", 195 | "crossbeam-utils", 196 | ] 197 | 198 | [[package]] 199 | name = "crossbeam-epoch" 200 | version = "0.9.15" 201 | source = "registry+https://github.com/rust-lang/crates.io-index" 202 | checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" 203 | dependencies = [ 204 | "autocfg 1.1.0", 205 | "cfg-if 1.0.0", 206 | "crossbeam-utils", 207 | "memoffset", 208 | "scopeguard", 209 | ] 210 | 211 | [[package]] 212 | name = "crossbeam-utils" 213 | version = "0.8.16" 214 | source = "registry+https://github.com/rust-lang/crates.io-index" 215 | checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" 216 | dependencies = [ 217 | "cfg-if 1.0.0", 218 | ] 219 | 220 | [[package]] 221 | name = "doc-comment" 222 | version = "0.3.3" 223 | source = "registry+https://github.com/rust-lang/crates.io-index" 224 | checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" 225 | 226 | [[package]] 227 | name = "ecoji" 228 | version = "1.0.0" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | checksum = "b445d0e84889cc97ea32e3c129d6f67022d2e4b13a46bd295b955db5d212d00b" 231 | dependencies = [ 232 | "phf", 233 | "phf_codegen", 234 | ] 235 | 236 | [[package]] 237 | name = "either" 238 | version = "1.5.1" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | checksum = "c67353c641dc847124ea1902d69bd753dee9bb3beff9aa3662ecf86c971d1fac" 241 | 242 | [[package]] 243 | name = "errno" 244 | version = "0.3.2" 245 | source = "registry+https://github.com/rust-lang/crates.io-index" 246 | checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" 247 | dependencies = [ 248 | "errno-dragonfly", 249 | "libc", 250 | "windows-sys", 251 | ] 252 | 253 | [[package]] 254 | name = "errno-dragonfly" 255 | version = "0.1.2" 256 | source = "registry+https://github.com/rust-lang/crates.io-index" 257 | checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" 258 | dependencies = [ 259 | "cc", 260 | "libc", 261 | ] 262 | 263 | [[package]] 264 | name = "fuchsia-cprng" 265 | version = "0.1.1" 266 | source = "registry+https://github.com/rust-lang/crates.io-index" 267 | checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" 268 | 269 | [[package]] 270 | name = "heck" 271 | version = "0.4.0" 272 | source = "registry+https://github.com/rust-lang/crates.io-index" 273 | checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" 274 | 275 | [[package]] 276 | name = "hermit-abi" 277 | version = "0.3.2" 278 | source = "registry+https://github.com/rust-lang/crates.io-index" 279 | checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" 280 | 281 | [[package]] 282 | name = "indoc" 283 | version = "2.0.3" 284 | source = "registry+https://github.com/rust-lang/crates.io-index" 285 | checksum = "2c785eefb63ebd0e33416dfcb8d6da0bf27ce752843a45632a67bf10d4d4b5c4" 286 | 287 | [[package]] 288 | name = "is-terminal" 289 | version = "0.4.9" 290 | source = "registry+https://github.com/rust-lang/crates.io-index" 291 | checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" 292 | dependencies = [ 293 | "hermit-abi", 294 | "rustix", 295 | "windows-sys", 296 | ] 297 | 298 | [[package]] 299 | name = "itertools" 300 | version = "0.10.5" 301 | source = "registry+https://github.com/rust-lang/crates.io-index" 302 | checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" 303 | dependencies = [ 304 | "either", 305 | ] 306 | 307 | [[package]] 308 | name = "libc" 309 | version = "0.2.147" 310 | source = "registry+https://github.com/rust-lang/crates.io-index" 311 | checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" 312 | 313 | [[package]] 314 | name = "linux-raw-sys" 315 | version = "0.4.5" 316 | source = "registry+https://github.com/rust-lang/crates.io-index" 317 | checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" 318 | 319 | [[package]] 320 | name = "memoffset" 321 | version = "0.9.0" 322 | source = "registry+https://github.com/rust-lang/crates.io-index" 323 | checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" 324 | dependencies = [ 325 | "autocfg 1.1.0", 326 | ] 327 | 328 | [[package]] 329 | name = "ntapi" 330 | version = "0.3.7" 331 | source = "registry+https://github.com/rust-lang/crates.io-index" 332 | checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f" 333 | dependencies = [ 334 | "winapi", 335 | ] 336 | 337 | [[package]] 338 | name = "num_cpus" 339 | version = "1.16.0" 340 | source = "registry+https://github.com/rust-lang/crates.io-index" 341 | checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" 342 | dependencies = [ 343 | "hermit-abi", 344 | "libc", 345 | ] 346 | 347 | [[package]] 348 | name = "once_cell" 349 | version = "1.18.0" 350 | source = "registry+https://github.com/rust-lang/crates.io-index" 351 | checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" 352 | 353 | [[package]] 354 | name = "phf" 355 | version = "0.7.24" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | checksum = "b3da44b85f8e8dfaec21adae67f95d93244b2ecf6ad2a692320598dcc8e6dd18" 358 | dependencies = [ 359 | "phf_shared", 360 | ] 361 | 362 | [[package]] 363 | name = "phf_codegen" 364 | version = "0.7.24" 365 | source = "registry+https://github.com/rust-lang/crates.io-index" 366 | checksum = "b03e85129e324ad4166b06b2c7491ae27fe3ec353af72e72cd1654c7225d517e" 367 | dependencies = [ 368 | "phf_generator", 369 | "phf_shared", 370 | ] 371 | 372 | [[package]] 373 | name = "phf_generator" 374 | version = "0.7.24" 375 | source = "registry+https://github.com/rust-lang/crates.io-index" 376 | checksum = "09364cc93c159b8b06b1f4dd8a4398984503483891b0c26b867cf431fb132662" 377 | dependencies = [ 378 | "phf_shared", 379 | "rand", 380 | ] 381 | 382 | [[package]] 383 | name = "phf_shared" 384 | version = "0.7.24" 385 | source = "registry+https://github.com/rust-lang/crates.io-index" 386 | checksum = "234f71a15de2288bcb7e3b6515828d22af7ec8598ee6d24c3b526fa0a80b67a0" 387 | dependencies = [ 388 | "siphasher", 389 | ] 390 | 391 | [[package]] 392 | name = "proc-macro2" 393 | version = "1.0.66" 394 | source = "registry+https://github.com/rust-lang/crates.io-index" 395 | checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" 396 | dependencies = [ 397 | "unicode-ident", 398 | ] 399 | 400 | [[package]] 401 | name = "quote" 402 | version = "1.0.32" 403 | source = "registry+https://github.com/rust-lang/crates.io-index" 404 | checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" 405 | dependencies = [ 406 | "proc-macro2", 407 | ] 408 | 409 | [[package]] 410 | name = "rand" 411 | version = "0.6.5" 412 | source = "registry+https://github.com/rust-lang/crates.io-index" 413 | checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" 414 | dependencies = [ 415 | "autocfg 0.1.2", 416 | "libc", 417 | "rand_chacha", 418 | "rand_core 0.4.2", 419 | "rand_hc", 420 | "rand_isaac", 421 | "rand_jitter", 422 | "rand_os", 423 | "rand_pcg", 424 | "rand_xorshift", 425 | "winapi", 426 | ] 427 | 428 | [[package]] 429 | name = "rand_chacha" 430 | version = "0.1.1" 431 | source = "registry+https://github.com/rust-lang/crates.io-index" 432 | checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" 433 | dependencies = [ 434 | "autocfg 0.1.2", 435 | "rand_core 0.3.1", 436 | ] 437 | 438 | [[package]] 439 | name = "rand_core" 440 | version = "0.3.1" 441 | source = "registry+https://github.com/rust-lang/crates.io-index" 442 | checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" 443 | dependencies = [ 444 | "rand_core 0.4.2", 445 | ] 446 | 447 | [[package]] 448 | name = "rand_core" 449 | version = "0.4.2" 450 | source = "registry+https://github.com/rust-lang/crates.io-index" 451 | checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" 452 | 453 | [[package]] 454 | name = "rand_hc" 455 | version = "0.1.0" 456 | source = "registry+https://github.com/rust-lang/crates.io-index" 457 | checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" 458 | dependencies = [ 459 | "rand_core 0.3.1", 460 | ] 461 | 462 | [[package]] 463 | name = "rand_isaac" 464 | version = "0.1.1" 465 | source = "registry+https://github.com/rust-lang/crates.io-index" 466 | checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" 467 | dependencies = [ 468 | "rand_core 0.3.1", 469 | ] 470 | 471 | [[package]] 472 | name = "rand_jitter" 473 | version = "0.1.3" 474 | source = "registry+https://github.com/rust-lang/crates.io-index" 475 | checksum = "7b9ea758282efe12823e0d952ddb269d2e1897227e464919a554f2a03ef1b832" 476 | dependencies = [ 477 | "libc", 478 | "rand_core 0.4.2", 479 | "winapi", 480 | ] 481 | 482 | [[package]] 483 | name = "rand_os" 484 | version = "0.1.3" 485 | source = "registry+https://github.com/rust-lang/crates.io-index" 486 | checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" 487 | dependencies = [ 488 | "cloudabi", 489 | "fuchsia-cprng", 490 | "libc", 491 | "rand_core 0.4.2", 492 | "rdrand", 493 | "winapi", 494 | ] 495 | 496 | [[package]] 497 | name = "rand_pcg" 498 | version = "0.1.2" 499 | source = "registry+https://github.com/rust-lang/crates.io-index" 500 | checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" 501 | dependencies = [ 502 | "autocfg 0.1.2", 503 | "rand_core 0.4.2", 504 | ] 505 | 506 | [[package]] 507 | name = "rand_xorshift" 508 | version = "0.1.1" 509 | source = "registry+https://github.com/rust-lang/crates.io-index" 510 | checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" 511 | dependencies = [ 512 | "rand_core 0.3.1", 513 | ] 514 | 515 | [[package]] 516 | name = "rayon" 517 | version = "1.7.0" 518 | source = "registry+https://github.com/rust-lang/crates.io-index" 519 | checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" 520 | dependencies = [ 521 | "either", 522 | "rayon-core", 523 | ] 524 | 525 | [[package]] 526 | name = "rayon-core" 527 | version = "1.11.0" 528 | source = "registry+https://github.com/rust-lang/crates.io-index" 529 | checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" 530 | dependencies = [ 531 | "crossbeam-channel", 532 | "crossbeam-deque", 533 | "crossbeam-utils", 534 | "num_cpus", 535 | ] 536 | 537 | [[package]] 538 | name = "rdrand" 539 | version = "0.4.0" 540 | source = "registry+https://github.com/rust-lang/crates.io-index" 541 | checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" 542 | dependencies = [ 543 | "rand_core 0.3.1", 544 | ] 545 | 546 | [[package]] 547 | name = "rustix" 548 | version = "0.38.6" 549 | source = "registry+https://github.com/rust-lang/crates.io-index" 550 | checksum = "1ee020b1716f0a80e2ace9b03441a749e402e86712f15f16fe8a8f75afac732f" 551 | dependencies = [ 552 | "bitflags 2.3.3", 553 | "errno", 554 | "libc", 555 | "linux-raw-sys", 556 | "windows-sys", 557 | ] 558 | 559 | [[package]] 560 | name = "scopeguard" 561 | version = "1.2.0" 562 | source = "registry+https://github.com/rust-lang/crates.io-index" 563 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 564 | 565 | [[package]] 566 | name = "siphasher" 567 | version = "0.2.3" 568 | source = "registry+https://github.com/rust-lang/crates.io-index" 569 | checksum = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac" 570 | 571 | [[package]] 572 | name = "strsim" 573 | version = "0.10.0" 574 | source = "registry+https://github.com/rust-lang/crates.io-index" 575 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 576 | 577 | [[package]] 578 | name = "syn" 579 | version = "2.0.28" 580 | source = "registry+https://github.com/rust-lang/crates.io-index" 581 | checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567" 582 | dependencies = [ 583 | "proc-macro2", 584 | "quote", 585 | "unicode-ident", 586 | ] 587 | 588 | [[package]] 589 | name = "sysinfo" 590 | version = "0.13.4" 591 | source = "registry+https://github.com/rust-lang/crates.io-index" 592 | checksum = "1cac193374347e7c263c5f547524f36ff8ec6702d56c8799c8331d26dffe8c1e" 593 | dependencies = [ 594 | "cfg-if 0.1.10", 595 | "doc-comment", 596 | "libc", 597 | "ntapi", 598 | "once_cell", 599 | "rayon", 600 | "winapi", 601 | ] 602 | 603 | [[package]] 604 | name = "unicode-ident" 605 | version = "1.0.11" 606 | source = "registry+https://github.com/rust-lang/crates.io-index" 607 | checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" 608 | 609 | [[package]] 610 | name = "utf8parse" 611 | version = "0.2.1" 612 | source = "registry+https://github.com/rust-lang/crates.io-index" 613 | checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" 614 | 615 | [[package]] 616 | name = "which" 617 | version = "3.1.1" 618 | source = "registry+https://github.com/rust-lang/crates.io-index" 619 | checksum = "d011071ae14a2f6671d0b74080ae0cd8ebf3a6f8c9589a2cd45f23126fe29724" 620 | dependencies = [ 621 | "libc", 622 | ] 623 | 624 | [[package]] 625 | name = "winapi" 626 | version = "0.3.9" 627 | source = "registry+https://github.com/rust-lang/crates.io-index" 628 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 629 | dependencies = [ 630 | "winapi-i686-pc-windows-gnu", 631 | "winapi-x86_64-pc-windows-gnu", 632 | ] 633 | 634 | [[package]] 635 | name = "winapi-i686-pc-windows-gnu" 636 | version = "0.4.0" 637 | source = "registry+https://github.com/rust-lang/crates.io-index" 638 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 639 | 640 | [[package]] 641 | name = "winapi-x86_64-pc-windows-gnu" 642 | version = "0.4.0" 643 | source = "registry+https://github.com/rust-lang/crates.io-index" 644 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 645 | 646 | [[package]] 647 | name = "windows-sys" 648 | version = "0.48.0" 649 | source = "registry+https://github.com/rust-lang/crates.io-index" 650 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 651 | dependencies = [ 652 | "windows-targets", 653 | ] 654 | 655 | [[package]] 656 | name = "windows-targets" 657 | version = "0.48.1" 658 | source = "registry+https://github.com/rust-lang/crates.io-index" 659 | checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" 660 | dependencies = [ 661 | "windows_aarch64_gnullvm", 662 | "windows_aarch64_msvc", 663 | "windows_i686_gnu", 664 | "windows_i686_msvc", 665 | "windows_x86_64_gnu", 666 | "windows_x86_64_gnullvm", 667 | "windows_x86_64_msvc", 668 | ] 669 | 670 | [[package]] 671 | name = "windows_aarch64_gnullvm" 672 | version = "0.48.0" 673 | source = "registry+https://github.com/rust-lang/crates.io-index" 674 | checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" 675 | 676 | [[package]] 677 | name = "windows_aarch64_msvc" 678 | version = "0.48.0" 679 | source = "registry+https://github.com/rust-lang/crates.io-index" 680 | checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" 681 | 682 | [[package]] 683 | name = "windows_i686_gnu" 684 | version = "0.48.0" 685 | source = "registry+https://github.com/rust-lang/crates.io-index" 686 | checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" 687 | 688 | [[package]] 689 | name = "windows_i686_msvc" 690 | version = "0.48.0" 691 | source = "registry+https://github.com/rust-lang/crates.io-index" 692 | checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" 693 | 694 | [[package]] 695 | name = "windows_x86_64_gnu" 696 | version = "0.48.0" 697 | source = "registry+https://github.com/rust-lang/crates.io-index" 698 | checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" 699 | 700 | [[package]] 701 | name = "windows_x86_64_gnullvm" 702 | version = "0.48.0" 703 | source = "registry+https://github.com/rust-lang/crates.io-index" 704 | checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" 705 | 706 | [[package]] 707 | name = "windows_x86_64_msvc" 708 | version = "0.48.0" 709 | source = "registry+https://github.com/rust-lang/crates.io-index" 710 | checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" 711 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "coloursum" 3 | description = "🎨 Colourise your checksum output" 4 | version = "0.3.0" 5 | authors = ["Jessica Stokes "] 6 | edition = "2021" 7 | repository = "https://github.com/ticky/coloursum" 8 | readme = "Readme.md" 9 | license = "MIT" 10 | 11 | [dependencies] 12 | ansi_term = "0.12.1" 13 | clap = { version = "4.0", features = ["derive"] } 14 | ecoji = "1.0.0" 15 | itertools = "0.10.5" 16 | 17 | [target.'cfg(unix)'.dependencies] 18 | sysinfo = "0.13.1" 19 | which = { version = "3.1.1", default-features = false } 20 | 21 | [dev-dependencies] 22 | indoc = "2.0.3" 23 | 24 | # The profile that 'cargo dist' will build with 25 | [profile.dist] 26 | inherits = "release" 27 | lto = "thin" 28 | -------------------------------------------------------------------------------- /License.md: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | 3 | **Copyright © 2019 Jessica Stokes** 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # ColourSum 2 | 3 | [![crates.io](https://img.shields.io/crates/v/coloursum.svg)](https://crates.io/crates/coloursum) [![Rust](https://github.com/ticky/coloursum/actions/workflows/rust.yml/badge.svg)](https://github.com/ticky/coloursum/actions/workflows/rust.yml) [![codecov](https://codecov.io/gh/ticky/coloursum/branch/develop/graph/badge.svg)](https://codecov.io/gh/ticky/coloursum) 4 | 5 | 🎨 Colourise your checksum output 6 | 7 | ## What is this? 8 | 9 | This is a utility into which you can pipe the output from various checksum generators, to get coloured output. 10 | 11 | It understands both the BSD "tag" form, as well as the GNU Coreutils/Perl `shasum(1)` form of checksums, and has been tested with the output from macOS' `md5` and `shasum`, as well as GNU `md5sum` and `sha256sum`. 12 | 13 | ## Installation 14 | 15 | You'll need [Rust installed and ready to go](https://www.rust-lang.org/tools/install). 16 | 17 | ```bash 18 | cargo install coloursum 19 | ``` 20 | 21 | ## Usage 22 | 23 | ```bash 24 | md5sum [file] | coloursum 25 | ``` 26 | 27 | Coloursum also prints full usage information if you run `coloursum --help`. 28 | 29 | ### Shell Integration 30 | 31 | You can also integrate coloursum into your shell, to output colourful checksums by default! 32 | 33 | By default, it will search for known checksum commands' presence, and generate shell functions for those which are found. 34 | 35 | If this behaviour is not acceptable, or your checksum command is not in the list, you can optionally specify a checksum command as the last argument to `coloursum shell-setup` to generate a shell function just for it. 36 | 37 | #### bash, zsh, and other similar shells 38 | 39 | Add this line to your ~/.bash_profile, ~/.zshrc or equivalent file: 40 | 41 | ```sh 42 | eval "$(coloursum --mode=1password shell-setup)" 43 | ``` 44 | 45 | #### fish shell 46 | 47 | Add this line to ~/.config/fish/config.fish: 48 | 49 | ```fish 50 | status --is-interactive; and coloursum --mode=ecoji shell-setup | source 51 | ``` 52 | -------------------------------------------------------------------------------- /dist-workspace.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["cargo:."] 3 | 4 | # Config for 'dist' 5 | [dist] 6 | # The preferred dist version to use in CI (Cargo.toml SemVer syntax) 7 | cargo-dist-version = "0.28.0" 8 | # The preferred Rust toolchain to use in CI (rustup toolchain syntax) 9 | rust-toolchain-version = "1.67.1" 10 | # CI backends to support 11 | ci = "github" 12 | # The installers to generate for each app 13 | installers = ["shell"] 14 | # Target platforms to build apps for (Rust target-triple syntax) 15 | targets = ["aarch64-apple-darwin", "x86_64-apple-darwin", "x86_64-unknown-linux-gnu", "x86_64-pc-windows-msvc"] 16 | # Path that installers should place binaries in 17 | install-path = "CARGO_HOME" 18 | # Whether to install an updater program 19 | install-updater = false 20 | 21 | [dist.github-custom-runners] 22 | global = "ubuntu-22.04" 23 | x86_64-unknown-linux-gnu = "ubuntu-22.04" 24 | -------------------------------------------------------------------------------- /src/ansi_coloured_line.rs: -------------------------------------------------------------------------------- 1 | use std::convert::From; 2 | use std::fmt; 3 | use std::fmt::Display; 4 | use std::iter::FromIterator; 5 | 6 | use ansi_term::Colour::Fixed; 7 | use itertools::Itertools; 8 | 9 | use crate::base_line::{FormattableLine, Line}; 10 | 11 | #[derive(Debug)] 12 | /// Line with naïve ANSI Colour code formatting. 13 | pub struct ANSIColouredLine(FormattableLine); 14 | impl From for ANSIColouredLine { 15 | fn from(contents: String) -> Self { 16 | Self(FormattableLine::from(contents)) 17 | } 18 | } 19 | 20 | impl Display for ANSIColouredLine { 21 | fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 22 | self.to_formatted(formatter) 23 | } 24 | } 25 | 26 | impl Line for ANSIColouredLine { 27 | fn get_line(&self) -> &FormattableLine { 28 | &self.0 29 | } 30 | 31 | /// Formats a base16-format hash or digest. 32 | /// 33 | /// Each 8-bit hexadecimal digit will be coloured 34 | /// with the corresponding xterm colour. 35 | fn format_hash(hash: String) -> String { 36 | use std::num::ParseIntError; 37 | 38 | // map over every two characters 39 | let result: Result = hash 40 | .chars() 41 | .chunks(2) 42 | .into_iter() 43 | .map(|byte| { 44 | let ord_string = String::from_iter(byte); 45 | // attempt to parse those two characters as a u8, 46 | // and if that works, colour them in 47 | u8::from_str_radix(&ord_string, 16) 48 | .map(|ordinal| Fixed(ordinal).paint(ord_string).to_string()) 49 | }) 50 | .collect(); 51 | 52 | // if there was an error at any point, return the original value 53 | result.unwrap_or(hash) 54 | } 55 | } 56 | 57 | #[cfg(test)] 58 | mod tests { 59 | #[test] 60 | fn display_works() { 61 | use super::ANSIColouredLine; 62 | 63 | assert_eq!( 64 | format!("{}", ANSIColouredLine::from("MD5 (./src/main.rs) = b7527e0e28c09f6f62dd2d4197d5d225".to_string())), 65 | "MD5 (./src/main.rs) = \u{1b}[38;5;183mb7\u{1b}[0m\u{1b}[38;5;82m52\u{1b}[0m\u{1b}[38;5;126m7e\u{1b}[0m\u{1b}[38;5;14m0e\u{1b}[0m\u{1b}[38;5;40m28\u{1b}[0m\u{1b}[38;5;192mc0\u{1b}[0m\u{1b}[38;5;159m9f\u{1b}[0m\u{1b}[38;5;111m6f\u{1b}[0m\u{1b}[38;5;98m62\u{1b}[0m\u{1b}[38;5;221mdd\u{1b}[0m\u{1b}[38;5;45m2d\u{1b}[0m\u{1b}[38;5;65m41\u{1b}[0m\u{1b}[38;5;151m97\u{1b}[0m\u{1b}[38;5;213md5\u{1b}[0m\u{1b}[38;5;210md2\u{1b}[0m\u{1b}[38;5;37m25\u{1b}[0m" 66 | ) 67 | } 68 | 69 | #[test] 70 | fn format_hash_works() { 71 | use super::ANSIColouredLine; 72 | use crate::Line; 73 | 74 | assert_eq!( 75 | ANSIColouredLine::format_hash( 76 | "b7527e0e28c09f6f62dd2d4197d5d225".to_string() 77 | ), 78 | "\u{1b}[38;5;183mb7\u{1b}[0m\u{1b}[38;5;82m52\u{1b}[0m\u{1b}[38;5;126m7e\u{1b}[0m\u{1b}[38;5;14m0e\u{1b}[0m\u{1b}[38;5;40m28\u{1b}[0m\u{1b}[38;5;192mc0\u{1b}[0m\u{1b}[38;5;159m9f\u{1b}[0m\u{1b}[38;5;111m6f\u{1b}[0m\u{1b}[38;5;98m62\u{1b}[0m\u{1b}[38;5;221mdd\u{1b}[0m\u{1b}[38;5;45m2d\u{1b}[0m\u{1b}[38;5;65m41\u{1b}[0m\u{1b}[38;5;151m97\u{1b}[0m\u{1b}[38;5;213md5\u{1b}[0m\u{1b}[38;5;210md2\u{1b}[0m\u{1b}[38;5;37m25\u{1b}[0m" 79 | ); 80 | } 81 | 82 | #[test] 83 | fn format_hash_doesnt_crash_on_non_base16_characters() { 84 | use super::ANSIColouredLine; 85 | use crate::Line; 86 | 87 | ANSIColouredLine::format_hash("ASDF".to_string()); 88 | ANSIColouredLine::format_hash("😄".to_string()); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/base_line.rs: -------------------------------------------------------------------------------- 1 | use std::convert::From; 2 | use std::fmt; 3 | use std::fmt::Display; 4 | use std::io; 5 | use std::io::{BufRead, Write}; 6 | 7 | #[derive(Debug)] 8 | /// Representation of the formattable contents of a line of console output. 9 | pub struct FormattableLine { 10 | contents: String, 11 | formattable_start: Option, 12 | formattable_end: Option, 13 | } 14 | 15 | impl From for FormattableLine { 16 | /// Converts a `String` to a `FormattableLine`. 17 | fn from(contents: String) -> Self { 18 | let mut formattable_start: Option = None; 19 | let mut formattable_end: Option = None; 20 | 21 | if let Some(suffix_start) = find_bsd_tag_line(&contents) { 22 | formattable_start = Some(suffix_start); 23 | } else if let Some(prefix_end) = find_sum_prefixed_line(&contents) { 24 | formattable_end = Some(prefix_end); 25 | } 26 | 27 | Self { 28 | contents, 29 | formattable_start, 30 | formattable_end, 31 | } 32 | } 33 | } 34 | 35 | /// Used to present a formattable line, which can be derived from a `String`. 36 | pub trait Line: Display + From { 37 | /// Formats the given checksum string. 38 | fn format_hash(hash: String) -> String; 39 | 40 | /// Retrieves the underlying `FormattableLine` object. 41 | fn get_line(&self) -> &FormattableLine; 42 | 43 | /// Takes each line in `from`, and writes it to `to`. 44 | /// 45 | /// If a given line is recognisable as the output of a 46 | /// hashing utility, its hash value will be coloured. 47 | fn coloursum(from: I, mut to: O) -> io::Result<()> { 48 | for wrapped_line in from.lines() { 49 | writeln!(to, "{}", Self::from(wrapped_line?))? 50 | } 51 | 52 | Ok(()) 53 | } 54 | 55 | /// Writes the processed line to the supplied `Formatter`. 56 | /// 57 | /// May be overridden in order to replace the checksum-replacing behaviour if necessary. 58 | fn to_formatted(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 59 | let line = self.get_line(); 60 | 61 | // Fall back to writing with no extra formatting 62 | // if we didn't detect a hash at any position 63 | if line.formattable_start.is_none() && line.formattable_end.is_none() { 64 | return write!(formatter, "{}", line.contents); 65 | } 66 | 67 | let slice_start = line.formattable_start.unwrap_or(0); 68 | let slice_end = line.formattable_end.unwrap_or(line.contents.len()); 69 | 70 | write!( 71 | formatter, 72 | "{}{}{}", 73 | &line.contents[..slice_start], 74 | Self::format_hash(line.contents[slice_start..slice_end].to_string()), 75 | &line.contents[slice_end..], 76 | ) 77 | } 78 | } 79 | 80 | /// Detects the *starting* offset of the 81 | /// hash in a BSD `md5(1)` style line 82 | fn find_bsd_tag_line(line: &str) -> Option { 83 | let needle = " = "; 84 | line.rfind(needle).map(|offset| offset + needle.len()) 85 | } 86 | 87 | /// Detects the *ending* offset of the hash in a 88 | /// GNU `md5sum(1)` / perl `shasum(1)` style line 89 | fn find_sum_prefixed_line(line: &str) -> Option { 90 | line.find(" ") 91 | } 92 | 93 | #[cfg(test)] 94 | mod tests { 95 | #[test] 96 | fn from_string_works() { 97 | use super::FormattableLine; 98 | 99 | let string = "MD5 (./src/main.rs) = b7527e0e28c09f6f62dd2d4197d5d225".to_string(); 100 | let line = FormattableLine::from(string.clone()); 101 | 102 | assert_eq!(line.contents, string); 103 | assert_eq!(line.formattable_start, Some(22)); 104 | assert_eq!(line.formattable_end, None); 105 | } 106 | 107 | #[test] 108 | fn find_bsd_tag_line_works() { 109 | use super::find_bsd_tag_line; 110 | 111 | assert_eq!( 112 | find_bsd_tag_line("MD5 (./src/main.rs) = b7527e0e28c09f6f62dd2d4197d5d225"), 113 | Some(22) 114 | ); 115 | assert_eq!( 116 | find_bsd_tag_line("b7527e0e28c09f6f62dd2d4197d5d225 ./src/main.rs"), 117 | None 118 | ); 119 | assert_eq!( 120 | find_bsd_tag_line( 121 | "3e08ba70bfc57da75612af458c7ea94108f9a9ddf9d1bfd96de9c0e34e684bda ./src/main.rs" 122 | ), 123 | None 124 | ); 125 | } 126 | 127 | #[test] 128 | fn find_sum_prefixed_line_works() { 129 | use super::find_sum_prefixed_line; 130 | 131 | assert_eq!( 132 | find_sum_prefixed_line("b7527e0e28c09f6f62dd2d4197d5d225 ./src/main.rs"), 133 | Some(32) 134 | ); 135 | assert_eq!( 136 | find_sum_prefixed_line( 137 | "3e08ba70bfc57da75612af458c7ea94108f9a9ddf9d1bfd96de9c0e34e684bda ./src/main.rs" 138 | ), 139 | Some(64) 140 | ); 141 | assert_eq!( 142 | find_sum_prefixed_line("MD5 (./src/main.rs) = b7527e0e28c09f6f62dd2d4197d5d225"), 143 | None 144 | ); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/ecoji_line.rs: -------------------------------------------------------------------------------- 1 | use std::convert::From; 2 | use std::fmt; 3 | use std::fmt::Display; 4 | use std::iter::FromIterator; 5 | 6 | use itertools::Itertools; 7 | 8 | use crate::base_line::{FormattableLine, Line}; 9 | 10 | #[derive(Debug)] 11 | /// Line with Ecoji base-1024 emoji encoding. 12 | pub struct EcojiLine(FormattableLine); 13 | impl From for EcojiLine { 14 | fn from(contents: String) -> Self { 15 | Self(FormattableLine::from(contents)) 16 | } 17 | } 18 | 19 | impl Display for EcojiLine { 20 | fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 21 | self.to_formatted(formatter) 22 | } 23 | } 24 | 25 | impl Line for EcojiLine { 26 | fn get_line(&self) -> &FormattableLine { 27 | &self.0 28 | } 29 | 30 | /// Formats a base16-format hash or digest. 31 | /// 32 | /// Data will be encoded using the Ecoji base-1024 emoji encoding. 33 | fn format_hash(hash: String) -> String { 34 | use std::num::ParseIntError; 35 | 36 | // map over every two characters 37 | let result: Result, ParseIntError> = hash 38 | .chars() 39 | .chunks(2) 40 | .into_iter() 41 | .map(|byte| { 42 | let ord_string = String::from_iter(byte); 43 | // attempt to parse those two characters as a u8 44 | u8::from_str_radix(&ord_string, 16) 45 | }) 46 | .collect(); 47 | 48 | match result { 49 | // if there was an error at any point, return the original value 50 | Err(_) => hash, 51 | // otherwise, encode with ecoji 52 | Ok(bytes) => ecoji::encode_to_string(&mut &*bytes).unwrap_or(hash), 53 | } 54 | } 55 | } 56 | 57 | #[cfg(test)] 58 | mod tests { 59 | #[test] 60 | fn display_works() { 61 | use super::EcojiLine; 62 | 63 | assert_eq!( 64 | format!( 65 | "{}", 66 | EcojiLine::from( 67 | "MD5 (./src/main.rs) = b7527e0e28c09f6f62dd2d4197d5d225".to_string() 68 | ) 69 | ), 70 | "MD5 (./src/main.rs) = 😨🏸🤰📺🙎📇🦎😨🍽🇮📆💣🍜☕☕☕" 71 | ) 72 | } 73 | 74 | #[test] 75 | fn format_hash_works() { 76 | use super::EcojiLine; 77 | use crate::Line; 78 | 79 | assert_eq!( 80 | EcojiLine::format_hash("cc6917b830dae305766d1d72d7bd9fdc673272b2".to_string()), 81 | "🚭🕹💿🈳🤘🔃🐮🕋🌽🚩📀👰🤞🕒🍤🗽" 82 | ); 83 | } 84 | 85 | #[test] 86 | fn format_hash_doesnt_crash_on_non_base16_characters() { 87 | use super::EcojiLine; 88 | use crate::Line; 89 | 90 | EcojiLine::format_hash("ASDF".to_string()); 91 | EcojiLine::format_hash("😄".to_string()); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! 🎨 Colourise your checksum output 2 | //! 3 | //! Coloursum provides several line formatters capable of being produced from 4 | //! a line of various checksum generators' output, in order to colourise or 5 | //! otherwise transform their checksum in order to improve readability. 6 | //! 7 | //! ## Formatting one line with the `Line` trait 8 | //! 9 | //! Types implementing `Line` understand both the BSD "tag" form, as well as 10 | //! the GNU Coreutils/Perl `shasum(1)` form of checksums, and have been tested 11 | //! with the output from macOS' `md5`, `shasum`, as well as GNU `md5sum` 12 | //! and `sha256sum`. 13 | //! 14 | //! They emit their formatted contents when `Display`ed to a user, with 15 | //! macros like `format!` or `writeln!`: 16 | //! 17 | //! ```rust 18 | //! use coloursum::EcojiLine; 19 | //! 20 | //! // BSD "tag" form 21 | //! let bsd_line = EcojiLine::from("MD5 (./src/ecoji_line.rs) = 841d462b66e1f4bb839a1b72ab3f3668".to_string()); 22 | //! 23 | //! assert_eq!( 24 | //! format!("{}", bsd_line), 25 | //! "MD5 (./src/ecoji_line.rs) = 📢💥👛🤓🤴🛌😫🥊🌵🚦😚🚲👱☕☕☕" 26 | //! ); 27 | //! 28 | //! // GNU Coreutils/Perl form 29 | //! let gnu_line = EcojiLine::from("841d462b66e1f4bb839a1b72ab3f3668 ./src/ecoji_line.rs".to_string()); 30 | //! 31 | //! assert_eq!( 32 | //! format!("{}", gnu_line), 33 | //! "📢💥👛🤓🤴🛌😫🥊🌵🚦😚🚲👱☕☕☕ ./src/ecoji_line.rs" 34 | //! ); 35 | //! ``` 36 | //! 37 | //! ## Formatting a buffer of lines with the `coloursum` function 38 | //! 39 | //! The `Line` trait implements a `coloursum` function. 40 | //! This consumes a `BufRead` input buffer, and writes formatted lines to a 41 | //! `Write` output buffer. 42 | //! 43 | //! ```rust 44 | //! use std::io::BufReader; 45 | //! 46 | //! use coloursum::{Line, EcojiLine}; 47 | //! use indoc::indoc; 48 | //! 49 | //! // Note that each line will have its format detected separately, so 50 | //! // BSD and GNU form lines can be in the same buffer 51 | //! let input = indoc!(" 52 | //! MD5 (./src/ecoji_line.rs) = 841d462b66e1f4bb839a1b72ab3f3668 53 | //! 841d462b66e1f4bb839a1b72ab3f3668 ./src/ecoji_line.rs 54 | //! ").as_bytes(); 55 | //! 56 | //! let input_buffer = BufReader::new(input); 57 | //! let mut output_buffer: Vec = Vec::new(); 58 | //! 59 | //! EcojiLine::coloursum(input_buffer, &mut output_buffer); 60 | //! 61 | //! assert_eq!( 62 | //! std::str::from_utf8(&output_buffer).unwrap(), 63 | //! indoc!(" 64 | //! MD5 (./src/ecoji_line.rs) = 📢💥👛🤓🤴🛌😫🥊🌵🚦😚🚲👱☕☕☕ 65 | //! 📢💥👛🤓🤴🛌😫🥊🌵🚦😚🚲👱☕☕☕ ./src/ecoji_line.rs 66 | //! ") 67 | //! ); 68 | //! ``` 69 | 70 | mod base_line; 71 | pub use base_line::{FormattableLine, Line}; 72 | 73 | mod ansi_coloured_line; 74 | pub use ansi_coloured_line::ANSIColouredLine; 75 | 76 | mod ecoji_line; 77 | pub use ecoji_line::EcojiLine; 78 | 79 | mod onepassword_line; 80 | pub use onepassword_line::OnePasswordLine; 81 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use clap::{Parser, ValueEnum}; 2 | use std::io; 3 | 4 | use coloursum::{ANSIColouredLine, EcojiLine, Line, OnePasswordLine}; 5 | 6 | #[derive(Clone, PartialEq, Debug, ValueEnum)] 7 | enum FormattingMode { 8 | ANSIColours, 9 | Ecoji, 10 | #[value(name = "1password")] 11 | OnePassword, 12 | } 13 | 14 | impl ToString for FormattingMode { 15 | fn to_string(&self) -> String { 16 | match self { 17 | FormattingMode::ANSIColours => "ansi-colours", 18 | FormattingMode::Ecoji => "ecoji", 19 | FormattingMode::OnePassword => "1password", 20 | } 21 | .to_string() 22 | } 23 | } 24 | 25 | #[derive(Parser, Debug)] 26 | #[clap(version)] 27 | struct Options { 28 | #[clap(flatten)] 29 | main_options: MainOptions, 30 | 31 | #[clap(subcommand)] 32 | cmd: Option, 33 | } 34 | 35 | #[derive(Parser, Debug)] 36 | struct MainOptions { 37 | /// What sort of formatting to use for checksum values. 38 | #[clap( 39 | short, 40 | long, 41 | ignore_case = true, 42 | value_enum, 43 | default_value = "ansi-colours" 44 | )] 45 | mode: FormattingMode, 46 | } 47 | 48 | #[derive(Parser, Debug)] 49 | #[clap(rename_all = "kebab-case")] 50 | enum Subcommand { 51 | /// Configure the current shell environment to use coloursum 52 | /// 53 | /// Automatically detects the currently used shell, and configures 54 | /// it to use coloursum with the specified options 55 | /// 56 | /// For example, to configure your zsh shell to use 1Password-style 57 | /// formatting, add the line 58 | /// `eval "$(coloursum --mode 1password shell-setup)"` to your ~/.zshrc 59 | /// file 60 | /// 61 | /// Or, to configure your fish shell to use ecoji formatting, but only 62 | /// for `sha256sum`, add the line 63 | /// `status --is-interactive; and coloursum --mode ecoji shell-setup sha256sum | source` 64 | /// to your ~/.config/fish/config.fish file 65 | #[cfg(unix)] 66 | #[clap(override_usage = r#" 67 | # for bash, zsh, and other similar shells 68 | eval "$(coloursum [OPTIONS] shell-setup [command])" 69 | 70 | # for fish 71 | status --is-interactive; and coloursum [OPTIONS] shell-setup [command] | source"#)] 72 | ShellSetup(ShellSetupOptions), 73 | } 74 | 75 | #[derive(Parser, Debug)] 76 | struct ShellSetupOptions { 77 | /// Checksum command to set up shell integration for. If omitted, coloursum 78 | /// will attempt to automatically detect installed checksum commands. 79 | /// If specified, coloursum will configure only that checksum command. 80 | /// You may call shell-setup repeatedly to configure integration for 81 | /// multiple manually-configured checksum commands. 82 | command: Option, 83 | } 84 | 85 | fn coloursum(options: &MainOptions) -> io::Result<()> { 86 | let stdin = io::stdin(); 87 | let locked_stdin = stdin.lock(); 88 | 89 | let stdout = io::stdout(); 90 | let locked_stdout = stdout.lock(); 91 | 92 | match options.mode { 93 | FormattingMode::ANSIColours => ANSIColouredLine::coloursum(locked_stdin, locked_stdout), 94 | FormattingMode::Ecoji => EcojiLine::coloursum(locked_stdin, locked_stdout), 95 | FormattingMode::OnePassword => OnePasswordLine::coloursum(locked_stdin, locked_stdout), 96 | } 97 | } 98 | 99 | #[cfg(unix)] 100 | static SUM_EXECNAMES: &[&str] = &[ 101 | "md5", 102 | "md5sum", 103 | "gmd5sum", 104 | "shasum", 105 | "sha1sum", 106 | "gsha1sum", 107 | "sha2", 108 | "sha256sum", 109 | "gsha256sum", 110 | "sha224sum", 111 | "gsha224sum", 112 | "sha384sum", 113 | "gsha384sum", 114 | "sha512sum", 115 | "gsha512sum", 116 | ]; 117 | 118 | #[cfg(unix)] 119 | fn shell_setup( 120 | options: &MainOptions, 121 | shell_setup_options: &ShellSetupOptions, 122 | ) -> Result<(), std::io::Error> { 123 | // detect the calling shell's name 124 | let shell_name = match get_shell_name() { 125 | Some(shell_name) => shell_name, 126 | // fall back to "sh" if not detectable/something weird happens 127 | None => "sh".to_string(), 128 | }; 129 | 130 | println!("# coloursum: generated setup for `{}`", shell_name); 131 | 132 | if let Some(command) = &shell_setup_options.command { 133 | print_shell_function(options, shell_name.as_ref(), command.to_string()); 134 | } else { 135 | for executable in SUM_EXECNAMES { 136 | if let Ok(_path) = which::which(executable) { 137 | print_shell_function(options, shell_name.as_ref(), (*executable).to_string()) 138 | } 139 | } 140 | } 141 | 142 | Ok(()) 143 | } 144 | 145 | #[cfg(unix)] 146 | fn get_shell_name() -> Option { 147 | use sysinfo::{ProcessExt, System, SystemExt}; 148 | 149 | let system = System::new_with_specifics(sysinfo::RefreshKind::new().with_processes()); 150 | 151 | // find the current process by pid, fall back to 0 152 | // (which is invalid/reserved, so will return None early) 153 | let process = system.get_process(sysinfo::get_current_pid().unwrap_or(0))?; 154 | 155 | // find the parent process via the current process's parent ID 156 | let parent = system.get_process(process.parent()?)?; 157 | 158 | // and if we make it all the way here, return some name 159 | Some(parent.name().to_string()) 160 | } 161 | 162 | #[cfg(unix)] 163 | fn print_shell_function(options: &MainOptions, shell_name: &str, command: String) { 164 | // TODO: work out how to print this losslessly 165 | let exe_name = match std::env::current_exe() { 166 | Ok(path) => path.to_string_lossy().into_owned(), 167 | Err(_) => "coloursum".to_string(), 168 | }; 169 | 170 | match shell_name { 171 | "fish" => println!( 172 | "function {0}\n\ 173 | \tcommand {0} $argv | {1} --mode {2}\n\ 174 | end", 175 | command, 176 | exe_name, 177 | options.mode.to_string() 178 | ), 179 | "ksh" => println!( 180 | "function {0} {{\n\ 181 | \tcommand {0} \"$@\" | {1} --mode {2}\n\ 182 | }}", 183 | command, 184 | exe_name, 185 | options.mode.to_string() 186 | ), 187 | _ => println!( 188 | "function {0}() {{\n\ 189 | \tcommand {0} \"$@\" | {1} --mode {2}\n\ 190 | }}", 191 | command, 192 | exe_name, 193 | options.mode.to_string() 194 | ), 195 | } 196 | } 197 | 198 | fn main() -> Result<(), std::io::Error> { 199 | let options = Options::parse(); 200 | 201 | if let Some(command) = options.cmd { 202 | match command { 203 | #[cfg(unix)] 204 | Subcommand::ShellSetup(shell_setup_options) => { 205 | shell_setup(&options.main_options, &shell_setup_options) 206 | } 207 | } 208 | } else { 209 | coloursum(&options.main_options) 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /src/onepassword_line.rs: -------------------------------------------------------------------------------- 1 | use std::convert::From; 2 | use std::fmt; 3 | use std::fmt::Display; 4 | 5 | use ansi_term::Colour::Fixed; 6 | 7 | use crate::base_line::{FormattableLine, Line}; 8 | 9 | #[derive(Debug)] 10 | /// Line with formatting which colours numeric digits in blue, 11 | /// and leaves the rest alone. Inspired by 1Password. 12 | pub struct OnePasswordLine(FormattableLine); 13 | impl From for OnePasswordLine { 14 | fn from(contents: String) -> Self { 15 | Self(FormattableLine::from(contents)) 16 | } 17 | } 18 | 19 | impl Display for OnePasswordLine { 20 | fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 21 | self.to_formatted(formatter) 22 | } 23 | } 24 | 25 | impl Line for OnePasswordLine { 26 | fn get_line(&self) -> &FormattableLine { 27 | &self.0 28 | } 29 | 30 | /// Formats a base16-format hash or digest. 31 | /// 32 | /// Any numeric characters are formatted in blue. 33 | fn format_hash(hash: String) -> String { 34 | hash.chars() 35 | .map(|character| { 36 | if character.is_ascii_digit() { 37 | Fixed(4).paint(character.to_string()).to_string() 38 | } else { 39 | character.to_string() 40 | } 41 | }) 42 | .collect() 43 | } 44 | } 45 | 46 | #[cfg(test)] 47 | mod tests { 48 | #[test] 49 | fn display_works() { 50 | use super::OnePasswordLine; 51 | 52 | assert_eq!( 53 | format!("{}", OnePasswordLine::from("MD5 (./src/main.rs) = b7527e0e28c09f6f62dd2d4197d5d225".to_string())), 54 | "MD5 (./src/main.rs) = b\u{1b}[38;5;4m7\u{1b}[0m\u{1b}[38;5;4m5\u{1b}[0m\u{1b}[38;5;4m2\u{1b}[0m\u{1b}[38;5;4m7\u{1b}[0me\u{1b}[38;5;4m0\u{1b}[0me\u{1b}[38;5;4m2\u{1b}[0m\u{1b}[38;5;4m8\u{1b}[0mc\u{1b}[38;5;4m0\u{1b}[0m\u{1b}[38;5;4m9\u{1b}[0mf\u{1b}[38;5;4m6\u{1b}[0mf\u{1b}[38;5;4m6\u{1b}[0m\u{1b}[38;5;4m2\u{1b}[0mdd\u{1b}[38;5;4m2\u{1b}[0md\u{1b}[38;5;4m4\u{1b}[0m\u{1b}[38;5;4m1\u{1b}[0m\u{1b}[38;5;4m9\u{1b}[0m\u{1b}[38;5;4m7\u{1b}[0md\u{1b}[38;5;4m5\u{1b}[0md\u{1b}[38;5;4m2\u{1b}[0m\u{1b}[38;5;4m2\u{1b}[0m\u{1b}[38;5;4m5\u{1b}[0m" 55 | ) 56 | } 57 | 58 | #[test] 59 | fn format_hash_works() { 60 | use super::OnePasswordLine; 61 | use crate::Line; 62 | 63 | assert_eq!( 64 | OnePasswordLine::format_hash( 65 | "b7527e0e28c09f6f62dd2d4197d5d225".to_string() 66 | ), 67 | "b\u{1b}[38;5;4m7\u{1b}[0m\u{1b}[38;5;4m5\u{1b}[0m\u{1b}[38;5;4m2\u{1b}[0m\u{1b}[38;5;4m7\u{1b}[0me\u{1b}[38;5;4m0\u{1b}[0me\u{1b}[38;5;4m2\u{1b}[0m\u{1b}[38;5;4m8\u{1b}[0mc\u{1b}[38;5;4m0\u{1b}[0m\u{1b}[38;5;4m9\u{1b}[0mf\u{1b}[38;5;4m6\u{1b}[0mf\u{1b}[38;5;4m6\u{1b}[0m\u{1b}[38;5;4m2\u{1b}[0mdd\u{1b}[38;5;4m2\u{1b}[0md\u{1b}[38;5;4m4\u{1b}[0m\u{1b}[38;5;4m1\u{1b}[0m\u{1b}[38;5;4m9\u{1b}[0m\u{1b}[38;5;4m7\u{1b}[0md\u{1b}[38;5;4m5\u{1b}[0md\u{1b}[38;5;4m2\u{1b}[0m\u{1b}[38;5;4m2\u{1b}[0m\u{1b}[38;5;4m5\u{1b}[0m" 68 | ); 69 | } 70 | 71 | #[test] 72 | fn format_hash_doesnt_crash_on_non_base16_characters() { 73 | use super::OnePasswordLine; 74 | use crate::Line; 75 | 76 | OnePasswordLine::format_hash("ASDF".to_string()); 77 | OnePasswordLine::format_hash("😄".to_string()); 78 | } 79 | } 80 | --------------------------------------------------------------------------------