├── .github └── workflows │ ├── build.yaml │ └── docs.yaml ├── .gitignore ├── .rustfmt.toml ├── Cargo.lock ├── Cargo.toml ├── Cross.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── RELEASES.md ├── build.rs ├── completions ├── sheldon.bash └── sheldon.zsh ├── docs ├── .gitignore ├── README_TEMPLATE.md ├── book.toml └── src │ ├── Command-line-interface.md │ ├── Configuration.md │ ├── Examples.md │ ├── Getting-started.md │ ├── Installation.md │ ├── Introduction.md │ ├── RELEASES.md │ └── SUMMARY.md ├── flake.lock ├── flake.nix ├── onedoc.toml ├── sheldon.nix ├── src ├── cli │ ├── color_choice.rs │ ├── mod.rs │ ├── raw.rs │ ├── testdata │ │ ├── raw_opt_add_help.golden │ │ ├── raw_opt_help.golden │ │ ├── raw_opt_init_help.golden │ │ ├── raw_opt_lock_help.golden │ │ ├── raw_opt_source_help.golden │ │ └── raw_opt_subcommand_required.golden │ └── tests.rs ├── config │ ├── clean.rs │ ├── edit.rs │ ├── file.rs │ ├── mod.rs │ ├── normalize.rs │ ├── plugins.toml │ └── profile.rs ├── context │ ├── message.rs │ ├── mod.rs │ └── tests.rs ├── editor.rs ├── lock │ ├── file.rs │ ├── mod.rs │ ├── plugin.rs │ ├── script.rs │ └── source │ │ ├── git.rs │ │ ├── local.rs │ │ ├── mod.rs │ │ └── remote.rs ├── macros.rs ├── main.rs └── util │ ├── build.rs │ ├── git.rs │ ├── mod.rs │ └── temp.rs └── tests ├── helpers ├── dirs.rs ├── mod.rs └── process.rs ├── lib.rs └── testdata ├── clean ├── lock.stderr ├── plugins.lock ├── plugins.toml └── source.stderr ├── clean_permission_denied ├── lock.stderr ├── plugins.lock ├── plugins.toml └── source.stderr ├── directories_default ├── lock.stderr ├── plugins.lock ├── plugins.toml ├── source.stderr └── source.stdout ├── directories_xdg_from_env ├── lock.stderr ├── plugins.lock ├── plugins.toml ├── source.stderr └── source.stdout ├── empty ├── lock.stderr ├── plugins.lock ├── plugins.toml └── source.stderr ├── github_bad_reinstall ├── lock.stderr ├── plugins.lock └── plugins.toml ├── github_bad_url ├── lock.stderr ├── plugins.toml ├── source.stderr └── source.stdout ├── github_branch ├── lock.stderr ├── plugins.lock ├── plugins.toml ├── source.stderr └── source.stdout ├── github_https ├── lock.stderr ├── plugins.lock ├── plugins.toml ├── source.stderr └── source.stdout ├── github_submodule ├── lock.stderr ├── plugins.lock ├── plugins.toml ├── source.stderr └── source.stdout ├── github_tag ├── lock.stderr ├── plugins.lock ├── plugins.toml ├── source.stderr └── source.stdout ├── hooks ├── lock.stderr ├── plugins.lock ├── plugins.toml ├── source.stderr └── source.stdout ├── inline ├── lock.stderr ├── plugins.lock ├── plugins.toml ├── source.stderr └── source.stdout ├── override_config_file ├── lock.stderr ├── plugins.lock ├── source.stderr └── test.toml ├── override_config_file_missing ├── lock.stderr └── source.stderr ├── override_data_dir ├── lock.stderr ├── plugins.lock ├── plugins.toml └── source.stderr └── profiles ├── lock.stderr ├── plugins.p1.lock ├── plugins.toml ├── source.stderr └── source.stdout /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | # This is a big workflow and it contains two main processes: 2 | # 3 | # 1. Running lints and tests. 4 | # - Rustfmt and Clippy checks are run against the Rust code. 5 | # - The README is must be up to date. 6 | # - The completions must be up to date. 7 | # - Tests are run on multiple platforms and targets. 8 | # 9 | # 2. Publishing and releasing Sheldon when a tag is pushed. 10 | # - Firstly we assert that the tag version matches the package version. 11 | # In parallel 12 | # - Publish Sheldon to Crates.io 13 | # - For each platform build a release artifact and upload to the GitHub release. 14 | 15 | name: build 16 | 17 | on: [push, pull_request] 18 | 19 | env: 20 | CRATE: sheldon 21 | 22 | jobs: 23 | 24 | # --------------------------------------------------------------------------- 25 | # Lint 26 | # --------------------------------------------------------------------------- 27 | 28 | lint: 29 | strategy: 30 | matrix: 31 | toolchain: [stable, beta, nightly] 32 | 33 | runs-on: ubuntu-latest 34 | 35 | env: 36 | RUSTFLAGS: --deny warnings 37 | 38 | steps: 39 | - uses: actions/checkout@v4 40 | with: 41 | fetch-depth: 0 42 | 43 | - uses: dtolnay/rust-toolchain@master 44 | with: 45 | toolchain: ${{ matrix.toolchain }} 46 | components: clippy, rustfmt 47 | 48 | - name: Rustfmt 49 | run: cargo fmt -- --check 50 | 51 | - name: Clippy 52 | continue-on-error: ${{ matrix.toolchain == 'nightly' }} 53 | run: cargo clippy --workspace 54 | 55 | # --------------------------------------------------------------------------- 56 | # Test 57 | # --------------------------------------------------------------------------- 58 | 59 | test: 60 | strategy: 61 | matrix: 62 | include: 63 | - { os: macos-latest, target: aarch64-apple-darwin } 64 | - { os: ubuntu-latest, target: x86_64-unknown-linux-musl } 65 | - { os: ubuntu-latest, target: aarch64-unknown-linux-musl } 66 | - { os: ubuntu-latest, target: armv7-unknown-linux-musleabihf } 67 | 68 | name: test (${{ matrix.target }}) 69 | runs-on: ${{ matrix.os }} 70 | 71 | steps: 72 | - uses: actions/checkout@v4 73 | with: 74 | fetch-depth: 0 75 | 76 | - uses: dtolnay/rust-toolchain@stable 77 | with: 78 | target: ${{ matrix.target }} 79 | 80 | - name: Test 81 | run: | 82 | cargo install cross 83 | cross test --locked --target ${{ matrix.target }} 84 | 85 | # --------------------------------------------------------------------------- 86 | # Check README 87 | # --------------------------------------------------------------------------- 88 | 89 | readme: 90 | runs-on: ubuntu-latest 91 | 92 | steps: 93 | - uses: actions/checkout@v4 94 | - uses: dtolnay/rust-toolchain@stable 95 | 96 | - name: Install cargo-onedoc 97 | run: cargo install cargo-onedoc --locked 98 | 99 | - name: Check README 100 | run: cargo onedoc --check 101 | 102 | # --------------------------------------------------------------------------- 103 | # Check completions 104 | # --------------------------------------------------------------------------- 105 | 106 | completions: 107 | runs-on: ubuntu-latest 108 | 109 | steps: 110 | - uses: actions/checkout@v4 111 | - uses: dtolnay/rust-toolchain@stable 112 | 113 | - name: Generate completions (bash) 114 | run: cargo run -- completions --shell bash > completions/$CRATE.bash 115 | 116 | - name: Generate completions (zsh) 117 | run: cargo run -- completions --shell zsh > completions/$CRATE.zsh 118 | 119 | - name: Check up to date 120 | run: git diff --no-ext-diff --exit-code -- completions/ 121 | 122 | # --------------------------------------------------------------------------- 123 | # Check version against tag 124 | # --------------------------------------------------------------------------- 125 | 126 | prepare: 127 | needs: [lint, test, readme, completions] 128 | if: startsWith(github.ref, 'refs/tags/') 129 | 130 | runs-on: ubuntu-latest 131 | 132 | steps: 133 | - uses: actions/checkout@v4 134 | 135 | - name: Calculate version from tag 136 | id: version 137 | run: echo "value=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT 138 | 139 | - name: Check tag against package version 140 | run: grep '^version = "${{ steps.version.outputs.value }}"$' Cargo.toml 141 | 142 | # --------------------------------------------------------------------------- 143 | # Publish to Crates.io 144 | # --------------------------------------------------------------------------- 145 | 146 | publish: 147 | needs: prepare 148 | runs-on: ubuntu-latest 149 | 150 | steps: 151 | - uses: actions/checkout@v4 152 | - uses: dtolnay/rust-toolchain@stable 153 | 154 | - name: Publish 155 | env: 156 | CARGO_REGISTRY_TOKEN: ${{ secrets.CRATES_IO_TOKEN }} 157 | run: cargo publish 158 | 159 | # --------------------------------------------------------------------------- 160 | # Release to GitHub 161 | # --------------------------------------------------------------------------- 162 | 163 | release: 164 | needs: prepare 165 | 166 | strategy: 167 | matrix: 168 | include: 169 | - { os: macos-latest, target: aarch64-apple-darwin } 170 | - { os: ubuntu-latest, target: x86_64-unknown-linux-musl, prefix: x86_64-linux-musl } 171 | - { os: ubuntu-latest, target: aarch64-unknown-linux-musl, prefix: aarch64-linux-musl } 172 | - { os: ubuntu-latest, target: armv7-unknown-linux-musleabihf, prefix: arm-linux-musleabihf } 173 | 174 | name: release (${{ matrix.target }}) 175 | runs-on: ${{ matrix.os }} 176 | 177 | steps: 178 | - uses: actions/checkout@v4 179 | with: 180 | fetch-depth: 0 181 | 182 | - uses: dtolnay/rust-toolchain@stable 183 | with: 184 | target: ${{ matrix.target }} 185 | 186 | - name: Build 187 | run: | 188 | cargo install cross 189 | cross build --locked --release --target ${{ matrix.target }} 190 | 191 | - name: Calculate version from tag 192 | id: version 193 | run: echo "value=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT 194 | 195 | - name: Archive 196 | id: archive 197 | run: | 198 | mkdir release 199 | archive=$CRATE-${{ steps.version.outputs.value }}-${{ matrix.target }}.tar.gz 200 | cp target/${{ matrix.target }}/release/$CRATE release/$CRATE 201 | cp LICENSE* release 202 | cp README.md release 203 | cp -R completions release 204 | cd release 205 | tar cfz "../$archive" -- * 206 | cd .. 207 | rm -r release 208 | echo "path=$archive" >> $GITHUB_OUTPUT 209 | 210 | - uses: softprops/action-gh-release@v1 211 | env: 212 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 213 | with: 214 | files: ${{ steps.archive.outputs.path }} 215 | -------------------------------------------------------------------------------- /.github/workflows/docs.yaml: -------------------------------------------------------------------------------- 1 | name: docs 2 | 3 | on: [workflow_dispatch] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - uses: actions/checkout@v4 11 | 12 | - uses: actions/checkout@v4 13 | with: 14 | ref: gh-pages 15 | path: gh-pages 16 | 17 | - uses: extractions/setup-crate@v1 18 | with: 19 | owner: rust-lang 20 | name: mdBook 21 | 22 | - uses: extractions/setup-crate@v1 23 | with: 24 | owner: Michael-F-Bryan 25 | name: mdbook-linkcheck 26 | 27 | - name: Build 28 | run: | 29 | mdbook build docs 30 | cp -R docs/book/html/* gh-pages/ 31 | 32 | - name: Calculate Git short SHA 33 | id: git 34 | run: echo "::set-output name=short_sha::$(git rev-parse --short HEAD)" 35 | 36 | - name: Git commit 37 | run: | 38 | cd gh-pages 39 | git config user.name "github-actions[bot]" 40 | git config user.email "41898282+github-actions[bot]@users.noreply.github.com" 41 | git add . 42 | git commit -m "publish docs for ${{ steps.git.outputs.short_sha }}" 43 | git push 44 | cd - 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /release 2 | /target 3 | /docs/plugins.example.lock 4 | .vscode/ 5 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | # Stable options 2 | newline_style = "Unix" 3 | 4 | # Unstable Options 5 | unstable_features = true 6 | wrap_comments = true 7 | condense_wildcard_suffixes = true 8 | format_strings = true 9 | normalize_comments = true 10 | normalize_doc_attributes = true 11 | reorder_impl_items = true 12 | use_field_init_shorthand = true 13 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sheldon" 3 | version = "0.8.2" 4 | authors = ["Ross MacArthur "] 5 | edition = "2021" 6 | description = "Fast, configurable, shell plugin manager." 7 | documentation = "https://sheldon.cli.rs" 8 | readme = "README.md" 9 | repository = "https://github.com/rossmacarthur/sheldon" 10 | license = "MIT OR Apache-2.0" 11 | keywords = ["shell", "plugin", "manager", "zsh", "bash"] 12 | categories = ["command-line-utilities"] 13 | build = "build.rs" 14 | include = ["src/**/*", "LICENSE-*", "README.md", "build.rs"] 15 | rust-version = "1.81.0" 16 | 17 | [profile.release] 18 | strip = "symbols" 19 | 20 | [profile.compact] 21 | inherits = "release" 22 | opt-level = "s" 23 | lto = true 24 | panic = "abort" 25 | codegen-units = 1 26 | 27 | [package.metadata.binstall] 28 | pkg-url = "{ repo }/releases/download/{ version }/sheldon-{ version }-{ target }.tar.gz" 29 | 30 | [dependencies] 31 | anyhow = "1.0.86" 32 | casual = "0.2.0" 33 | clap_complete = "4.4.10" 34 | constcat = "0.6.0" 35 | curl = "0.4.46" 36 | fmutex = "0.3.0" 37 | git2 = "0.20.0" 38 | globwalk = "0.9.1" 39 | home = "0.5.9" 40 | indexmap = { version = "2.4.0", features = ["rayon", "serde"] } 41 | itertools = "0.14.0" 42 | maplit = "1.0.2" 43 | rayon = "1.10.0" 44 | regex-macro = "0.3.0" 45 | serde = { version = "1.0.209", features = ["derive"] } 46 | thiserror = "2.0.11" 47 | toml = { version = "0.8.19", features = ["preserve_order"] } 48 | toml_edit = "0.22.20" 49 | upon = { version = "0.9.0", default-features = false, features = ["serde", "filters"] } 50 | url = { version = "2.5.2", features = ["serde"] } 51 | walkdir = "2.5.0" 52 | which = { version = "7.0.2", default-features = false } 53 | yansi = "1.0.1" 54 | 55 | [dependencies.clap] 56 | version = "4.5.18" 57 | default-features = false 58 | features = [ 59 | "std", "help", "usage", "error-context", "suggestions", # default excluding "color" 60 | "cargo", "env", "derive" # optional 61 | ] 62 | 63 | [build-dependencies] 64 | anyhow = "1.0.86" 65 | 66 | [dev-dependencies] 67 | goldie = "0.5.0" 68 | pretty_assertions = "1.4.0" 69 | tempfile = "3.12.0" 70 | upon = { version = "0.9.0", default-features = false, features = ["serde", "filters", "syntax"] } 71 | 72 | [features] 73 | # By default vendor libgit2 since we rely on some modern features. 74 | default = ["vendored-libgit2"] 75 | 76 | # Vendor and statically link curl, libgit2, and openssl 77 | vendored = ["vendored-curl", "vendored-libgit2", "vendored-openssl"] 78 | 79 | # Vendor and statically link curl 80 | vendored-curl = ["curl/static-curl"] 81 | 82 | # Vendor and statically link libgit2 83 | # 84 | # Disabling this will not force the usage of the system libgit2, you can do that 85 | # using LIBGIT2_NO_VENDOR=1 which will error at compile time if the system 86 | # libgit2 is not a high enough version. 87 | vendored-libgit2 = ["git2/vendored-libgit2"] 88 | 89 | # Vendor and statically link openssl 90 | # 91 | # Disabling this will not force the usage of the system OpenSSL you can do that 92 | # using OPENSSL_NO_VENDOR=1 93 | vendored-openssl = ["git2/vendored-openssl", "curl/static-ssl"] 94 | 95 | [lints.rust] 96 | unknown_lints = "allow" 97 | elided_lifetimes_in_paths = "warn" 98 | let_underscore_drop = "warn" 99 | macro_use_extern_crate = "warn" 100 | meta_variable_misuse = "warn" 101 | unsafe_op_in_unsafe_fn = "warn" 102 | 103 | [lints.clippy] 104 | if_not_else = "warn" 105 | items_after_statements = "warn" 106 | semicolon_if_nothing_returned = "warn" 107 | -------------------------------------------------------------------------------- /Cross.toml: -------------------------------------------------------------------------------- 1 | [target.aarch64-unknown-linux-musl] 2 | image = "rossmacarthur/sheldon-cross:aarch64-unknown-linux-musl-0.2.1" 3 | 4 | [target.armv7-unknown-linux-musleabihf] 5 | image = "rossmacarthur/sheldon-cross:armv7-unknown-linux-musleabihf-0.2.1" 6 | 7 | [target.x86_64-unknown-linux-musl] 8 | image = "rossmacarthur/sheldon-cross:x86_64-unknown-linux-musl-0.2.1" 9 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any person obtaining a copy 2 | of this software and associated documentation files (the "Software"), to deal 3 | in the Software without restriction, including without limitation the rights 4 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 5 | copies of the Software, and to permit persons to whom the Software is 6 | furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all 9 | copies or substantial portions of the Software. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 12 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 15 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 17 | SOFTWARE. 18 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::io; 3 | use std::path::{Path, PathBuf}; 4 | use std::process; 5 | 6 | use anyhow::{bail, Context, Result}; 7 | 8 | /// Nicely format an error message for when the subprocess didn't exit 9 | /// successfully. 10 | pub fn format_error_msg(cmd: &process::Command, output: &process::Output) -> String { 11 | let stdout = String::from_utf8_lossy(&output.stdout); 12 | let stderr = String::from_utf8_lossy(&output.stderr); 13 | let mut msg = format!( 14 | "subprocess didn't exit successfully `{:?}` ({})", 15 | cmd, output.status 16 | ); 17 | if !stdout.trim().is_empty() { 18 | msg.push_str("\n--- stdout\n"); 19 | msg.push_str(&stdout); 20 | } 21 | if !stderr.trim().is_empty() { 22 | msg.push_str("\n--- stderr\n"); 23 | msg.push_str(&stderr); 24 | } 25 | msg 26 | } 27 | 28 | /// Whether underlying error kind for the given error is 29 | /// `io::ErrorKind::NotFound`. 30 | pub fn is_io_not_found(error: &anyhow::Error) -> bool { 31 | for cause in error.chain() { 32 | if let Some(io_error) = cause.downcast_ref::() { 33 | return io_error.kind() == io::ErrorKind::NotFound; 34 | } 35 | } 36 | false 37 | } 38 | 39 | trait CommandExt { 40 | /// Run the command and return the standard output as a string. 41 | fn output_text(&mut self) -> Result; 42 | } 43 | 44 | impl CommandExt for process::Command { 45 | /// Run the command and return the standard output as a string. 46 | fn output_text(&mut self) -> Result { 47 | let output = self 48 | .output() 49 | .with_context(|| format!("could not execute subprocess: `{self:?}`"))?; 50 | if !output.status.success() { 51 | bail!(format_error_msg(self, &output)); 52 | } 53 | String::from_utf8(output.stdout).context("failed to parse stdout") 54 | } 55 | } 56 | 57 | /// Run a Git subcommand and set the result as a rustc environment variable. 58 | /// 59 | /// Note: Success is returned if the Git subcommand is not available. 60 | fn print_git_env(dir: &Path, key: &str, cmd: &str) -> Result<()> { 61 | let mut split = cmd.split_whitespace(); 62 | let value = match process::Command::new(split.next().unwrap()) 63 | .arg("-C") 64 | .arg(dir) 65 | .args(split) 66 | .output_text() 67 | { 68 | Ok(text) => text.trim().to_string(), 69 | Err(err) if is_io_not_found(&err) => return Ok(()), 70 | Err(err) => return Err(err), 71 | }; 72 | println!("cargo:rustc-env={key}={value}"); 73 | Ok(()) 74 | } 75 | 76 | /// Fetch Git info and set as rustc environment variables. 77 | /// 78 | /// If the Git subcommand is missing or the `.git` directory does not exist then 79 | /// no errors will be produced. 80 | fn print_git_envs() -> Result<()> { 81 | let dir = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap()); 82 | if !dir.join(".git").exists() { 83 | return Ok(()); 84 | } 85 | print_git_env( 86 | &dir, 87 | "GIT_COMMIT_DATE", 88 | "git log -1 --no-show-signature --date=short --format=%cd", 89 | )?; 90 | print_git_env(&dir, "GIT_COMMIT_HASH", "git rev-parse HEAD")?; 91 | print_git_env( 92 | &dir, 93 | "GIT_COMMIT_SHORT_HASH", 94 | "git rev-parse --short=9 HEAD", 95 | )?; 96 | Ok(()) 97 | } 98 | 99 | /// Fetch rustc info and set as rustc environment variables. 100 | fn print_rustc_envs() -> Result<()> { 101 | let text = process::Command::new(env::var("RUSTC")?) 102 | .arg("--verbose") 103 | .arg("--version") 104 | .output_text()?; 105 | let mut lines = text.lines(); 106 | println!( 107 | "cargo:rustc-env=RUSTC_VERSION_SUMMARY={}", 108 | lines.next().unwrap() 109 | ); 110 | for line in lines { 111 | let (key, value) = line.split_once(": ").unwrap(); 112 | println!( 113 | "cargo:rustc-env=RUSTC_VERSION_{}={}", 114 | key.replace(['-', ' '], "_").to_uppercase(), 115 | value, 116 | ); 117 | } 118 | Ok(()) 119 | } 120 | 121 | fn main() -> Result<()> { 122 | print_git_envs().context("failed to fetch Git information")?; 123 | print_rustc_envs().context("failed to fetch rustc information")?; 124 | println!("cargo:rustc-env=TARGET={}", env::var("TARGET")?); 125 | Ok(()) 126 | } 127 | -------------------------------------------------------------------------------- /completions/sheldon.bash: -------------------------------------------------------------------------------- 1 | _sheldon() { 2 | local i cur prev opts cmd 3 | COMPREPLY=() 4 | if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then 5 | cur="$2" 6 | else 7 | cur="${COMP_WORDS[COMP_CWORD]}" 8 | fi 9 | prev="$3" 10 | cmd="" 11 | opts="" 12 | 13 | for i in "${COMP_WORDS[@]:0:COMP_CWORD}" 14 | do 15 | case "${cmd},${i}" in 16 | ",$1") 17 | cmd="sheldon" 18 | ;; 19 | sheldon,add) 20 | cmd="sheldon__add" 21 | ;; 22 | sheldon,completions) 23 | cmd="sheldon__completions" 24 | ;; 25 | sheldon,edit) 26 | cmd="sheldon__edit" 27 | ;; 28 | sheldon,init) 29 | cmd="sheldon__init" 30 | ;; 31 | sheldon,lock) 32 | cmd="sheldon__lock" 33 | ;; 34 | sheldon,remove) 35 | cmd="sheldon__remove" 36 | ;; 37 | sheldon,source) 38 | cmd="sheldon__source" 39 | ;; 40 | sheldon,version) 41 | cmd="sheldon__version" 42 | ;; 43 | *) 44 | ;; 45 | esac 46 | done 47 | 48 | case "${cmd}" in 49 | sheldon) 50 | opts="-q -v -h -V --quiet --non-interactive --verbose --color --config-dir --data-dir --config-file --profile --help --version init add edit remove lock source completions version" 51 | if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then 52 | COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) 53 | return 0 54 | fi 55 | case "${prev}" in 56 | --color) 57 | COMPREPLY=($(compgen -W "auto always never" -- "${cur}")) 58 | return 0 59 | ;; 60 | --config-dir) 61 | COMPREPLY=($(compgen -f "${cur}")) 62 | return 0 63 | ;; 64 | --data-dir) 65 | COMPREPLY=($(compgen -f "${cur}")) 66 | return 0 67 | ;; 68 | --config-file) 69 | COMPREPLY=($(compgen -f "${cur}")) 70 | return 0 71 | ;; 72 | --profile) 73 | COMPREPLY=($(compgen -f "${cur}")) 74 | return 0 75 | ;; 76 | *) 77 | COMPREPLY=() 78 | ;; 79 | esac 80 | COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) 81 | return 0 82 | ;; 83 | sheldon__add) 84 | opts="-h --git --gist --github --remote --local --proto --branch --rev --tag --dir --use --apply --profiles --hooks --help " 85 | if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then 86 | COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) 87 | return 0 88 | fi 89 | case "${prev}" in 90 | --git) 91 | COMPREPLY=($(compgen -f "${cur}")) 92 | return 0 93 | ;; 94 | --gist) 95 | COMPREPLY=($(compgen -f "${cur}")) 96 | return 0 97 | ;; 98 | --github) 99 | COMPREPLY=($(compgen -f "${cur}")) 100 | return 0 101 | ;; 102 | --remote) 103 | COMPREPLY=($(compgen -f "${cur}")) 104 | return 0 105 | ;; 106 | --local) 107 | COMPREPLY=($(compgen -f "${cur}")) 108 | return 0 109 | ;; 110 | --proto) 111 | COMPREPLY=($(compgen -f "${cur}")) 112 | return 0 113 | ;; 114 | --branch) 115 | COMPREPLY=($(compgen -f "${cur}")) 116 | return 0 117 | ;; 118 | --rev) 119 | COMPREPLY=($(compgen -f "${cur}")) 120 | return 0 121 | ;; 122 | --tag) 123 | COMPREPLY=($(compgen -f "${cur}")) 124 | return 0 125 | ;; 126 | --dir) 127 | COMPREPLY=($(compgen -f "${cur}")) 128 | return 0 129 | ;; 130 | --use) 131 | COMPREPLY=($(compgen -f "${cur}")) 132 | return 0 133 | ;; 134 | --apply) 135 | COMPREPLY=($(compgen -f "${cur}")) 136 | return 0 137 | ;; 138 | --profiles) 139 | COMPREPLY=($(compgen -f "${cur}")) 140 | return 0 141 | ;; 142 | --hooks) 143 | COMPREPLY=($(compgen -f "${cur}")) 144 | return 0 145 | ;; 146 | *) 147 | COMPREPLY=() 148 | ;; 149 | esac 150 | COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) 151 | return 0 152 | ;; 153 | sheldon__completions) 154 | opts="-h --shell --help" 155 | if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then 156 | COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) 157 | return 0 158 | fi 159 | case "${prev}" in 160 | --shell) 161 | COMPREPLY=($(compgen -W "bash elvish fish powershell zsh" -- "${cur}")) 162 | return 0 163 | ;; 164 | *) 165 | COMPREPLY=() 166 | ;; 167 | esac 168 | COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) 169 | return 0 170 | ;; 171 | sheldon__edit) 172 | opts="-h --help" 173 | if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then 174 | COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) 175 | return 0 176 | fi 177 | case "${prev}" in 178 | *) 179 | COMPREPLY=() 180 | ;; 181 | esac 182 | COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) 183 | return 0 184 | ;; 185 | sheldon__init) 186 | opts="-h --shell --help" 187 | if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then 188 | COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) 189 | return 0 190 | fi 191 | case "${prev}" in 192 | --shell) 193 | COMPREPLY=($(compgen -W "bash zsh" -- "${cur}")) 194 | return 0 195 | ;; 196 | *) 197 | COMPREPLY=() 198 | ;; 199 | esac 200 | COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) 201 | return 0 202 | ;; 203 | sheldon__lock) 204 | opts="-h --update --reinstall --help" 205 | if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then 206 | COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) 207 | return 0 208 | fi 209 | case "${prev}" in 210 | *) 211 | COMPREPLY=() 212 | ;; 213 | esac 214 | COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) 215 | return 0 216 | ;; 217 | sheldon__remove) 218 | opts="-h --help " 219 | if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then 220 | COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) 221 | return 0 222 | fi 223 | case "${prev}" in 224 | *) 225 | COMPREPLY=() 226 | ;; 227 | esac 228 | COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) 229 | return 0 230 | ;; 231 | sheldon__source) 232 | opts="-h --relock --update --reinstall --help" 233 | if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then 234 | COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) 235 | return 0 236 | fi 237 | case "${prev}" in 238 | *) 239 | COMPREPLY=() 240 | ;; 241 | esac 242 | COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) 243 | return 0 244 | ;; 245 | sheldon__version) 246 | opts="-h --help" 247 | if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then 248 | COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) 249 | return 0 250 | fi 251 | case "${prev}" in 252 | *) 253 | COMPREPLY=() 254 | ;; 255 | esac 256 | COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) 257 | return 0 258 | ;; 259 | esac 260 | } 261 | 262 | if [[ "${BASH_VERSINFO[0]}" -eq 4 && "${BASH_VERSINFO[1]}" -ge 4 || "${BASH_VERSINFO[0]}" -gt 4 ]]; then 263 | complete -F _sheldon -o nosort -o bashdefault -o default sheldon 264 | else 265 | complete -F _sheldon -o bashdefault -o default sheldon 266 | fi 267 | -------------------------------------------------------------------------------- /completions/sheldon.zsh: -------------------------------------------------------------------------------- 1 | #compdef sheldon 2 | 3 | autoload -U is-at-least 4 | 5 | _sheldon() { 6 | typeset -A opt_args 7 | typeset -a _arguments_options 8 | local ret=1 9 | 10 | if is-at-least 5.2; then 11 | _arguments_options=(-s -S -C) 12 | else 13 | _arguments_options=(-s -C) 14 | fi 15 | 16 | local context curcontext="$curcontext" state line 17 | _arguments "${_arguments_options[@]}" : \ 18 | '--color=[Output coloring]:WHEN:(auto always never)' \ 19 | '--config-dir=[The configuration directory]:PATH:_files' \ 20 | '--data-dir=[The data directory]:PATH:_files' \ 21 | '--config-file=[The config file]:PATH:_files' \ 22 | '--profile=[The profile used for conditional plugins]:PROFILE:_default' \ 23 | '-q[Suppress any informational output]' \ 24 | '--quiet[Suppress any informational output]' \ 25 | '--non-interactive[Suppress any interactive prompts and assume "yes" as the answer]' \ 26 | '-v[Use verbose output]' \ 27 | '--verbose[Use verbose output]' \ 28 | '-h[Print help]' \ 29 | '--help[Print help]' \ 30 | '-V[Print version]' \ 31 | '--version[Print version]' \ 32 | ":: :_sheldon_commands" \ 33 | "*::: :->sheldon" \ 34 | && ret=0 35 | case $state in 36 | (sheldon) 37 | words=($line[1] "${words[@]}") 38 | (( CURRENT += 1 )) 39 | curcontext="${curcontext%:*:*}:sheldon-command-$line[1]:" 40 | case $line[1] in 41 | (init) 42 | _arguments "${_arguments_options[@]}" : \ 43 | '--shell=[The type of shell]:SHELL:(bash zsh)' \ 44 | '-h[Print help]' \ 45 | '--help[Print help]' \ 46 | && ret=0 47 | ;; 48 | (add) 49 | _arguments "${_arguments_options[@]}" : \ 50 | '--git=[Add a clonable Git repository]:URL:_default' \ 51 | '--gist=[Add a clonable Gist snippet]:ID:_default' \ 52 | '--github=[Add a clonable GitHub repository]:REPO:_default' \ 53 | '--remote=[Add a downloadable file]:URL:_default' \ 54 | '--local=[Add a local directory]:DIR:_files' \ 55 | '(--git --remote --local)--proto=[The Git protocol for a Gist or GitHub plugin]:PROTO:_default' \ 56 | '--branch=[Checkout the tip of a branch]:BRANCH:_default' \ 57 | '--rev=[Checkout a specific commit]:SHA:_default' \ 58 | '--tag=[Checkout a specific tag]:TAG:_default' \ 59 | '--dir=[Which sub directory to use in this plugin]:PATH:_default' \ 60 | '*--use=[Which files to use in this plugin]:MATCH:_default' \ 61 | '*--apply=[Templates to apply to this plugin]:TEMPLATE:_default' \ 62 | '*--profiles=[Only use this plugin under one of the given profiles]:PROFILES:_default' \ 63 | '*--hooks=[Hooks executed during template evaluation]:SCRIPT:_default' \ 64 | '-h[Print help]' \ 65 | '--help[Print help]' \ 66 | ':name -- A unique name for this plugin:_default' \ 67 | && ret=0 68 | ;; 69 | (edit) 70 | _arguments "${_arguments_options[@]}" : \ 71 | '-h[Print help]' \ 72 | '--help[Print help]' \ 73 | && ret=0 74 | ;; 75 | (remove) 76 | _arguments "${_arguments_options[@]}" : \ 77 | '-h[Print help]' \ 78 | '--help[Print help]' \ 79 | ':name -- A unique name for this plugin:_default' \ 80 | && ret=0 81 | ;; 82 | (lock) 83 | _arguments "${_arguments_options[@]}" : \ 84 | '--update[Update all plugin sources]' \ 85 | '(--update)--reinstall[Reinstall all plugin sources]' \ 86 | '-h[Print help]' \ 87 | '--help[Print help]' \ 88 | && ret=0 89 | ;; 90 | (source) 91 | _arguments "${_arguments_options[@]}" : \ 92 | '--relock[Regenerate the lock file]' \ 93 | '--update[Update all plugin sources (implies --relock)]' \ 94 | '(--update)--reinstall[Reinstall all plugin sources (implies --relock)]' \ 95 | '-h[Print help]' \ 96 | '--help[Print help]' \ 97 | && ret=0 98 | ;; 99 | (completions) 100 | _arguments "${_arguments_options[@]}" : \ 101 | '--shell=[The type of shell]:SHELL:(bash elvish fish powershell zsh)' \ 102 | '-h[Print help]' \ 103 | '--help[Print help]' \ 104 | && ret=0 105 | ;; 106 | (version) 107 | _arguments "${_arguments_options[@]}" : \ 108 | '-h[Print help]' \ 109 | '--help[Print help]' \ 110 | && ret=0 111 | ;; 112 | esac 113 | ;; 114 | esac 115 | } 116 | 117 | (( $+functions[_sheldon_commands] )) || 118 | _sheldon_commands() { 119 | local commands; commands=( 120 | 'init:Initialize a new config file' \ 121 | 'add:Add a new plugin to the config file' \ 122 | 'edit:Open up the config file in the default editor' \ 123 | 'remove:Remove a plugin from the config file' \ 124 | 'lock:Install the plugins sources and generate the lock file' \ 125 | 'source:Generate and print out the script' \ 126 | 'completions:Generate completions for the given shell' \ 127 | 'version:Prints detailed version information' \ 128 | ) 129 | _describe -t commands 'sheldon commands' commands "$@" 130 | } 131 | (( $+functions[_sheldon__add_commands] )) || 132 | _sheldon__add_commands() { 133 | local commands; commands=() 134 | _describe -t commands 'sheldon add commands' commands "$@" 135 | } 136 | (( $+functions[_sheldon__completions_commands] )) || 137 | _sheldon__completions_commands() { 138 | local commands; commands=() 139 | _describe -t commands 'sheldon completions commands' commands "$@" 140 | } 141 | (( $+functions[_sheldon__edit_commands] )) || 142 | _sheldon__edit_commands() { 143 | local commands; commands=() 144 | _describe -t commands 'sheldon edit commands' commands "$@" 145 | } 146 | (( $+functions[_sheldon__init_commands] )) || 147 | _sheldon__init_commands() { 148 | local commands; commands=() 149 | _describe -t commands 'sheldon init commands' commands "$@" 150 | } 151 | (( $+functions[_sheldon__lock_commands] )) || 152 | _sheldon__lock_commands() { 153 | local commands; commands=() 154 | _describe -t commands 'sheldon lock commands' commands "$@" 155 | } 156 | (( $+functions[_sheldon__remove_commands] )) || 157 | _sheldon__remove_commands() { 158 | local commands; commands=() 159 | _describe -t commands 'sheldon remove commands' commands "$@" 160 | } 161 | (( $+functions[_sheldon__source_commands] )) || 162 | _sheldon__source_commands() { 163 | local commands; commands=() 164 | _describe -t commands 'sheldon source commands' commands "$@" 165 | } 166 | (( $+functions[_sheldon__version_commands] )) || 167 | _sheldon__version_commands() { 168 | local commands; commands=() 169 | _describe -t commands 'sheldon version commands' commands "$@" 170 | } 171 | 172 | if [ "$funcstack[1]" = "_sheldon" ]; then 173 | _sheldon "$@" 174 | else 175 | compdef _sheldon sheldon 176 | fi 177 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | book 2 | -------------------------------------------------------------------------------- /docs/README_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # sheldon 2 | 3 | *Fast, configurable, shell plugin manager* 4 | 5 | [![Crates.io version](https://badgers.space/crates/version/sheldon)](https://crates.io/crates/sheldon) 6 | [![Download](https://badgers.space/github/release/rossmacarthur/sheldon)](https://github.com/rossmacarthur/sheldon/releases/latest) 7 | [![License](https://badgers.space/github/license/rossmacarthur/sheldon)](https://github.com/rossmacarthur/sheldon#license) 8 | [![Build Status](https://badgers.space/github/checks/rossmacarthur/sheldon/trunk?label=build)](https://github.com/rossmacarthur/sheldon/actions/workflows/build.yaml) 9 | 10 | ## Features 11 | 12 | - Plugins from Git repositories. 13 | - Branch / tag / commit support. 14 | - Submodule support. 15 | - First class support for GitHub repositories. 16 | - First class support for Gists. 17 | - Arbitrary remote scripts or binary plugins. 18 | - Local plugins. 19 | - Inline plugins. 20 | - Highly configurable install methods using templates. 21 | - Shell agnostic, with sensible defaults for Bash or Zsh 22 | - Super-fast plugin loading and parallel installation. See [benchmarks]. 23 | - Config file using [TOML](https://toml.io) syntax. 24 | - Clean `~/.bashrc` or `~/.zshrc` (just add 1 line). 25 | 26 | [benchmarks]: https://github.com/rossmacarthur/zsh-plugin-manager-benchmark 27 | 28 | ## Table of Contents 29 | 30 | {{ toc -}} 31 | - [💡 Examples](#-examples) 32 | - [License](#license) 33 | 34 | {{ full_contents }} 35 | 36 | ## 💡 Examples 37 | 38 | You can find many examples including deferred loading of plugins in the 39 | [documentation](https://sheldon.cli.rs/Examples.html). 40 | 41 | ## License 42 | 43 | Licensed under either of 44 | 45 | - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or 46 | http://www.apache.org/licenses/LICENSE-2.0) 47 | - MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 48 | 49 | at your option. 50 | -------------------------------------------------------------------------------- /docs/book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | authors = ["Ross MacArthur"] 3 | title = "sheldon docs" 4 | 5 | [output.html] 6 | git-repository-url = "https://github.com/rossmacarthur/sheldon" 7 | no-section-label = true 8 | 9 | [output.linkcheck] 10 | # See https://github.com/rust-lang/crates.io/issues/788 for why we exclude this. 11 | exclude = ['crates.io'] 12 | -------------------------------------------------------------------------------- /docs/src/Command-line-interface.md: -------------------------------------------------------------------------------- 1 | # 💻 Command line interface 2 | 3 | Sheldon has three different types of commands. 4 | 5 | - [`init`](#init) initializes a new config file. 6 | - [`lock`](#lock) and [`source`](#source) deal with plugin downloading, 7 | installation, and generation of shell source code. 8 | - [`add`](#add), [`edit`](#edit), and [`remove`](#remove) automate editing of 9 | the config file. 10 | 11 | ## `init` 12 | 13 | This command initializes a new config file. If a config file exists then this 14 | command does nothing. 15 | 16 | For example 17 | 18 | ```sh 19 | sheldon init 20 | ``` 21 | 22 | Or you can specify the shell. 23 | 24 | ```sh 25 | sheldon init --shell bash 26 | ``` 27 | 28 | or 29 | 30 | ```sh 31 | sheldon init --shell zsh 32 | ``` 33 | 34 | ## `lock` 35 | 36 | The `lock` command installs the plugins sources and generates the lock file. 37 | Rerunning this command without any extra options will not reinstall plugin 38 | sources, just verify that they are correctly installed. It will always 39 | regenerate the lock file. 40 | 41 | ```sh 42 | sheldon lock 43 | ``` 44 | 45 | To update all plugin sources you can use the `--update` flag. 46 | 47 | ```sh 48 | sheldon lock --update 49 | ``` 50 | 51 | To force a reinstall of all plugin sources you can use the `--reinstall` flag. 52 | 53 | ```sh 54 | sheldon lock --reinstall 55 | ``` 56 | 57 | ## `source` 58 | 59 | This command generates the shell script. This command will first check if there 60 | is an up to date lock file, if not, then it will first do the equivalent of the 61 | lock command above. This command is usually used with the built-in shell `eval` 62 | command. 63 | 64 | ```sh 65 | eval "$(sheldon source)" 66 | ``` 67 | 68 | But you can also run it directly to inspect the output. The output of this 69 | command is highly configurable. You can define your own custom templates to 70 | apply to your plugins. 71 | 72 | ## `add` 73 | 74 | This command adds a new plugin to the config file. It does nothing else but edit 75 | the config file. In the following command we add a GitHub repository as a 76 | source. 77 | 78 | ```sh 79 | sheldon add my-repo --git https://github.com/owner/repo.git 80 | ``` 81 | 82 | An example usage of this command for each source type is shown in the 83 | [Configuration](Configuration.md) section. 84 | 85 | ## `edit` 86 | 87 | This command will open the config file in the default editor and only overwrite 88 | the contents if the updated config file is valid. To override the editor that is 89 | used you should set the `EDITOR` environment variable. 90 | 91 | For example using `vim` 92 | 93 | ```sh 94 | EDITOR=vim sheldon edit 95 | ``` 96 | 97 | Or with Visual Studio Code 98 | 99 | ```sh 100 | EDITOR="code --wait" sheldon edit 101 | ``` 102 | 103 | ## `remove` 104 | 105 | This command removes a plugin from the config file. It does nothing else but 106 | edit the config file. In the following command we remove the plugin with name 107 | `my-repo`. 108 | 109 | ```sh 110 | sheldon remove my-repo 111 | ``` 112 | 113 | ## Options 114 | 115 | Sheldon accepts the following global command line options and environment 116 | variables. You can also view all options by running Sheldon with `-h` or 117 | `--help`. The value that will be used for the option follows the following 118 | priority. 119 | 120 | 1. Command line option. 121 | 2. Environment variable. 122 | 3. Default value. 123 | 124 | #### `--color ` 125 | 126 | Set the output coloring. 127 | 128 | - `always`: Always use colored output. 129 | - `auto`: Automatically determine whether to use colored output (*default*). 130 | - `never`: Never use colored output. 131 | 132 | #### `--config-dir ` 133 | 134 | *Environment variable:* `SHELDON_CONFIG_DIR` 135 | 136 | Set the config directory where the configuration file will be stored. This 137 | defaults to `$XDG_CONFIG_HOME/sheldon` or `~/.config/sheldon`. 138 | 139 | #### `--data-dir ` 140 | 141 | *Environment variable:* `SHELDON_DATA_DIR` 142 | 143 | Set the data directory where plugins will be downloaded to. This defaults to 144 | `$XDG_DATA_HOME/sheldon` or `~/.local/share/sheldon`. 145 | 146 | #### `--config-file ` 147 | 148 | *Environment variable:* `SHELDON_CONFIG_FILE` 149 | 150 | Set the path to the config file. This defaults to `/plugins.toml` 151 | where `` is the config directory. 152 | 153 | #### `--profile ` 154 | 155 | *Environment variable:* `SHELDON_PROFILE` 156 | 157 | Specify the profile to match plugins against. Plugins which have 158 | [profiles](Configuration.md#profiles) configured will only get loaded if one of 159 | the given profiles matches the profile. 160 | 161 | ## Completions 162 | 163 | Shell completion scripts for Bash and Zsh are available. If Sheldon was 164 | installed via Homebrew then the completions should have been installed 165 | automatically. 166 | 167 | They can also be generated by Sheldon using the `completions` subcommand which 168 | will output the completions to stdout. Refer to your specific shell 169 | documentation for more details on how to install these. 170 | 171 | ``` 172 | sheldon completions --shell bash > /path/to/completions/sheldon.bash 173 | ``` 174 | 175 | or 176 | 177 | ``` 178 | sheldon completions --shell zsh > /path/to/completions/_sheldon 179 | ``` 180 | -------------------------------------------------------------------------------- /docs/src/Configuration.md: -------------------------------------------------------------------------------- 1 | # ⚙️ Configuration 2 | 3 | ## Plugin sources 4 | 5 | A plugin is defined by adding a new unique name to the `plugins` table in the 6 | [TOML](https://toml.io) config file. This can be done by either editing the file 7 | directly or using the provided Sheldon commands. A plugin must provide the 8 | location of the source. There are three types of sources, each kind is described 9 | in this section. A plugin may only specify *one* source type. 10 | 11 | ```toml 12 | # ~/.config/sheldon/plugins.toml 13 | 14 | # ┌─ Unique name for the plugin 15 | # ┌──┴─┐ 16 | [plugins.base16] 17 | github = "chriskempson/base16-shell" 18 | # └─────┬────┘ └─────┬────┘ 19 | # │ └─ GitHub repository name 20 | # └─ GitHub user or organization 21 | ``` 22 | 23 | ### Git 24 | 25 | Git sources specify a remote Git repository that will be cloned to the Sheldon 26 | data directory. There are three flavors of Git sources. 27 | 28 | #### `github` 29 | 30 | A GitHub source must set the `github` field and specify the repository. This 31 | should be the username or organization and the repository name separated by a 32 | forward slash. Add the following to the Sheldon config file. 33 | 34 | ```toml 35 | [plugins.example] 36 | github = "owner/repo" 37 | ``` 38 | 39 | Or run `add` with the `--github` option. 40 | 41 | ```sh 42 | sheldon add example --github owner/repo 43 | ``` 44 | 45 | #### `gist` 46 | 47 | A Gist source must set the `gist` field and specify the repository. This should 48 | be the hash or username and hash of the Gist. Add the following to the Sheldon 49 | config file. 50 | 51 | ```toml 52 | [plugins.example] 53 | gist = "579d02802b1cc17baed07753d09f5009" 54 | ``` 55 | 56 | Or run `add` with the `--gist` option. 57 | 58 | ```sh 59 | sheldon add example --gist 579d02802b1cc17baed07753d09f5009 60 | ``` 61 | 62 | #### `git` 63 | 64 | A Git source must set the `git` field and specify the URL to clone. Add the 65 | following to the Sheldon config file. 66 | 67 | ```toml 68 | [plugins.example] 69 | git = "https://github.com/owner/repo" 70 | ``` 71 | 72 | Or run `add` with the `--git` option. 73 | 74 | ```sh 75 | sheldon add example --git https://github.com/owner/repo 76 | ``` 77 | 78 | #### Specifying a branch, tag, or commit 79 | 80 | All Git sources also allow setting of one of the `branch`, `tag` or `rev` 81 | fields. Sheldon will then checkout the repository at this reference. 82 | 83 | ```toml 84 | [plugins.example] 85 | github = "owner/repo" 86 | tag = "v0.1.0" 87 | ``` 88 | 89 | Or run `add` with the `--tag`, `--branch`, or `--rev` option. 90 | 91 | ```sh 92 | sheldon add example --github owner/repo --tag v0.1.0 93 | ``` 94 | 95 | #### Cloning with Git or SSH protocols 96 | 97 | GitHub and Gist sources are cloned using HTTPS by default. You can specify that 98 | Git or SSH should be used by setting the `proto` field to the protocol type. 99 | This must be one of `git`, `https`, or `ssh`. 100 | 101 | ```toml 102 | [plugins.example] 103 | github = "owner/repo" 104 | proto = "ssh" 105 | ``` 106 | 107 | For a plain Git source you should specify the URL with a `git://` or `ssh://`. 108 | For SSH you will need to specify the username as well (it is `git` for GitHub). 109 | 110 | ```toml 111 | [plugins.example] 112 | git = "ssh://git@github.com/owner/repo" 113 | ``` 114 | 115 | #### Private Git repositories 116 | 117 | Currently Sheldon only supports authentication when cloning using SSH and 118 | requires an SSH agent to provide credentials. This means if you have a plugin 119 | source that is a private repository you will have to use the SSH protocol for 120 | cloning. 121 | 122 | ### Remote 123 | 124 | Remote sources specify a remote file that will be downloaded by Sheldon. A 125 | remote source must set the `remote` field and specify the URL. Add the following 126 | to the Sheldon config file. 127 | 128 | ```toml 129 | [plugins.example] 130 | remote = "https://github.com/owner/repo/raw/branch/plugin.zsh" 131 | ``` 132 | 133 | Or run `add` with the `--remote` option. 134 | 135 | ```sh 136 | sheldon add example --remote https://github.com/owner/repo/raw/branch/plugin.zsh 137 | ``` 138 | 139 | ### Local 140 | 141 | Local sources reference local directories. A local source must set the `local` 142 | field and specify a directory. Tildes may be used and will be expanded to the 143 | current user's home directory. Add the following to the Sheldon config file. 144 | 145 | ```toml 146 | [plugins.example] 147 | local = "~/Downloads/plugin" 148 | ``` 149 | 150 | Or run `add` with the `--local` option. 151 | 152 | ```sh 153 | sheldon add example --local '~/Downloads/plugin' 154 | ``` 155 | 156 | ## Plugin options 157 | 158 | These are options that are common to all the above plugins. 159 | 160 | ### `use` 161 | 162 | A list of files / globs to use in the plugin's source directory. If this field 163 | is not given then the first pattern in the global [`match`](#match) field that 164 | matches any files will be used. Add the following to the Sheldon config file. 165 | 166 | ```toml 167 | [plugins.example] 168 | github = "owner/repo" 169 | use = ["*.zsh"] 170 | ``` 171 | 172 | Or run `add` with the `--use` option when adding the plugin. 173 | 174 | ```sh 175 | sheldon add example --github owner/repo --use '*.zsh' 176 | ``` 177 | 178 | ### `apply` 179 | 180 | A list of template names to apply to this plugin. This defaults to the global 181 | [`apply`](#apply-1). 182 | 183 | ```toml 184 | [plugins.example] 185 | github = "owner/repo" 186 | apply = ["source", "PATH"] 187 | ``` 188 | 189 | Or run `add` with the `--apply` option when adding the plugin. 190 | 191 | ```sh 192 | sheldon add example --github owner/repo --apply source PATH 193 | ``` 194 | 195 | You can define your own [custom templates](#custom-templates) to apply to your 196 | plugins. 197 | 198 | ### `profiles` 199 | 200 | A list of profiles this plugin should be used in. If this field is not given the 201 | plugin will be used regardless of the profile. Otherwise, the plugin is only 202 | used if the specified [profile](Command-line-interface.md#--profile-profile) is 203 | included in the configured list of profiles. 204 | 205 | ### `hooks` 206 | 207 | Statements executed around plugin installation. 208 | 209 | ```toml 210 | [plugins.example] 211 | github = "owner/repo" 212 | 213 | [plugins.example.hooks] 214 | pre = "export TEST=test" 215 | post = "unset TEST" 216 | ``` 217 | 218 | ## Inline plugins 219 | 220 | For convenience it also possible to define Inline plugins. An Inline plugin must 221 | set the `inline` field and specify the raw source. 222 | 223 | ```toml 224 | [plugins.example] 225 | inline = 'example() { echo "Just an example of inline shell code" }' 226 | ``` 227 | 228 | ## Templates 229 | 230 | A template defines how the shell source for a particular plugin is generated. 231 | For example the **PATH** template adds the plugin directory to the shell `PATH` 232 | variable. A template will be applied to a plugin if you add the template name to 233 | the [`apply`](#apply) field on a plugin. 234 | 235 | Available built-in templates are different depending on what shell you are 236 | using. The following are available for both Bash and Zsh. 237 | 238 | * **source**: source each file in a plugin. 239 | * **PATH**: add the plugin directory to the `PATH` variable. 240 | 241 | If you are using Zsh then the following are also available. 242 | 243 | * **path**: add the plugin directory to the `path` variable. 244 | * **fpath**: add the plugin directory to the `fpath` variable. 245 | 246 | As template strings in the config file they could be represented like the 247 | following. 248 | 249 | ```toml 250 | [templates] 251 | source = """ 252 | {{ hooks?.pre | nl }}{% for file in files %}source "{{ file }}" 253 | {% endfor %}{{ hooks?.post | nl }}""" 254 | PATH = 'export PATH="{{ dir }}:$PATH"' 255 | path = 'path=( "{{ dir }}" $path )' 256 | fpath = 'fpath=( "{{ dir }}" $fpath )' 257 | ``` 258 | 259 | For example if we change the `apply` field for the below plugin, it will only 260 | add the plugin directory to the `PATH` and append it to the `fpath`. The plugin 261 | will not be sourced. 262 | 263 | ```toml 264 | [plugins.example] 265 | github = "owner/repo" 266 | apply = ["PATH", "fpath"] 267 | ``` 268 | 269 | ### Custom templates 270 | 271 | It is possible to create your own custom templates, and you can even override 272 | the built-in ones. 273 | 274 | Plugins all have the following information that can be used in templates. 275 | 276 | * **A unique name.** This is completely arbitrary, and it is the value specified 277 | for the plugin in the plugins table. However, it is often the name of the 278 | plugin, so it can be useful to use this name in templates with `{{ name }}`. 279 | 280 | * **A directory.** For Git sources this is the location of the cloned 281 | repository, for local sources, it is the directory specified. This directory 282 | can be used in templates with `{{ dir }}`. 283 | 284 | * **One or more files.** These are the matched files in the plugin directory 285 | either discovered using the the global `match` field or specified as a plugin 286 | option with `use`. These can be used in templates by iterating over the files. 287 | For example: `{% for file in files %} ... {{ file }} ... {% endfor %}`. 288 | 289 | * **Hooks** Hooks are taken directly from the configuration and can be used as 290 | `{{ hooks.[KEY] }}`. 291 | 292 | To add or update a template add a new key to the `[templates]` table in the 293 | config file. Take a look at the [examples](Examples.md) for some interesting 294 | applications of this. 295 | 296 | ## Global options 297 | 298 | ### `shell` 299 | 300 | Indicates the shell that you are using. This setting will affect the default 301 | values for several global config settings. This includes the global 302 | [`match`](#match) setting and the available templates. This defaults to `zsh`. 303 | 304 | ```toml 305 | shell = "bash" 306 | ``` 307 | 308 | or 309 | 310 | ```toml 311 | shell = "zsh" 312 | ``` 313 | 314 | ### `match` 315 | 316 | A list of glob patterns to match against a plugin's contents. The first pattern 317 | that matches any files will be used by default as a plugin's `use` field. This 318 | defaults to 319 | 320 | ```toml 321 | match = [ 322 | "{{ name }}.plugin.zsh", 323 | "{{ name }}.zsh", 324 | "{{ name }}.sh", 325 | "{{ name }}.zsh-theme", 326 | "*.plugin.zsh", 327 | "*.zsh", 328 | "*.sh", 329 | "*.zsh-theme" 330 | ] 331 | ``` 332 | 333 | If the shell is Bash then this defaults to 334 | 335 | ```toml 336 | match = [ 337 | "{{ name }}.plugin.bash", 338 | "{{ name }}.plugin.sh", 339 | "{{ name }}.bash", 340 | "{{ name }}.sh", 341 | "*.plugin.bash", 342 | "*.plugin.sh", 343 | "*.bash", 344 | "*.sh" 345 | ] 346 | ``` 347 | 348 | ### `apply` 349 | 350 | A list of template names to apply to all plugins by default (see 351 | [`apply`](#apply)). This defaults to 352 | 353 | ```toml 354 | apply = ["source"] 355 | ``` 356 | -------------------------------------------------------------------------------- /docs/src/Examples.md: -------------------------------------------------------------------------------- 1 | # 💡 Examples 2 | 3 | This section demonstrates the configuration file contents for some common 4 | installation practices as well how to configure popular plugins and themes. 5 | 6 | ## Deferred loading of plugins in Zsh 7 | 8 | A commonly desired feature of shell plugin managers is deferred loading of 9 | plugins because of the massive increase in speed that it provides. Because 10 | Sheldon is not written in a shell language it cannot provide the level of 11 | integration that other plugin managers can. However, it is pretty easy to get 12 | deferred loading working with Sheldon using 13 | [romkatv/zsh-defer](https://github.com/romkatv/zsh-defer). 14 | 15 | Firstly, you should add `zsh-defer` as a plugin. 16 | 17 | ```toml 18 | [plugins.zsh-defer] 19 | github = "romkatv/zsh-defer" 20 | ``` 21 | 22 | Important: the `zsh-defer` plugin definition should be placed before any plugins 23 | that will use the `defer` template. Sheldon always processes plugins in the 24 | order they are defined in the config file. 25 | 26 | Then add a template that calls `zsh-defer source` instead of just `source`. 27 | 28 | ```toml 29 | [templates] 30 | defer = "{{ hooks?.pre | nl }}{% for file in files %}zsh-defer source \"{{ file }}\"\n{% endfor %}{{ hooks?.post | nl }}" 31 | ``` 32 | 33 | Now any plugin that you want to defer you can apply the `defer` template. For 34 | example if you wanted to defer loading of `zsh-syntax-highlighting`. 35 | 36 | ```toml 37 | [plugins.zsh-syntax-highlighting] 38 | github = "zsh-users/zsh-syntax-highlighting" 39 | apply = ["defer"] 40 | ``` 41 | 42 | ## Overriding the PATH template 43 | 44 | The built-in **PATH** template adds the directory path to the beginning of the 45 | `PATH` variable, we might want to change it to the be added at the end. We could 46 | do this like this 47 | 48 | ```toml 49 | [templates] 50 | PATH = 'export PATH="$PATH:{{ dir }}"' 51 | ``` 52 | 53 | You can then apply it to the plugin like this 54 | 55 | ```toml 56 | [plugins.example] 57 | github = "owner/repo" 58 | apply = ["source", "PATH"] 59 | ``` 60 | 61 | **Note:** this would change the behavior of **PATH** for *all* plugins using it. 62 | 63 | ## Zsh frameworks 64 | 65 | ### [ohmyzsh](https://github.com/ohmyzsh/ohmyzsh) 66 | 67 | Add the following to the Sheldon config file. 68 | 69 | ```toml 70 | [plugins.oh-my-zsh] 71 | github = "ohmyzsh/ohmyzsh" 72 | ``` 73 | 74 | Or run the following to automatically add it. 75 | 76 | ```sh 77 | sheldon add oh-my-zsh --github "ohmyzsh/ohmyzsh" 78 | ``` 79 | 80 | Add the following to your `~/.zshrc` file. 81 | 82 | ```sh 83 | # ~/.zshrc 84 | 85 | export ZSH="$HOME/.local/share/sheldon/repos/github.com/ohmyzsh/ohmyzsh" 86 | 87 | # Oh My Zsh settings here 88 | 89 | eval "$(sheldon source)" 90 | ``` 91 | 92 | ## Zsh plugins 93 | 94 | ### [autosuggestions](https://github.com/zsh-users/zsh-autosuggestions) 95 | 96 | Add the following to the Sheldon config file. 97 | 98 | ```toml 99 | [plugins.zsh-autosuggestions] 100 | github = "zsh-users/zsh-autosuggestions" 101 | use = ["{{ name }}.zsh"] 102 | ``` 103 | 104 | Or run the following to automatically add it. 105 | 106 | ```sh 107 | sheldon add zsh-autosuggestions --github zsh-users/zsh-autosuggestions --use '{{ name }}.zsh' 108 | ``` 109 | 110 | ### [autojump](https://github.com/wting/autojump) 111 | 112 | Add the following to the Sheldon config file. 113 | 114 | ```toml 115 | [plugins.autojump] 116 | github = "wting/autojump" 117 | dir = "bin" 118 | apply = ["PATH", "source"] 119 | ``` 120 | 121 | Or run the following to automatically add it. 122 | 123 | ```sh 124 | sheldon add autojump --github wting/autojump --dir bin --apply PATH source 125 | ``` 126 | 127 | ### [syntax-highlighting](https://github.com/zsh-users/zsh-syntax-highlighting) 128 | 129 | Add the following to the Sheldon config file. 130 | 131 | ```toml 132 | [plugins.zsh-syntax-highlighting] 133 | github = "zsh-users/zsh-syntax-highlighting" 134 | ``` 135 | 136 | Or run the following to automatically add it. 137 | 138 | ```sh 139 | sheldon add zsh-syntax-highlighting --github zsh-users/zsh-syntax-highlighting 140 | ``` 141 | 142 | ### [blackbox](https://github.com/StackExchange/blackbox) 143 | 144 | Add the following to the Sheldon config file. 145 | 146 | ```toml 147 | [plugins.blackbox] 148 | github = "StackExchange/blackbox" 149 | ``` 150 | 151 | Or run the following to automatically add it. 152 | 153 | ```sh 154 | sheldon add blackbox --github StackExchange/blackbox 155 | ``` 156 | 157 | ### [z.lua](https://github.com/skywind3000/z.lua) 158 | 159 | Add the following to the Sheldon config file. 160 | 161 | ```toml 162 | [plugins."z.lua"] 163 | github = "skywind3000/z.lua" 164 | ``` 165 | 166 | Or run the following to automatically add it. 167 | 168 | ```sh 169 | sheldon add z.lua --github skywind3000/z.lua 170 | ``` 171 | 172 | ### [enhancd](https://github.com/b4b4r07/enhancd) 173 | 174 | Add the following to the Sheldon config file. 175 | 176 | ```toml 177 | [plugins.enhancd] 178 | github = "b4b4r07/enhancd" 179 | ``` 180 | 181 | Or run the following to automatically add it. 182 | 183 | ```sh 184 | sheldon add enhancd --github b4b4r07/enhancd 185 | ``` 186 | 187 | ### [base16](https://github.com/chriskempson/base16-shell) 188 | 189 | Add the following to the Sheldon config file. 190 | 191 | ```toml 192 | [plugins.base16] 193 | github = "chriskempson/base16-shell" 194 | ``` 195 | 196 | Or run the following to automatically add it. 197 | 198 | ```sh 199 | sheldon add base16 --github chriskempson/base16-shell 200 | ``` 201 | 202 | ## Zsh themes 203 | 204 | ### [powerlevel10k](https://github.com/romkatv/powerlevel10k) 205 | 206 | Add the following to the Sheldon config file. 207 | 208 | ```toml 209 | [plugins.powerlevel10k] 210 | github = "romkatv/powerlevel10k" 211 | ``` 212 | 213 | Or run the following to automatically add it. 214 | 215 | ``` 216 | sheldon add powerlevel10k --github romkatv/powerlevel10k 217 | ``` 218 | 219 | ### [spaceship](https://github.com/spaceship-prompt/spaceship-prompt) 220 | 221 | Add the following to the Sheldon config file. 222 | 223 | ```toml 224 | [plugins.spaceship] 225 | github = "spaceship-prompt/spaceship-prompt" 226 | ``` 227 | 228 | Or run the following to automatically add it. 229 | 230 | ```sh 231 | sheldon add spaceship --github spaceship-prompt/spaceship-prompt 232 | ``` 233 | 234 | ### [pure](https://github.com/sindresorhus/pure) 235 | 236 | Add the following to the Sheldon config file. 237 | 238 | ```toml 239 | [plugins.pure] 240 | github = "sindresorhus/pure" 241 | use = ["async.zsh", "pure.zsh"] 242 | ``` 243 | 244 | Or run the following to automatically add it. 245 | 246 | ```sh 247 | sheldon add pure --github sindresorhus/pure --use async.zsh pure.zsh 248 | ``` 249 | -------------------------------------------------------------------------------- /docs/src/Getting-started.md: -------------------------------------------------------------------------------- 1 | # 🚀 Getting started 2 | 3 | ## Initializing 4 | 5 | Sheldon works by specifying plugin information in a [TOML](https://toml.io) 6 | configuration file, `plugins.toml`. You can initialize this file by running 7 | `sheldon init`. 8 | 9 | ```sh 10 | sheldon init --shell bash 11 | ``` 12 | 13 | or 14 | 15 | ```sh 16 | sheldon init --shell zsh 17 | ``` 18 | 19 | This will create `plugins.toml` under `$XDG_CONFIG_HOME/sheldon`, on most 20 | systems this will be `~/.config/sheldon/plugins.toml`. You can either edit this 21 | file directly or use the provided command line interface to add or remove 22 | plugins. 23 | 24 | ## Adding a plugin 25 | 26 | To add your first plugin append the following to the Sheldon config file. 27 | 28 | ```toml 29 | # ~/.config/sheldon/plugins.toml 30 | 31 | [plugins.base16] 32 | github = "chriskempson/base16-shell" 33 | ``` 34 | 35 | Or use the `add` command to automatically add it. 36 | 37 | ```sh 38 | sheldon add base16 --github chriskempson/base16-shell 39 | ``` 40 | 41 | The first argument given here `base16` is a unique name for the plugin. The 42 | `--github` option specifies that we want Sheldon to manage a clone of the 43 | [https://github.com/chriskempson/base16-shell](https://github.com/chriskempson/base16-shell) 44 | repository. 45 | 46 | ## Loading plugins 47 | 48 | You can then use `sheldon source` to install this plugin, generate a lock file, 49 | and print out the shell script to source. Simply add the following to your 50 | `~/.zshrc` or `~/.bashrc` file. 51 | 52 | ```sh 53 | # ~/.zshrc or ~/.bashrc 54 | 55 | eval "$(sheldon source)" 56 | ``` 57 | -------------------------------------------------------------------------------- /docs/src/Installation.md: -------------------------------------------------------------------------------- 1 | # 📦 Installation 2 | 3 | ## Nix 4 | 5 | This repository is a flake, and can be installed using nix profile: 6 | 7 | ``` 8 | nix profile install "github:rossmacarthur/sheldon" 9 | ``` 10 | 11 | ## Homebrew 12 | 13 | Sheldon can be installed using Homebrew. 14 | 15 | ```sh 16 | brew install sheldon 17 | ``` 18 | 19 | ## Cargo 20 | 21 | Sheldon can be installed from [Crates.io](https://crates.io/crates/sheldon) 22 | using [Cargo](https://doc.rust-lang.org/cargo/), the Rust package manager. 23 | 24 | ```sh 25 | cargo install sheldon 26 | ``` 27 | 28 | In some circumstances this can fail due to the fact that Cargo does not use 29 | `Cargo.lock` file by default. You can force Cargo to use it using the `--locked` 30 | option. 31 | 32 | ```sh 33 | cargo install sheldon --locked 34 | ``` 35 | 36 | ## Cargo BInstall 37 | 38 | Sheldon can be installed using 39 | [`cargo-binstall`](https://github.com/cargo-bins/cargo-binstall), which will 40 | download the release artifacts directly from the GitHub release. 41 | 42 | ```sh 43 | cargo binstall sheldon 44 | ``` 45 | 46 | ## Pre-built binaries 47 | 48 | Pre-built binaries for Linux (x86-64, aarch64, armv7) and macOS (x86-64) are 49 | provided. These can be downloaded directly from the [the releases 50 | page](https://github.com/rossmacarthur/sheldon/releases). 51 | 52 | Alternatively, the following script can be used to automatically detect your host 53 | system, download the required artifact, and extract the `sheldon` binary to the 54 | given directory. 55 | 56 | ```sh 57 | curl --proto '=https' -fLsS https://rossmacarthur.github.io/install/crate.sh \ 58 | | bash -s -- --repo rossmacarthur/sheldon --to ~/.local/bin 59 | ``` 60 | 61 | ## Building from source 62 | 63 | Sheldon is written in Rust, so to install it from source you will first need to 64 | install Rust and Cargo using [rustup](https://rustup.rs/). Then you can run the 65 | following to build Sheldon. 66 | 67 | ```sh 68 | git clone https://github.com/rossmacarthur/sheldon.git 69 | cd sheldon 70 | cargo build --release 71 | ``` 72 | 73 | The binary will be found at `target/release/sheldon`. 74 | -------------------------------------------------------------------------------- /docs/src/Introduction.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | Sheldon is a fast, configurable, command-line tool to manage your shell plugins. 4 | 5 | ## How does it work? 6 | 7 | Plugins are specified in a [TOML](https://toml.io) configuration file and 8 | Sheldon renders an install script using user configurable templates. 9 | 10 | A `~/.zshrc` or `~/.bashrc` that uses Sheldon simply contains the following. 11 | 12 | ```sh 13 | eval "$(sheldon source)" 14 | ``` 15 | 16 | Sheldon can manage GitHub or Git repositories, Gists, arbitrary remote scripts 17 | or binaries, local plugins, and inline plugins. Plugins are installed and 18 | updated in parallel and as a result Sheldon is blazingly fast. 19 | 20 | ## Source code 21 | 22 | Sheldon is open source and you can find the code on 23 | [GitHub](https://github.com/rossmacarthur/sheldon). 24 | 25 | ## License 26 | 27 | Sheldon and its source code is licensed under either of 28 | 29 | - [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) 30 | - [MIT license](http://opensource.org/licenses/MIT) 31 | 32 | at your option. 33 | -------------------------------------------------------------------------------- /docs/src/RELEASES.md: -------------------------------------------------------------------------------- 1 | ../../RELEASES.md -------------------------------------------------------------------------------- /docs/src/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | [Introduction](Introduction.md) 4 | 5 | - [📦 Installation](Installation.md) 6 | - [🚀 Getting started](Getting-started.md) 7 | - [💻 Command line interface](Command-line-interface.md) 8 | - [⚙️ Configuration](Configuration.md) 9 | - [💡 Examples](Examples.md) 10 | - [📝 Release notes](RELEASES.md) 11 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-utils": { 4 | "inputs": { 5 | "systems": "systems" 6 | }, 7 | "locked": { 8 | "lastModified": 1710146030, 9 | "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", 10 | "owner": "numtide", 11 | "repo": "flake-utils", 12 | "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", 13 | "type": "github" 14 | }, 15 | "original": { 16 | "owner": "numtide", 17 | "repo": "flake-utils", 18 | "type": "github" 19 | } 20 | }, 21 | "nixpkgs": { 22 | "locked": { 23 | "lastModified": 1717602782, 24 | "narHash": "sha256-pL9jeus5QpX5R+9rsp3hhZ+uplVHscNJh8n8VpqscM0=", 25 | "owner": "NixOS", 26 | "repo": "nixpkgs", 27 | "rev": "e8057b67ebf307f01bdcc8fba94d94f75039d1f6", 28 | "type": "github" 29 | }, 30 | "original": { 31 | "owner": "NixOS", 32 | "ref": "nixos-unstable", 33 | "repo": "nixpkgs", 34 | "type": "github" 35 | } 36 | }, 37 | "root": { 38 | "inputs": { 39 | "flake-utils": "flake-utils", 40 | "nixpkgs": "nixpkgs", 41 | "rust-overlay": "rust-overlay" 42 | } 43 | }, 44 | "rust-overlay": { 45 | "inputs": { 46 | "flake-utils": [ 47 | "flake-utils" 48 | ], 49 | "nixpkgs": [ 50 | "nixpkgs" 51 | ] 52 | }, 53 | "locked": { 54 | "lastModified": 1717640276, 55 | "narHash": "sha256-xtWJuHl0Zq1/pibvU7V8+qad4fcNLP4yerO54nr4Hec=", 56 | "owner": "oxalica", 57 | "repo": "rust-overlay", 58 | "rev": "96161e19677e2ae639c41147e422da4d6ec48240", 59 | "type": "github" 60 | }, 61 | "original": { 62 | "owner": "oxalica", 63 | "repo": "rust-overlay", 64 | "type": "github" 65 | } 66 | }, 67 | "systems": { 68 | "locked": { 69 | "lastModified": 1681028828, 70 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 71 | "owner": "nix-systems", 72 | "repo": "default", 73 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 74 | "type": "github" 75 | }, 76 | "original": { 77 | "owner": "nix-systems", 78 | "repo": "default", 79 | "type": "github" 80 | } 81 | } 82 | }, 83 | "root": "root", 84 | "version": 7 85 | } 86 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs = { 3 | nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; 4 | flake-utils.url = "github:numtide/flake-utils"; 5 | rust-overlay = { 6 | url = "github:oxalica/rust-overlay"; 7 | inputs = { 8 | nixpkgs.follows = "nixpkgs"; 9 | flake-utils.follows = "flake-utils"; 10 | }; 11 | }; 12 | }; 13 | outputs = { self, nixpkgs, flake-utils, rust-overlay }: 14 | flake-utils.lib.eachDefaultSystem 15 | (system: 16 | let 17 | overlays = [ (import rust-overlay) ]; 18 | pkgs = import nixpkgs { 19 | inherit system overlays; 20 | }; 21 | in 22 | with pkgs; 23 | { 24 | packages.sheldon = pkgs.callPackage ./sheldon.nix { 25 | inherit (pkgs.darwin.apple_sdk.frameworks) Security; 26 | }; 27 | packages.default = self.outputs.packages.${system}.sheldon; 28 | defaultPackage = self.packages.${system}.sheldon; 29 | devShells.default = mkShell { 30 | buildInputs = [ rust-bin.stable.latest.default ]; 31 | }; 32 | } 33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /onedoc.toml: -------------------------------------------------------------------------------- 1 | [[doc]] 2 | input = [ 3 | "docs/src/Installation.md", 4 | "docs/src/Getting-started.md", 5 | "docs/src/Command-line-interface.md", 6 | "docs/src/Configuration.md", 7 | ] 8 | output = "README.md" 9 | template = "docs/README_TEMPLATE.md" 10 | 11 | [links] 12 | "Command-line-interface.md" = "https://sheldon.cli.rs/Command-line-interface.html" 13 | "Configuration.md" = "https://sheldon.cli.rs/Configuration.html" 14 | "Examples.md" = "https://sheldon.cli.rs/Examples.html" 15 | -------------------------------------------------------------------------------- /sheldon.nix: -------------------------------------------------------------------------------- 1 | # Sheldon package definition 2 | # 3 | # This file will be similar to the package definition in nixpkgs: 4 | # https://github.com/NixOS/nixpkgs/blob/master/pkgs/by-name/sh/sheldon/package.nix 5 | # 6 | # Helpful documentation: https://github.com/NixOS/nixpkgs/blob/master/doc/languages-frameworks/rust.section.md 7 | { 8 | pkgs, 9 | lib, 10 | stdenv, 11 | installShellFiles, 12 | rustPlatform, 13 | Security, 14 | }: 15 | rustPlatform.buildRustPackage { 16 | name = "sheldon"; 17 | 18 | src = lib.cleanSource ./.; 19 | 20 | cargoLock = { 21 | lockFile = ./Cargo.lock; 22 | # Allow dependencies to be fetched from git and avoid having to set the outputHashes manually 23 | allowBuiltinFetchGit = true; 24 | }; 25 | 26 | nativeBuildInputs = [installShellFiles]; 27 | 28 | buildInputs = [ pkgs.openssl pkgs.curl ] ++ lib.optionals stdenv.isDarwin [Security]; 29 | 30 | doCheck = false; 31 | 32 | meta = with lib; { 33 | description = "Fast, configurable, shell plugin manager"; 34 | homepage = "https://github.com/rossmacarthur/sheldon"; 35 | license = licenses.mit; 36 | mainProgram = "sheldon"; 37 | }; 38 | } 39 | -------------------------------------------------------------------------------- /src/cli/color_choice.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::io; 3 | use std::io::IsTerminal; 4 | use std::str::FromStr; 5 | 6 | use thiserror::Error; 7 | 8 | /// Whether messages should use color output. 9 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 10 | pub enum ColorChoice { 11 | /// Force color output. 12 | Always, 13 | /// Intelligently guess whether to use color output. 14 | Auto, 15 | /// Force disable color output. 16 | Never, 17 | } 18 | 19 | impl Default for ColorChoice { 20 | fn default() -> Self { 21 | Self::Auto 22 | } 23 | } 24 | 25 | impl fmt::Display for ColorChoice { 26 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 27 | match self { 28 | Self::Always => f.write_str("always"), 29 | Self::Auto => f.write_str("auto"), 30 | Self::Never => f.write_str("never"), 31 | } 32 | } 33 | } 34 | 35 | #[derive(Debug, Error)] 36 | #[error("expected `always` or `never`, got `{}`", self.0)] 37 | pub struct ParseColorChoiceError(String); 38 | 39 | impl FromStr for ColorChoice { 40 | type Err = ParseColorChoiceError; 41 | 42 | fn from_str(s: &str) -> Result { 43 | match s { 44 | "always" => Ok(Self::Always), 45 | "auto" => Ok(Self::Auto), 46 | "never" => Ok(Self::Never), 47 | s => Err(ParseColorChoiceError(s.to_string())), 48 | } 49 | } 50 | } 51 | 52 | impl ColorChoice { 53 | pub fn is_color(self) -> bool { 54 | match self { 55 | Self::Always => true, 56 | Self::Auto => io::stderr().is_terminal(), 57 | Self::Never => false, 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/cli/mod.rs: -------------------------------------------------------------------------------- 1 | //! Command line interface. 2 | 3 | mod color_choice; 4 | mod raw; 5 | 6 | #[cfg(test)] 7 | mod tests; 8 | 9 | use std::env; 10 | use std::io; 11 | use std::path::{Path, PathBuf}; 12 | use std::process; 13 | 14 | use anyhow::{anyhow, Context as ResultExt, Result}; 15 | use clap::{CommandFactory, Parser}; 16 | 17 | use crate::cli::raw::{Add, RawCommand, RawOpt}; 18 | use crate::config::{EditPlugin, GitReference, RawPlugin, Shell}; 19 | use crate::context::{log_error, Context, Output, Verbosity}; 20 | use crate::lock::LockMode; 21 | use crate::util::build; 22 | 23 | /// Parse the command line arguments. 24 | /// 25 | /// In the event of failure it will print the error message and quit the program 26 | /// without returning. 27 | pub fn from_args() -> Opt { 28 | Opt::from_raw_opt(RawOpt::parse()) 29 | } 30 | 31 | /// Resolved command line options with defaults set. 32 | #[derive(Debug)] 33 | pub struct Opt { 34 | /// Global context for use across the entire program. 35 | pub ctx: Context, 36 | /// The subcommand. 37 | pub command: Command, 38 | } 39 | 40 | /// The resolved command. 41 | #[derive(Debug)] 42 | pub enum Command { 43 | /// Initialize a new config file. 44 | Init { shell: Option }, 45 | /// Add a new plugin to the config file. 46 | Add { 47 | name: String, 48 | plugin: Box, 49 | }, 50 | /// Open up the config file in the default editor. 51 | Edit, 52 | /// Remove a plugin from the config file. 53 | Remove { name: String }, 54 | /// Install the plugins sources and generate the lock file. 55 | Lock, 56 | /// Generate and print out the script. 57 | Source, 58 | } 59 | 60 | impl Opt { 61 | fn from_raw_opt(raw_opt: RawOpt) -> Self { 62 | let RawOpt { 63 | quiet, 64 | non_interactive, 65 | verbose, 66 | color, 67 | data_dir, 68 | config_dir, 69 | config_file, 70 | profile, 71 | command, 72 | } = raw_opt; 73 | 74 | let mut lock_mode = None; 75 | 76 | let command = match command { 77 | RawCommand::Init { shell } => Command::Init { shell }, 78 | RawCommand::Add(add) => { 79 | let (name, plugin) = EditPlugin::from_add(*add); 80 | Command::Add { 81 | name, 82 | plugin: Box::new(plugin), 83 | } 84 | } 85 | RawCommand::Edit => Command::Edit, 86 | RawCommand::Remove { name } => Command::Remove { name }, 87 | RawCommand::Lock { update, reinstall } => { 88 | lock_mode = LockMode::from_lock_flags(update, reinstall); 89 | Command::Lock 90 | } 91 | RawCommand::Source { 92 | relock, 93 | update, 94 | reinstall, 95 | } => { 96 | lock_mode = LockMode::from_source_flags(relock, update, reinstall); 97 | Command::Source 98 | } 99 | RawCommand::Completions { shell } => { 100 | let mut app = RawOpt::command(); 101 | clap_complete::generate(shell, &mut app, build::CRATE_NAME, &mut io::stdout()); 102 | process::exit(0); 103 | } 104 | RawCommand::Version => { 105 | println!("{} {}", build::CRATE_NAME, build::CRATE_VERBOSE_VERSION); 106 | process::exit(0); 107 | } 108 | }; 109 | 110 | let verbosity = if quiet { 111 | Verbosity::Quiet 112 | } else if verbose { 113 | Verbosity::Verbose 114 | } else { 115 | Verbosity::Normal 116 | }; 117 | 118 | let output = Output { 119 | verbosity, 120 | no_color: !color.is_color(), 121 | }; 122 | 123 | let home = match home::home_dir() { 124 | Some(home) => home, 125 | None => { 126 | let err = anyhow!("failed to determine the current user's home directory"); 127 | log_error(output.no_color, &err); 128 | process::exit(1); 129 | } 130 | }; 131 | 132 | let (config_dir, data_dir, config_file) = 133 | match resolve_paths(&home, config_dir, data_dir, config_file) { 134 | Ok(paths) => paths, 135 | Err(err) => { 136 | log_error(output.no_color, &err); 137 | process::exit(1); 138 | } 139 | }; 140 | let lock_file = match profile.as_deref() { 141 | Some("") | None => data_dir.join("plugins.lock"), 142 | Some(p) => data_dir.join(format!("plugins.{p}.lock")), 143 | }; 144 | let clone_dir = data_dir.join("repos"); 145 | let download_dir = data_dir.join("downloads"); 146 | 147 | let ctx = Context { 148 | version: build::CRATE_RELEASE.to_string(), 149 | home, 150 | config_dir, 151 | data_dir, 152 | config_file, 153 | lock_file, 154 | clone_dir, 155 | download_dir, 156 | profile, 157 | output, 158 | interactive: !non_interactive, 159 | lock_mode, 160 | }; 161 | 162 | Self { ctx, command } 163 | } 164 | } 165 | 166 | impl EditPlugin { 167 | fn from_add(add: Add) -> (String, Self) { 168 | let Add { 169 | name, 170 | git, 171 | gist, 172 | github, 173 | remote, 174 | local, 175 | proto, 176 | branch, 177 | rev, 178 | tag, 179 | dir, 180 | uses, 181 | apply, 182 | profiles, 183 | hooks, 184 | } = add; 185 | 186 | let hooks = hooks.map(|h| h.into_iter().collect()); 187 | 188 | let reference = match (branch, rev, tag) { 189 | (Some(s), None, None) => Some(GitReference::Branch(s)), 190 | (None, Some(s), None) => Some(GitReference::Rev(s)), 191 | (None, None, Some(s)) => Some(GitReference::Tag(s)), 192 | (None, None, None) => None, 193 | // this is unreachable because these three options are in the same mutually exclusive 194 | // 'git-reference' CLI group 195 | _ => unreachable!(), 196 | }; 197 | 198 | ( 199 | name, 200 | Self::from(RawPlugin { 201 | git, 202 | gist, 203 | github, 204 | remote, 205 | local, 206 | inline: None, 207 | proto, 208 | reference, 209 | dir, 210 | uses, 211 | apply, 212 | profiles, 213 | hooks, 214 | rest: None, 215 | }), 216 | ) 217 | } 218 | } 219 | 220 | impl LockMode { 221 | fn from_lock_flags(update: bool, reinstall: bool) -> Option { 222 | match (update, reinstall) { 223 | (false, false) => Some(Self::Normal), 224 | (true, false) => Some(Self::Update), 225 | (false, true) => Some(Self::Reinstall), 226 | (true, true) => unreachable!(), 227 | } 228 | } 229 | 230 | fn from_source_flags(relock: bool, update: bool, reinstall: bool) -> Option { 231 | match (relock, update, reinstall) { 232 | (false, false, false) => None, 233 | (true, false, false) => Some(Self::Normal), 234 | (_, true, false) => Some(Self::Update), 235 | (_, false, true) => Some(Self::Reinstall), 236 | (_, true, true) => unreachable!(), 237 | } 238 | } 239 | } 240 | 241 | fn resolve_paths( 242 | home: &Path, 243 | config_dir: Option, 244 | data_dir: Option, 245 | config_file: Option, 246 | ) -> Result<(PathBuf, PathBuf, PathBuf)> { 247 | let (config_dir, config_file) = match (config_dir, config_file) { 248 | // If both are set, then use them as is 249 | (Some(dir), Some(file)) => (dir, file), 250 | // If only the config file is set, then derive the directory from the file 251 | (None, Some(file)) => { 252 | let dir = file 253 | .parent() 254 | .with_context(|| { 255 | format!( 256 | "failed to get parent directory of config file path `{}`", 257 | file.display() 258 | ) 259 | })? 260 | .to_path_buf(); 261 | (dir, file) 262 | } 263 | // If only the config directory is set, then derive the file from the directory 264 | (Some(dir), None) => { 265 | let file = dir.join("plugins.toml"); 266 | (dir, file) 267 | } 268 | // If neither are set, then use the default config directory and file 269 | (None, None) => { 270 | let dir = default_config_dir(home); 271 | let file = dir.join("plugins.toml"); 272 | (dir, file) 273 | } 274 | }; 275 | 276 | let data_dir = data_dir.unwrap_or_else(|| default_data_dir(home)); 277 | 278 | Ok((config_dir, data_dir, config_file)) 279 | } 280 | 281 | fn default_config_dir(home: &Path) -> PathBuf { 282 | let mut p = env::var_os("XDG_CONFIG_HOME") 283 | .map(PathBuf::from) 284 | .unwrap_or_else(|| home.join(".config")); 285 | p.push("sheldon"); 286 | p 287 | } 288 | 289 | fn default_data_dir(home: &Path) -> PathBuf { 290 | let mut p = env::var_os("XDG_DATA_HOME") 291 | .map(PathBuf::from) 292 | .unwrap_or_else(|| home.join(".local/share")); 293 | p.push("sheldon"); 294 | p 295 | } 296 | -------------------------------------------------------------------------------- /src/cli/raw.rs: -------------------------------------------------------------------------------- 1 | #![deny(missing_docs)] 2 | 3 | use std::path::PathBuf; 4 | 5 | use clap::builder::PossibleValue; 6 | use clap::{ArgGroup, Parser}; 7 | use clap_complete as complete; 8 | use url::Url; 9 | 10 | use crate::cli::color_choice::ColorChoice; 11 | use crate::config::{GistRepository, GitHubRepository, GitProtocol, Shell}; 12 | use crate::util::build; 13 | 14 | const HELP_TEMPLATE: &str = "\ 15 | {before-help}{bin} {version} 16 | {author} 17 | {about} 18 | 19 | {usage-heading} 20 | {tab}{usage} 21 | 22 | {all-args}{after-help}"; 23 | 24 | #[derive(Debug, PartialEq, Eq, Parser)] 25 | #[clap( 26 | author, 27 | version = build::CRATE_RELEASE, 28 | long_version = build::CRATE_LONG_VERSION, 29 | about, 30 | long_about = None, 31 | help_template = HELP_TEMPLATE, 32 | disable_help_subcommand(true), 33 | subcommand_required(true), 34 | )] 35 | pub struct RawOpt { 36 | /// Suppress any informational output. 37 | #[clap(long, short)] 38 | pub quiet: bool, 39 | 40 | /// Suppress any interactive prompts and assume "yes" as the answer. 41 | #[clap(long)] 42 | pub non_interactive: bool, 43 | 44 | /// Use verbose output. 45 | #[clap(long, short)] 46 | pub verbose: bool, 47 | 48 | /// Output coloring. 49 | #[clap(long, value_name = "WHEN", default_value_t)] 50 | pub color: ColorChoice, 51 | 52 | /// The configuration directory. 53 | #[clap(long, value_name = "PATH", env = "SHELDON_CONFIG_DIR")] 54 | pub config_dir: Option, 55 | 56 | /// The data directory 57 | #[clap(long, value_name = "PATH", env = "SHELDON_DATA_DIR")] 58 | pub data_dir: Option, 59 | 60 | /// The config file. 61 | #[clap(long, value_name = "PATH", env = "SHELDON_CONFIG_FILE")] 62 | pub config_file: Option, 63 | 64 | /// The profile used for conditional plugins. 65 | #[clap(long, value_name = "PROFILE", env = "SHELDON_PROFILE")] 66 | pub profile: Option, 67 | 68 | /// The subcommand to run. 69 | #[clap(subcommand)] 70 | pub command: RawCommand, 71 | } 72 | 73 | #[derive(Debug, PartialEq, Eq, Parser)] 74 | pub enum RawCommand { 75 | /// Initialize a new config file. 76 | Init { 77 | /// The type of shell. 78 | #[clap(long, value_name = "SHELL")] 79 | shell: Option, 80 | }, 81 | 82 | /// Add a new plugin to the config file. 83 | Add(Box), 84 | 85 | /// Open up the config file in the default editor. 86 | Edit, 87 | 88 | /// Remove a plugin from the config file. 89 | Remove { 90 | /// A unique name for this plugin. 91 | #[clap(value_name = "NAME")] 92 | name: String, 93 | }, 94 | 95 | /// Install the plugins sources and generate the lock file. 96 | Lock { 97 | /// Update all plugin sources. 98 | #[clap(long)] 99 | update: bool, 100 | 101 | /// Reinstall all plugin sources. 102 | #[clap(long, conflicts_with = "update")] 103 | reinstall: bool, 104 | }, 105 | 106 | /// Generate and print out the script. 107 | Source { 108 | /// Regenerate the lock file. 109 | #[clap(long)] 110 | relock: bool, 111 | 112 | /// Update all plugin sources (implies --relock). 113 | #[clap(long)] 114 | update: bool, 115 | 116 | /// Reinstall all plugin sources (implies --relock). 117 | #[clap(long, conflicts_with = "update")] 118 | reinstall: bool, 119 | }, 120 | 121 | /// Generate completions for the given shell. 122 | Completions { 123 | /// The type of shell. 124 | #[clap(long, value_name = "SHELL")] 125 | shell: complete::Shell, 126 | }, 127 | 128 | /// Prints detailed version information. 129 | Version, 130 | } 131 | 132 | #[derive(Debug, PartialEq, Eq, Parser)] 133 | #[clap( 134 | group = ArgGroup::new("plugin").required(true), 135 | group = ArgGroup::new("git-reference").conflicts_with_all(&["remote", "local"]), 136 | )] 137 | pub struct Add { 138 | /// A unique name for this plugin. 139 | #[clap(value_name = "NAME")] 140 | pub name: String, 141 | 142 | /// Add a clonable Git repository. 143 | #[clap(long, value_name = "URL", group = "plugin")] 144 | pub git: Option, 145 | 146 | /// Add a clonable Gist snippet. 147 | #[clap(long, value_name = "ID", group = "plugin")] 148 | pub gist: Option, 149 | 150 | /// Add a clonable GitHub repository. 151 | #[clap(long, value_name = "REPO", group = "plugin")] 152 | pub github: Option, 153 | 154 | /// Add a downloadable file. 155 | #[clap(long, value_name = "URL", group = "plugin")] 156 | pub remote: Option, 157 | 158 | /// Add a local directory. 159 | #[clap(long, value_name = "DIR", group = "plugin")] 160 | pub local: Option, 161 | 162 | /// The Git protocol for a Gist or GitHub plugin. 163 | #[clap(long, value_name = "PROTO", conflicts_with_all = &["git", "remote", "local"])] 164 | pub proto: Option, 165 | 166 | /// Checkout the tip of a branch. 167 | #[clap(long, value_name = "BRANCH", group = "git-reference")] 168 | pub branch: Option, 169 | 170 | /// Checkout a specific commit. 171 | #[clap(long, value_name = "SHA", group = "git-reference")] 172 | pub rev: Option, 173 | 174 | /// Checkout a specific tag. 175 | #[clap(long, value_name = "TAG", group = "git-reference")] 176 | pub tag: Option, 177 | 178 | /// Which sub directory to use in this plugin. 179 | #[clap(long, value_name = "PATH")] 180 | pub dir: Option, 181 | 182 | /// Which files to use in this plugin. 183 | #[clap(long = "use", value_name = "MATCH", num_args(1..))] 184 | pub uses: Option>, 185 | 186 | /// Templates to apply to this plugin. 187 | #[clap(long, value_name = "TEMPLATE", num_args(1..))] 188 | pub apply: Option>, 189 | 190 | /// Only use this plugin under one of the given profiles 191 | #[clap(long, value_name = "PROFILES", num_args(1..))] 192 | pub profiles: Option>, 193 | 194 | /// Hooks executed during template evaluation. 195 | #[clap(long, value_name = "SCRIPT", value_parser = key_value_parser, num_args(1..))] 196 | pub hooks: Option>, 197 | } 198 | 199 | impl clap::ValueEnum for ColorChoice { 200 | fn value_variants<'a>() -> &'a [Self] { 201 | &[ColorChoice::Auto, ColorChoice::Always, ColorChoice::Never] 202 | } 203 | 204 | fn to_possible_value(&self) -> Option { 205 | Some(match self { 206 | ColorChoice::Auto => PossibleValue::new("auto"), 207 | ColorChoice::Always => PossibleValue::new("always"), 208 | ColorChoice::Never => PossibleValue::new("never"), 209 | }) 210 | } 211 | } 212 | 213 | impl clap::ValueEnum for Shell { 214 | fn value_variants<'a>() -> &'a [Self] { 215 | &[Shell::Bash, Shell::Zsh] 216 | } 217 | 218 | fn to_possible_value(&self) -> Option { 219 | Some(match self { 220 | Shell::Bash => PossibleValue::new("bash"), 221 | Shell::Zsh => PossibleValue::new("zsh"), 222 | }) 223 | } 224 | } 225 | 226 | fn key_value_parser(s: &str) -> Result<(String, String), String> { 227 | match s.split_once('=') { 228 | Some((k, v)) => Ok((k.to_string(), v.to_string())), 229 | _ => Err(format!("{s} isn't a valid key-value pair separated with =")), 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /src/cli/testdata/raw_opt_add_help.golden: -------------------------------------------------------------------------------- 1 | Add a new plugin to the config file 2 | 3 | Usage: sheldon add [OPTIONS] <--git |--gist |--github |--remote |--local > 4 | 5 | Arguments: 6 | A unique name for this plugin 7 | 8 | Options: 9 | --git Add a clonable Git repository 10 | --gist Add a clonable Gist snippet 11 | --github Add a clonable GitHub repository 12 | --remote Add a downloadable file 13 | --local Add a local directory 14 | --proto The Git protocol for a Gist or GitHub plugin 15 | --branch Checkout the tip of a branch 16 | --rev Checkout a specific commit 17 | --tag Checkout a specific tag 18 | --dir Which sub directory to use in this plugin 19 | --use ... Which files to use in this plugin 20 | --apply