├── cellar ├── .gitkeep ├── bin │ └── .gitkeep ├── man │ └── .gitkeep ├── packages │ └── .gitkeep └── completions │ ├── bash │ └── .gitkeep │ └── zsh │ ├── compctl │ └── .gitkeep │ └── compsys │ └── .gitkeep ├── .gitignore ├── bin └── basher ├── lib ├── include.dash ├── include.bash ├── include.zsh └── include.sh ├── Makefile ├── tests ├── libexec │ └── basher-echo ├── lib │ ├── commands.bash │ ├── mocks.bash │ └── package_helpers.bash ├── fixtures │ └── commands │ │ └── basher-_clone │ │ └── basher-_clone ├── basher-_unlink-man.bats ├── basher-package-path.bats ├── basher-completions.bats ├── basher-_commands.bats ├── basher-_link-man.bats ├── basher-_deps.bats ├── basher-outdated.bats ├── test_helper.bash ├── basher-_unlink-completions.bats ├── include.bats ├── basher-_link-completions.bats ├── basher-uninstall.bats ├── basher-upgrade.bats ├── basher.bats ├── basher-help.bats ├── basher-init.bats ├── basher-list.bats ├── basher-_unlink-bins.bats ├── basher-_clone.bats ├── basher-link.bats ├── basher-_link-bins.bats └── basher-install.bats ├── libexec ├── basher-_link ├── basher-commands ├── basher-_unlink ├── basher-_unlink-man ├── basher-package-path ├── basher-_link-man ├── basher-completions ├── basher-outdated ├── basher-_commands ├── basher-_deps ├── basher-_unlink-bins ├── basher-_unlink-completions ├── basher-uninstall ├── basher-upgrade ├── basher-_link-bins ├── basher-_link-completions ├── basher-_clone ├── basher ├── basher-link ├── basher-list ├── basher-install ├── basher-init └── basher-help ├── .projections.json ├── .gitmodules ├── Dockerfile ├── completions ├── basher.zsh ├── basher.bash └── basher.fish ├── CONTRIBUTING.md ├── LICENSE ├── .github └── workflows │ └── ci.yml ├── uninstall.sh ├── install.sh └── README.md /cellar/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | cellar/ 2 | -------------------------------------------------------------------------------- /cellar/bin/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cellar/man/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /bin/basher: -------------------------------------------------------------------------------- 1 | ../libexec/basher -------------------------------------------------------------------------------- /cellar/packages/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cellar/completions/bash/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cellar/completions/zsh/compctl/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cellar/completions/zsh/compsys/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/include.dash: -------------------------------------------------------------------------------- 1 | . "$BASHER_ROOT/lib/include.sh" 2 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | bats tests 3 | 4 | .PHONY: test 5 | -------------------------------------------------------------------------------- /lib/include.bash: -------------------------------------------------------------------------------- 1 | source "$BASHER_ROOT/lib/include.sh" 2 | -------------------------------------------------------------------------------- /lib/include.zsh: -------------------------------------------------------------------------------- 1 | source "$BASHER_ROOT/lib/include.sh" 2 | -------------------------------------------------------------------------------- /tests/libexec/basher-echo: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | eval "echo \$$1" 4 | -------------------------------------------------------------------------------- /tests/lib/commands.bash: -------------------------------------------------------------------------------- 1 | create_command() { 2 | echo "$2" > "$BASHER_TMP_BIN/$1" 3 | chmod +x "$BASHER_TMP_BIN/$1" 4 | } 5 | -------------------------------------------------------------------------------- /libexec/basher-_link: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | package="$1" 4 | 5 | basher-_link-bins "$package" 6 | basher-_link-completions "$package" 7 | basher-_link-man "$package" 8 | -------------------------------------------------------------------------------- /libexec/basher-commands: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Summary: List all available basher commands 4 | # Usage: basher commands 5 | 6 | set -e 7 | 8 | basher-_commands basher 9 | -------------------------------------------------------------------------------- /libexec/basher-_unlink: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | package="$1" 4 | 5 | basher-_unlink-man "$package" 6 | basher-_unlink-bins "$package" 7 | basher-_unlink-completions "$package" 8 | -------------------------------------------------------------------------------- /.projections.json: -------------------------------------------------------------------------------- 1 | { 2 | "libexec/*": { 3 | "alternate": "tests/{}.bats" 4 | }, 5 | "tests/*.bats": { 6 | "runner": "bats", 7 | "alternate": "libexec/{}" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/fixtures/commands/basher-_clone/basher-_clone: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | use_ssh="$1" 4 | site="$2" 5 | package="$3" 6 | 7 | git clone "${BASHER_ORIGIN_DIR}/$package" "${BASHER_PACKAGES_PATH}/$package" 8 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "tests/vendor/bats-core"] 2 | path = tests/vendor/bats-core 3 | url = https://github.com/ztombol/bats-core 4 | [submodule "tests/vendor/bats-assert"] 5 | path = tests/vendor/bats-assert 6 | url = https://github.com/ztombol/bats-assert 7 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ARG bashver=latest 2 | 3 | FROM bash:${bashver} 4 | 5 | RUN apk add --no-cache git; \ 6 | git config --global user.email "user@example.com"; \ 7 | git config --global user.name "User Name"; 8 | 9 | COPY . /opt/basher/ 10 | 11 | ENTRYPOINT ["bash", "/opt/basher/bats/bin/bats"] 12 | -------------------------------------------------------------------------------- /completions/basher.zsh: -------------------------------------------------------------------------------- 1 | if [[ ! -o interactive ]]; then 2 | return 3 | fi 4 | 5 | compctl -K _basher basher 6 | 7 | _basher() { 8 | local words completions 9 | read -cA words 10 | 11 | if [ "${#words}" -eq 2 ]; then 12 | completions="$(basher commands)" 13 | else 14 | completions="$(basher completions ${words[2,-2]})" 15 | fi 16 | 17 | reply=("${(ps:\n:)completions}") 18 | } 19 | -------------------------------------------------------------------------------- /libexec/basher-_unlink-man: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | package="$1" 6 | 7 | shopt -s nullglob 8 | 9 | files=("$BASHER_PACKAGES_PATH/$package"/man/*) 10 | files=("${files[@]##*/}") 11 | 12 | pattern="\.([1-9])\$" 13 | 14 | for file in "${files[@]}" 15 | do 16 | if [[ "$file" =~ $pattern ]]; then 17 | n="${BASH_REMATCH[1]}" 18 | rm -f "$BASHER_INSTALL_MAN/man${n}/${file}" 19 | fi 20 | done 21 | -------------------------------------------------------------------------------- /libexec/basher-package-path: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Summary: Outputs the path for a package 4 | # Usage: source "$(basher package-path )/file.sh" 5 | 6 | set -e 7 | 8 | # TAG completions 9 | if [ "$1" == "--complete" ]; then 10 | exec basher-list 11 | fi 12 | 13 | if [ "$#" -ne 1 ]; then 14 | basher-help package-path 15 | exit 1 16 | fi 17 | 18 | package="$1" 19 | 20 | echo "$BASHER_PACKAGES_PATH/$package" 21 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Commits 4 | 5 | Write a [good commit message][commit]. 6 | 7 | ## Tests 8 | 9 | To run the tests, install bats: 10 | 11 | ~~~ sh 12 | $ basher install sstephenson/bats 13 | ~~~ 14 | 15 | update submodules: 16 | 17 | ~~~ sh 18 | $ git submodule update --init 19 | ~~~ 20 | 21 | and then run: 22 | 23 | ~~~ sh 24 | $ make 25 | ~~~ 26 | 27 | [commit]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html 28 | -------------------------------------------------------------------------------- /tests/lib/mocks.bash: -------------------------------------------------------------------------------- 1 | mock_command() { 2 | local command="$1" 3 | mkdir -p "${BASHER_TEST_DIR}/path/$command" 4 | cat > "${BASHER_TEST_DIR}/path/$command/$command" <)/file.sh\"" 9 | } 10 | 11 | @test "outputs the package path" { 12 | mock_clone 13 | create_package username/package 14 | basher-install username/package 15 | 16 | run basher-package-path username/package 17 | assert_success "$BASHER_PACKAGES_PATH/username/package" 18 | } 19 | -------------------------------------------------------------------------------- /tests/basher-completions.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load test_helper 4 | 5 | @test "command with no completion support" { 6 | create_command "basher-hello" "#!$BASH 7 | echo hello" 8 | run basher-completions hello 9 | assert_success "" 10 | } 11 | 12 | @test "command with completion support" { 13 | create_command "basher-hello" "#!$BASH 14 | # TAG completions 15 | if [[ \$1 = --complete ]]; then 16 | echo hello 17 | else 18 | exit 1 19 | fi" 20 | run basher-completions hello 21 | assert_success "hello" 22 | } 23 | -------------------------------------------------------------------------------- /tests/basher-_commands.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load test_helper 4 | 5 | @test "without arguments prints usage" { 6 | run basher-_commands 7 | assert_failure 8 | assert_line "Usage: basher _commands " 9 | } 10 | 11 | @test "lists commands" { 12 | run basher-_commands basher 13 | assert_success 14 | assert_line init 15 | assert_line help 16 | assert_line commands 17 | } 18 | 19 | @test "does not list hidden commands" { 20 | run basher-_commands basher 21 | assert_success 22 | refute_line _commands 23 | } 24 | -------------------------------------------------------------------------------- /libexec/basher-completions: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Usage: basher completions 3 | # Summary: List completions for a command. 4 | # 5 | # In order to support completions, a command must have a comment 6 | # with the 'completions' TAG and provide a '--completions' argument. 7 | 8 | set -e 9 | 10 | COMMAND="$1" 11 | if [ -z "$COMMAND" ]; then 12 | basher-help completions 13 | exit 1 14 | fi 15 | 16 | COMMAND_PATH="$(command -v "basher-$COMMAND")" 17 | if grep -E "^([#%]|--|//) TAG completions" "$COMMAND_PATH" >/dev/null; then 18 | exec "$COMMAND_PATH" --complete 19 | fi 20 | -------------------------------------------------------------------------------- /lib/include.sh: -------------------------------------------------------------------------------- 1 | include() { 2 | local package="$1" 3 | local file="$2" 4 | 5 | if [ -z "$package" ] || [ -z "$file" ]; then 6 | echo "Usage: include " >&2 7 | return 1 8 | fi 9 | 10 | if [ ! -e "$BASHER_PREFIX/packages/$package" ]; then 11 | echo "Package not installed: $package" >&2 12 | return 1 13 | fi 14 | 15 | if [ -e "$BASHER_PREFIX/packages/$package/$file" ]; then 16 | . "$BASHER_PREFIX/packages/$package/$file" >&2 17 | else 18 | echo "File not found: $BASHER_PREFIX/packages/$package/$file" >&2 19 | return 1 20 | fi 21 | } 22 | -------------------------------------------------------------------------------- /completions/basher.fish: -------------------------------------------------------------------------------- 1 | function __fish_basher_needs_command 2 | set cmd (commandline -opc) 3 | if [ (count $cmd) -eq 1 -a $cmd[1] = 'basher' ] 4 | return 0 5 | end 6 | return 1 7 | end 8 | 9 | function __fish_basher_using_command 10 | set cmd (commandline -opc) 11 | if [ (count $cmd) -gt 1 ] 12 | if [ $argv[1] = $cmd[2] ] 13 | return 0 14 | end 15 | end 16 | return 1 17 | end 18 | 19 | complete -f -c basher -n '__fish_basher_needs_command' -a '(basher commands)' 20 | for cmd in (basher commands) 21 | complete -f -c basher -n "__fish_basher_using_command $cmd" -a "(basher completions $cmd)" 22 | end 23 | -------------------------------------------------------------------------------- /libexec/basher-outdated: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Summary: Displays a list of outdated packages 4 | # Usage: basher outdated 5 | 6 | set -e 7 | 8 | shopt -s nullglob 9 | 10 | IFS=$'\r\n' packages=($(basher list)) 11 | 12 | for package in "${packages[@]}" 13 | do 14 | package_path="$BASHER_PACKAGES_PATH/$package" 15 | if [ ! -L "$package_path" ]; then 16 | cd "$package_path" 17 | git remote update > /dev/null 2>&1 18 | if git symbolic-ref --short -q HEAD > /dev/null; then 19 | if [ "$(git rev-list --count HEAD...HEAD@{upstream})" -gt 0 ]; then 20 | echo "$package" 21 | fi 22 | fi 23 | fi 24 | done 25 | -------------------------------------------------------------------------------- /tests/basher-_link-man.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load test_helper 4 | 5 | @test "links each man page to install-man under correct subdirectory" { 6 | create_package username/package 7 | create_man username/package exec.1 8 | create_man username/package exec.2 9 | mock_clone 10 | basher-_clone false site username/package 11 | 12 | run basher-_link-man username/package 13 | echo "$output" 14 | assert_success 15 | assert [ "$(readlink $BASHER_INSTALL_MAN/man1/exec.1)" = "${BASHER_PACKAGES_PATH}/username/package/man/exec.1" ] 16 | assert [ "$(readlink $BASHER_INSTALL_MAN/man2/exec.2)" = "${BASHER_PACKAGES_PATH}/username/package/man/exec.2" ] 17 | } 18 | -------------------------------------------------------------------------------- /libexec/basher-_commands: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Summary: List all available commands for a package 4 | # Usage: basher _commands 5 | # 6 | # Looks for executable files prefixed with package- and 7 | # display each suffix as an available command. 8 | 9 | set -e 10 | 11 | if [ "$#" -ne 1 ]; then 12 | basher-help _commands 13 | exit 1 14 | fi 15 | 16 | package="$1" 17 | 18 | IFS=: paths=($PATH) 19 | 20 | shopt -s nullglob 21 | 22 | { for path in "${paths[@]}"; do 23 | for command in "${path}/$package-"*; do 24 | command="${command##*$package-}" 25 | if [[ ! "$command" == _* ]]; then 26 | echo "${command}" 27 | fi 28 | done 29 | done 30 | } | sort | uniq 31 | -------------------------------------------------------------------------------- /libexec/basher-_deps: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Usage: basher _deps 4 | # Summary: Globally installs package runtime dependencies 5 | # 6 | # Installs the package dependencies, specified with the 7 | # DEPS= variable on package.sh. 8 | # 9 | # Example: DEPS=username/repo1:otheruser/repo2 10 | 11 | set -e 12 | 13 | if [ "$#" -ne 1 ]; then 14 | basher-help _deps 15 | exit 1 16 | fi 17 | 18 | package="$1" 19 | 20 | if [ ! -e "$BASHER_PACKAGES_PATH/$package/package.sh" ]; then 21 | exit 22 | fi 23 | 24 | shopt -s nullglob 25 | 26 | source "$BASHER_PACKAGES_PATH/$package/package.sh" 27 | IFS=: read -ra deps <<< "$DEPS" 28 | 29 | for dep in "${deps[@]}" 30 | do 31 | basher-install "$dep" 32 | done 33 | -------------------------------------------------------------------------------- /libexec/basher-_unlink-bins: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | package="$1" 4 | 5 | if [ -e "$BASHER_PACKAGES_PATH/$package/package.sh" ]; then 6 | source "$BASHER_PACKAGES_PATH/$package/package.sh" 7 | IFS=: read -ra bins <<< "$BINS" 8 | fi 9 | 10 | if [ -z "$bins" ]; then 11 | if [ -e "$BASHER_PACKAGES_PATH/$package/bin" ]; then 12 | bins=("$BASHER_PACKAGES_PATH/$package"/bin/*) 13 | bins=("${bins[@]##*/}") 14 | bins=("${bins[@]/#/bin/}") 15 | else 16 | bins=($(find "$BASHER_PACKAGES_PATH/$package" -maxdepth 1 -perm -u+x -type f -or -type l)) 17 | bins=("${bins[@]##*/}") 18 | fi 19 | fi 20 | 21 | for bin in "${bins[@]}" 22 | do 23 | name="${bin##*/}" 24 | if ${REMOVE_EXTENSION:-false}; then 25 | name="${name%%.*}" 26 | fi 27 | rm -f "$BASHER_INSTALL_BIN/${name}" 28 | done 29 | -------------------------------------------------------------------------------- /libexec/basher-_unlink-completions: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | package="$1" 4 | 5 | if [ ! -e "$BASHER_PACKAGES_PATH/$package/package.sh" ]; then 6 | exit 7 | fi 8 | 9 | shopt -s nullglob 10 | 11 | source "$BASHER_PACKAGES_PATH/$package/package.sh" # TODO: make this secure? 12 | IFS=: read -ra bash_completions <<< "$BASH_COMPLETIONS" 13 | IFS=: read -ra zsh_completions <<< "$ZSH_COMPLETIONS" 14 | 15 | for completion in "${bash_completions[@]}" 16 | do 17 | rm -f "$BASHER_PREFIX/completions/bash/${completion##*/}" 18 | done 19 | 20 | for completion in "${zsh_completions[@]}" 21 | do 22 | target="$BASHER_PACKAGES_PATH/$package/$completion" 23 | if grep -q "#compdef" "$target"; then 24 | rm -f "$BASHER_PREFIX/completions/zsh/compsys/${completion##*/}" 25 | else 26 | rm -f "$BASHER_PREFIX/completions/zsh/compctl/${completion##*/}" 27 | fi 28 | done 29 | -------------------------------------------------------------------------------- /libexec/basher-uninstall: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Summary: Uninstalls a package 3 | # Usage: basher uninstall 4 | 5 | set -e 6 | 7 | if [ "$#" -ne 1 ]; then 8 | basher-help uninstall 9 | exit 1 10 | fi 11 | 12 | # TAG completions 13 | if [ "$1" == "--complete" ]; then 14 | exec basher-list 15 | fi 16 | 17 | package="$1" 18 | 19 | if [ -z "$package" ]; then 20 | basher-help uninstall 21 | exit 1 22 | fi 23 | 24 | IFS=/ read -r user name <<< "$package" 25 | 26 | if [ -z "$user" ]; then 27 | basher-help uninstall 28 | exit 1 29 | fi 30 | 31 | if [ -z "$name" ]; then 32 | basher-help uninstall 33 | exit 1 34 | fi 35 | 36 | shopt -s nullglob 37 | 38 | if [ ! -d "$BASHER_PACKAGES_PATH/$package" ]; then 39 | echo "Package '$package' is not installed" 40 | exit 1 41 | fi 42 | 43 | basher-_unlink "$package" 44 | 45 | rm -rf "${BASHER_PACKAGES_PATH}/$package" 46 | -------------------------------------------------------------------------------- /tests/basher-_deps.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load test_helper 4 | 5 | @test "without arguments, prints usage" { 6 | run basher-_deps 7 | 8 | assert_failure 9 | assert_line "Usage: basher _deps " 10 | } 11 | 12 | @test "without dependencies, does nothing" { 13 | mock_clone 14 | mock_command basher-install 15 | create_package "user/main" 16 | basher-_clone false site user/main 17 | 18 | run basher-_deps user/main 19 | 20 | assert_success "" 21 | } 22 | 23 | @test "installs dependencies" { 24 | mock_clone 25 | mock_command basher-install 26 | create_package "user/main" 27 | create_dep "user/main" "user/dep1" 28 | create_dep "user/main" "user/dep2" 29 | basher-_clone false site user/main 30 | 31 | run basher-_deps user/main 32 | 33 | assert_success 34 | assert_line "basher-install user/dep1" 35 | assert_line "basher-install user/dep2" 36 | } 37 | -------------------------------------------------------------------------------- /tests/basher-outdated.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load test_helper 4 | 5 | @test "displays nothing if there are no packages" { 6 | run basher-outdated 7 | assert_success 8 | assert_output "" 9 | } 10 | 11 | @test "displays outdated packages" { 12 | mock_clone 13 | create_package username/outdated 14 | create_package username/uptodate 15 | basher-install username/outdated 16 | basher-install username/uptodate 17 | create_exec username/outdated "second" 18 | 19 | run basher-outdated 20 | assert_success 21 | assert_output username/outdated 22 | } 23 | 24 | @test "ignore packages checked out with a tag or ref" { 25 | mock_clone 26 | create_package username/tagged 27 | basher-install username/tagged 28 | 29 | create_command git 'if [ "$1" = "symbolic-ref" ]; then exit 128; fi' 30 | 31 | run basher-outdated 32 | assert_success 33 | assert_output "" 34 | } 35 | -------------------------------------------------------------------------------- /libexec/basher-upgrade: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Summary: Upgrades a package 3 | # Usage: basher upgrade 4 | 5 | set -e 6 | 7 | if [ "$#" -ne 1 ]; then 8 | basher-help upgrade 9 | exit 1 10 | fi 11 | 12 | # TAG completions 13 | if [ "$1" == "--complete" ]; then 14 | exec basher-list 15 | fi 16 | 17 | if [ "$1" == "--all" ]; then 18 | basher-outdated | 19 | while read -r package; do 20 | echo "# $package" 21 | basher-upgrade "$package" 22 | done 23 | exit 0 24 | fi 25 | 26 | package="$1" 27 | 28 | if [ -z "$package" ]; then 29 | basher-help upgrade 30 | exit 1 31 | fi 32 | 33 | IFS=/ read -r user name <<< "$package" 34 | 35 | if [ -z "$user" ]; then 36 | basher-help upgrade 37 | exit 1 38 | fi 39 | 40 | if [ -z "$name" ]; then 41 | basher-help upgrade 42 | exit 1 43 | fi 44 | 45 | cd "${BASHER_PACKAGES_PATH}/$package" 46 | 47 | basher-_unlink "$package" 48 | git pull 49 | basher-_link "$package" 50 | -------------------------------------------------------------------------------- /libexec/basher-_link-bins: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | package="$1" 4 | 5 | shopt -s nullglob 6 | 7 | if [ -e "$BASHER_PACKAGES_PATH/$package/package.sh" ]; then 8 | source "$BASHER_PACKAGES_PATH/$package/package.sh" 9 | IFS=: read -ra bins <<< "$BINS" 10 | fi 11 | 12 | if [ -z "$bins" ]; then 13 | if [ -e "$BASHER_PACKAGES_PATH/$package/bin" ]; then 14 | bins=("$BASHER_PACKAGES_PATH/$package"/bin/*) 15 | bins=("${bins[@]##*/}") 16 | bins=("${bins[@]/#/bin/}") 17 | else 18 | bins=($(find "$BASHER_PACKAGES_PATH/$package" -maxdepth 1 -mindepth 1 -perm -u+x -type f -or -type l)) 19 | bins=("${bins[@]##*/}") 20 | fi 21 | fi 22 | 23 | for bin in "${bins[@]}" 24 | do 25 | name="${bin##*/}" 26 | if ${REMOVE_EXTENSION:-false}; then 27 | name="${name%%.*}" 28 | fi 29 | mkdir -p "$BASHER_INSTALL_BIN" 30 | ln -sf "$BASHER_PACKAGES_PATH/$package/$bin" "$BASHER_INSTALL_BIN/${name}" 31 | chmod +x "$BASHER_INSTALL_BIN/${name}" 32 | done 33 | -------------------------------------------------------------------------------- /libexec/basher-_link-completions: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | package="$1" 4 | 5 | if [ ! -e "$BASHER_PACKAGES_PATH/$package/package.sh" ]; then 6 | exit 7 | fi 8 | 9 | shopt -s nullglob 10 | 11 | source "$BASHER_PACKAGES_PATH/$package/package.sh" # TODO: make this secure? 12 | IFS=: read -ra bash_completions <<< "$BASH_COMPLETIONS" 13 | IFS=: read -ra zsh_completions <<< "$ZSH_COMPLETIONS" 14 | 15 | for completion in "${bash_completions[@]}" 16 | do 17 | mkdir -p "$BASHER_PREFIX/completions/bash" 18 | ln -sf "$BASHER_PACKAGES_PATH/$package/$completion" "$BASHER_PREFIX/completions/bash/${completion##*/}" 19 | done 20 | 21 | for completion in "${zsh_completions[@]}" 22 | do 23 | target="$BASHER_PACKAGES_PATH/$package/$completion" 24 | if grep -q "#compdef" "$target"; then 25 | mkdir -p "$BASHER_PREFIX/completions/zsh/compsys" 26 | ln -sf "$target" "$BASHER_PREFIX/completions/zsh/compsys/${completion##*/}" 27 | else 28 | mkdir -p "$BASHER_PREFIX/completions/zsh/compctl" 29 | ln -sf "$target" "$BASHER_PREFIX/completions/zsh/compctl/${completion##*/}" 30 | fi 31 | done 32 | -------------------------------------------------------------------------------- /tests/test_helper.bash: -------------------------------------------------------------------------------- 1 | load vendor/bats-core/load 2 | load vendor/bats-assert/load 3 | 4 | export BASHER_TEST_DIR="${BATS_TMPDIR}/basher" 5 | export BASHER_ORIGIN_DIR="${BASHER_TEST_DIR}/origin" 6 | export BASHER_CWD="${BASHER_TEST_DIR}/cwd" 7 | export BASHER_TMP_BIN="${BASHER_TEST_DIR}/bin" 8 | 9 | export XDG_DATA_HOME="" 10 | export BASHER_ROOT="${BATS_TEST_DIRNAME}/.." 11 | export BASHER_PREFIX="${BASHER_TEST_DIR}/prefix" 12 | export BASHER_INSTALL_BIN="${BASHER_PREFIX}/bin" 13 | export BASHER_INSTALL_MAN="${BASHER_PREFIX}/man" 14 | export BASHER_PACKAGES_PATH="$BASHER_PREFIX/packages" 15 | 16 | export FIXTURES_DIR="${BATS_TEST_DIRNAME}/fixtures" 17 | 18 | export PATH="${BATS_TEST_DIRNAME}/libexec:$PATH" 19 | export PATH="${BATS_TEST_DIRNAME}/../libexec:$PATH" 20 | export PATH="${BASHER_TMP_BIN}:$PATH" 21 | 22 | mkdir -p "${BASHER_TMP_BIN}" 23 | mkdir -p "${BASHER_TEST_DIR}/path" 24 | 25 | mkdir -p "${BASHER_ORIGIN_DIR}" 26 | 27 | mkdir -p "${BASHER_CWD}" 28 | 29 | setup() { 30 | cd ${BASHER_CWD} 31 | } 32 | 33 | teardown() { 34 | rm -rf "$BASHER_TEST_DIR" 35 | } 36 | 37 | load lib/mocks 38 | load lib/package_helpers 39 | load lib/commands 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Juan Ibiapina 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /tests/basher-_unlink-completions.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load test_helper 4 | 5 | @test "unlinks bash completions from prefix/completions" { 6 | create_package username/package 7 | create_bash_completions username/package comp.bash 8 | mock_clone 9 | basher-install username/package 10 | 11 | run basher-_unlink-completions username/package 12 | assert_success 13 | assert [ ! -e "$($BASHER_PREFIX/completions/bash/comp.bash)" ] 14 | } 15 | 16 | @test "unlinks zsh compsys completions from prefix/completions" { 17 | create_package username/package 18 | create_zsh_compsys_completions username/package _exec 19 | mock_clone 20 | basher-install username/package 21 | 22 | run basher-_unlink-completions username/package 23 | assert_success 24 | assert [ ! -e "$(readlink $BASHER_PREFIX/completions/zsh/compsys/_exec)" ] 25 | } 26 | 27 | @test "unlinks zsh compctl completions from prefix/completions" { 28 | create_package username/package 29 | create_zsh_compctl_completions username/package exec 30 | mock_clone 31 | basher-install username/package 32 | 33 | run basher-_unlink-completions username/package 34 | assert_success 35 | assert [ ! -e "$(readlink $BASHER_PREFIX/completions/zsh/compctl/exec)" ] 36 | } 37 | -------------------------------------------------------------------------------- /tests/include.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load test_helper 4 | 5 | @test "without arguments, prints an error" { 6 | eval "$(basher-init - sh)" 7 | run include 8 | assert_failure 9 | assert_output "Usage: include " 10 | } 11 | 12 | @test "with one argument, prints an error" { 13 | eval "$(basher-init - sh)" 14 | run include user/repo 15 | assert_failure 16 | assert_output "Usage: include " 17 | } 18 | 19 | @test "when package is not installed, prints an error" { 20 | eval "$(basher-init - sh)" 21 | run include user/repo file 22 | assert_failure 23 | assert_output "Package not installed: user/repo" 24 | } 25 | 26 | @test "when file doesn't exist, prints an error" { 27 | create_package username/repo 28 | mock_clone 29 | basher-_clone false site username/repo 30 | 31 | eval "$(basher-init - sh)" 32 | run include username/repo non_existent 33 | assert_failure 34 | assert_output "File not found: $BASHER_PREFIX/packages/username/repo/non_existent" 35 | } 36 | 37 | @test "sources a file into the current shell" { 38 | create_package username/repo 39 | create_file username/repo function.sh "func_name() { echo DONE; }" 40 | mock_clone 41 | basher-_clone false site username/repo 42 | 43 | eval "$(basher-init - sh)" 44 | include username/repo function.sh 45 | 46 | run func_name 47 | assert_success 48 | assert_output "DONE" 49 | } 50 | -------------------------------------------------------------------------------- /libexec/basher-_clone: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Summary: Clones a package from a site, but doesn't install it 4 | # 5 | # Usage: basher _clone [] [folder] 6 | 7 | set -e 8 | 9 | if [ "$#" -lt 3 -o "$#" -gt 5 ]; then 10 | basher-help _clone 11 | exit 1 12 | fi 13 | 14 | use_ssh="$1" 15 | site="$2" 16 | package="$3" 17 | ref="$4" 18 | folder="$5" 19 | 20 | if [ -z "$use_ssh" ]; then 21 | basher-help _clone 22 | exit 1 23 | fi 24 | 25 | if [ -z "$site" ]; then 26 | basher-help _clone 27 | exit 1 28 | fi 29 | 30 | if [ -z "$package" ]; then 31 | basher-help _clone 32 | exit 1 33 | fi 34 | 35 | if [ -z "$ref" ]; then 36 | BRANCH_OPTION="" 37 | else 38 | BRANCH_OPTION="-b $ref" 39 | fi 40 | 41 | IFS=/ read -r user name <<< "$package" 42 | 43 | if [ -z "$user" ]; then 44 | basher-help _clone 45 | exit 1 46 | fi 47 | 48 | if [ -z "$name" ]; then 49 | basher-help _clone 50 | exit 1 51 | fi 52 | 53 | if [ -z "$folder" ]; then 54 | folder="$package" 55 | fi 56 | 57 | if [ -e "$BASHER_PACKAGES_PATH/$folder" ]; then 58 | echo "Folder '$folder' already exists" 59 | exit 0 60 | fi 61 | 62 | if [ "$BASHER_FULL_CLONE" = "true" ]; then 63 | DEPTH_OPTION="" 64 | else 65 | DEPTH_OPTION="--depth=1" 66 | fi 67 | 68 | if [ "$use_ssh" = "true" ]; then 69 | URI="git@$site:$package.git" 70 | else 71 | URI="https://${site}/$package.git" 72 | fi 73 | 74 | git clone ${DEPTH_OPTION} ${BRANCH_OPTION} --recursive "$URI" "${BASHER_PACKAGES_PATH}/$folder" 75 | -------------------------------------------------------------------------------- /libexec/basher: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Summary: A package manager for shell scripts 4 | # 5 | # Usage: basher [] 6 | 7 | resolve_link() { 8 | $(type -p greadlink readlink | head -1) "$1" 9 | } 10 | 11 | abs_dirname() { 12 | local cwd="$PWD" 13 | local path="$1" 14 | 15 | while [ -n "$path" ]; do 16 | cd "${path%/*}" 17 | local name="${path##*/}" 18 | path="$(resolve_link "$name" || true)" 19 | done 20 | 21 | pwd 22 | cd "$cwd" 23 | } 24 | 25 | if [ -z "$BASHER_ROOT" ]; then 26 | BASHER_ROOT="$HOME/.basher" 27 | 28 | if [ -d "${XDG_DATA_HOME:-$HOME/.local/share}/basher" ]; then 29 | BASHER_ROOT="${XDG_DATA_HOME:-$HOME/.local/share}/basher" 30 | fi 31 | fi 32 | export BASHER_ROOT 33 | 34 | if [ -z "$BASHER_PREFIX" ]; then 35 | BASHER_PREFIX="$BASHER_ROOT/cellar" 36 | fi 37 | export BASHER_PREFIX 38 | 39 | if [ -z "$BASHER_PACKAGES_PATH" ]; then 40 | BASHER_PACKAGES_PATH="$BASHER_PREFIX/packages" 41 | fi 42 | export BASHER_PACKAGES_PATH 43 | 44 | if [ -z "$BASHER_INSTALL_BIN" ]; then 45 | BASHER_INSTALL_BIN="$BASHER_PREFIX/bin" 46 | fi 47 | export BASHER_INSTALL_BIN 48 | 49 | if [ -z "$BASHER_INSTALL_MAN" ]; then 50 | BASHER_INSTALL_MAN="$BASHER_PREFIX/man" 51 | fi 52 | export BASHER_INSTALL_MAN 53 | 54 | bin_path="$(abs_dirname "$0")" 55 | export PATH="${bin_path}:${PATH}" 56 | 57 | command="$1" 58 | case "$command" in 59 | "") 60 | basher-help 61 | ;; 62 | * ) 63 | command_path="$(command -v "basher-$command" || true)" 64 | if [ -z "$command_path" ]; then 65 | echo "basher: no such command '$command'" >&2 66 | exit 1 67 | fi 68 | 69 | shift 1 70 | exec "$command_path" "$@" 71 | ;; 72 | esac 73 | -------------------------------------------------------------------------------- /tests/basher-_link-completions.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load test_helper 4 | 5 | @test "links bash completions to prefix/completions" { 6 | create_package username/package 7 | create_bash_completions username/package comp.bash 8 | mock_clone 9 | basher-_clone false site username/package 10 | 11 | run basher-_link-completions username/package 12 | 13 | assert_success 14 | assert [ "$(readlink $BASHER_PREFIX/completions/bash/comp.bash)" = "${BASHER_PACKAGES_PATH}/username/package/completions/comp.bash" ] 15 | } 16 | 17 | @test "links zsh compsys completions to prefix/completions" { 18 | create_package username/package 19 | create_zsh_compsys_completions username/package _exec 20 | mock_clone 21 | basher-_clone false site username/package 22 | 23 | run basher-_link-completions username/package 24 | 25 | assert_success 26 | assert [ "$(readlink $BASHER_PREFIX/completions/zsh/compsys/_exec)" = "${BASHER_PACKAGES_PATH}/username/package/completions/_exec" ] 27 | } 28 | 29 | @test "links zsh compctl completions to prefix/completions" { 30 | create_package username/package 31 | create_zsh_compctl_completions username/package exec 32 | mock_clone 33 | basher-_clone false site username/package 34 | 35 | run basher-_link-completions username/package 36 | 37 | assert_success 38 | assert [ "$(readlink $BASHER_PREFIX/completions/zsh/compctl/exec)" = "${BASHER_PACKAGES_PATH}/username/package/completions/exec" ] 39 | } 40 | 41 | @test "does not fail if package doesn't have any completions" { 42 | create_package username/package 43 | mock_clone 44 | basher-_clone false site username/package 45 | 46 | run basher-_link-completions username/package 47 | 48 | assert_success 49 | } 50 | -------------------------------------------------------------------------------- /libexec/basher-link: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Summary: Installs a local directory as a basher package 3 | # Usage: basher link [--no-deps] 4 | 5 | set -e 6 | 7 | resolve_link() { 8 | if type -p realpath >/dev/null; then 9 | realpath "$1" 10 | else 11 | readlink -f "$1" 12 | fi 13 | } 14 | 15 | no_deps="false" 16 | 17 | case $1 in 18 | --no-deps) 19 | no_deps="true" 20 | shift 21 | ;; 22 | esac 23 | 24 | if [ "$#" -ne 2 ]; then 25 | basher-help link 26 | exit 1 27 | fi 28 | 29 | directory="$1" 30 | package="$2" 31 | 32 | if [ ! -d "$directory" ]; then 33 | echo "Directory '$directory' not found." 34 | exit 1 35 | fi 36 | 37 | if [ -z "$package" ]; then 38 | basher-help link 39 | exit 1 40 | fi 41 | 42 | IFS=/ read -r namespace name <<< "$package" 43 | 44 | if [ -z "$namespace" ]; then 45 | echo "Missing from package '$package', /" 46 | echo 47 | basher-help link 48 | exit 1 49 | fi 50 | 51 | if [ -z "$name" ]; then 52 | echo "Missing from package '$package', /" 53 | echo 54 | basher-help link 55 | exit 1 56 | fi 57 | 58 | if [ -d "${BASHER_PACKAGES_PATH}/$package" ]; then 59 | echo "Package '$package' is already present" 60 | exit 1 61 | fi 62 | 63 | # Make sure the namespace directory exists before linking 64 | if [ ! -d "${BASHER_PACKAGES_PATH}/$namespace" ]; then 65 | mkdir -p "${BASHER_PACKAGES_PATH}/$namespace" 66 | fi 67 | 68 | # Resolve local package path 69 | directory="$(resolve_link "$directory")" 70 | 71 | ln -s "$directory" "${BASHER_PACKAGES_PATH}/$package" 72 | 73 | basher-_link-bins "$package" 74 | basher-_link-completions "$package" 75 | basher-_link-man "$package" 76 | 77 | if [ "$no_deps" = "false" ]; then 78 | basher-_deps "$package" 79 | fi 80 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build-linux: 11 | strategy: 12 | matrix: 13 | bashver: ["4.0", "4.1", "4.2", "4.3", "4.4", "5.0", "latest"] 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | with: 20 | submodules: true 21 | 22 | - name: Install dependencies 23 | run: | 24 | wget --no-check-certificate -O bats.tar.gz https://github.com/bats-core/bats-core/archive/v1.1.0.tar.gz 25 | mkdir bats 26 | tar --strip-components=1 -xvzf bats.tar.gz -C bats 27 | 28 | - name: Build image 29 | run: | 30 | docker build --build-arg bashver=${{ matrix.bashver }} --tag basher/basher:bash-${{ matrix.bashver }} . 31 | docker run bash:${{ matrix.bashver }} --version 32 | 33 | - name: Run tests 34 | run: | 35 | time docker run basher/basher:bash-${{ matrix.bashver }} --tap /opt/basher/tests 36 | 37 | build-mac: 38 | runs-on: macos-latest 39 | 40 | steps: 41 | - uses: actions/checkout@v2 42 | with: 43 | submodules: true 44 | 45 | - name: Install dependencies 46 | run: | 47 | wget --no-check-certificate -O bats.tar.gz https://github.com/bats-core/bats-core/archive/v1.1.0.tar.gz 48 | mkdir bats 49 | tar --strip-components=1 -xvzf bats.tar.gz -C bats 50 | 51 | brew install bash coreutils 52 | 53 | - name: Run tests 54 | run: | 55 | bash --version 56 | git config --global user.email "user@example.com" 57 | git config --global user.name "User Name" 58 | time bats/bin/bats --tap tests 59 | -------------------------------------------------------------------------------- /tests/basher-uninstall.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load test_helper 4 | 5 | @test "without arguments prints usage" { 6 | run basher-uninstall 7 | assert_failure 8 | assert_line "Usage: basher uninstall " 9 | } 10 | 11 | @test "with invalid arguments, prints usage" { 12 | run basher-uninstall lol 13 | assert_failure 14 | assert_line "Usage: basher uninstall " 15 | } 16 | 17 | @test "with too many arguments, prints usage" { 18 | run basher-uninstall a/b lol 19 | assert_failure 20 | assert_line "Usage: basher uninstall " 21 | } 22 | 23 | @test "fails if package is not installed" { 24 | run basher-uninstall user/lol 25 | assert_failure 26 | assert_output "Package 'user/lol' is not installed" 27 | } 28 | 29 | @test "removes package directory" { 30 | mock_clone 31 | create_package username/package 32 | basher-install username/package 33 | 34 | run basher-uninstall username/package 35 | assert_success 36 | [ ! -d "$BASHER_PACKAGES_PATH/username/package" ] 37 | } 38 | 39 | @test "removes binaries" { 40 | mock_clone 41 | create_package username/package 42 | create_exec username/package exec1 43 | basher-install username/package 44 | 45 | run basher-uninstall username/package 46 | assert_success 47 | [ ! -e "$BASHER_INSTALL_BIN/exec1" ] 48 | } 49 | 50 | @test "does not remove other package directories and binaries" { 51 | mock_clone 52 | create_package username/package1 53 | create_exec username/package1 exec1 54 | create_package username/package2 55 | create_exec username/package2 exec2 56 | basher-install username/package1 57 | basher-install username/package2 58 | 59 | run basher-uninstall username/package1 60 | assert_success 61 | [ -d "$BASHER_PACKAGES_PATH/username/package2" ] 62 | [ -e "$BASHER_INSTALL_BIN/exec2" ] 63 | } 64 | -------------------------------------------------------------------------------- /libexec/basher-list: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Summary: List installed packages 4 | # Usage: basher list [-v] 5 | 6 | set -e 7 | 8 | case $1 in 9 | -v) 10 | verbose="true" 11 | shift 12 | ;; 13 | esac 14 | 15 | if [ "$#" -gt 0 ]; then 16 | basher-help list 17 | exit 1 18 | fi 19 | 20 | shopt -s nullglob 21 | 22 | for package_path in "${BASHER_PACKAGES_PATH}"/*/* 23 | do 24 | username="$(dirname "$package_path")" 25 | username="${username##*/}" 26 | package="${package_path##*/}" 27 | if [ -z "$verbose" ]; then 28 | echo "$username/$package" 29 | else 30 | printf "%-30s %-30s\n" "$username/$package" "($(git --git-dir=${BASHER_PACKAGES_PATH}/$username/$package/.git config --get remote.origin.url))" 31 | 32 | # Find and display executables for this package 33 | package_full="$username/$package" 34 | bins=() 35 | 36 | # Check for package.sh file with custom BINS 37 | if [ -e "$BASHER_PACKAGES_PATH/$package_full/package.sh" ]; then 38 | source "$BASHER_PACKAGES_PATH/$package_full/package.sh" 39 | IFS=: read -ra bins <<< "$BINS" 40 | fi 41 | 42 | # If no custom bins, look for executables 43 | if [ -z "$bins" ]; then 44 | if [ -e "$BASHER_PACKAGES_PATH/$package_full/bin" ]; then 45 | bins=("$BASHER_PACKAGES_PATH/$package_full"/bin/*) 46 | bins=("${bins[@]##*/}") 47 | bins=("${bins[@]/#/bin/}") 48 | else 49 | bins=($(find "$BASHER_PACKAGES_PATH/$package_full" -maxdepth 1 -mindepth 1 -perm -u+x -type f -or -type l 2>/dev/null)) 50 | bins=("${bins[@]##*/}") 51 | fi 52 | fi 53 | 54 | # Display executables if any exist 55 | if [ ${#bins[@]} -gt 0 ] && [ -n "${bins[0]}" ]; then 56 | for bin in "${bins[@]}"; do 57 | name="${bin##*/}" 58 | if ${REMOVE_EXTENSION:-false}; then 59 | name="${name%%.*}" 60 | fi 61 | if [ -n "$name" ]; then 62 | echo "- $name" 63 | fi 64 | done 65 | fi 66 | fi 67 | done 68 | -------------------------------------------------------------------------------- /uninstall.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | die() { 4 | echo "!! $1 " >&2 5 | echo "!! -----------------------------" >&2 6 | exit 1 7 | } 8 | 9 | xdg_basher_dir="${XDG_DATA_HOME:-$HOME/.local/share}/basher" 10 | 11 | ## stop if basher is not installed 12 | [[ -d "$HOME/.basher" || -d "$xdg_basher_dir" ]] || die "basher doesn't seem to be installed on [$HOME/.basher] or [$xdg_basher_dir]" 13 | echo ". remove basher code and installed packages" 14 | basher list 15 | sleep 2 16 | [ -d "$HOME/.basher" ] && rm -fr "$HOME/.basher" 17 | [ -d "$xdg_basher_dir" ] && rm -fr "$xdg_basher_dir" 18 | 19 | ## now check what shell is running 20 | shell_type=$(basename "$SHELL") 21 | case "$shell_type" in 22 | bash) startup_type="simple" ; startup_script="$HOME/.bashrc" ;; 23 | zsh) startup_type="simple" ; startup_script="$HOME/.zshrc" ;; 24 | sh) startup_type="simple" ; startup_script="$HOME/.profile";; 25 | fish) startup_type="fish" ; startup_script="$HOME/.config/fish/config.fish" ;; 26 | *) startup_type="?" ; startup_script="" ; ;; 27 | esac 28 | 29 | ## startup script should exist already 30 | [[ -n "$startup_script" && ! -f "$startup_script" ]] && die "startup script [$startup_script] does not exist" 31 | 32 | ## basher_keyword will allow us to remove the lines upon uninstall 33 | basher_keyword="basher5ea843" 34 | 35 | echo ". following basher folders are in your path:" 36 | echo $PATH | tr ':' "\n" | grep basher 37 | 38 | if grep -q "$basher_keyword" "$startup_script" ; then 39 | echo ". remove basher from startup script [$startup_script]" 40 | sleep 1 41 | temp_file="$startup_script.temp" 42 | cp "$startup_script" "$temp_file" 43 | < "$temp_file" grep -v "$basher_keyword" > "$startup_script" 44 | rm "$temp_file" 45 | elif grep -q basher "$startup_script" ; then 46 | grep basher "$startup_script" 47 | die "Can't auto-remove the lines from $(basename $startup_script) - please do so manually " 48 | else 49 | die "Can't find initialisation commands for basher" 50 | fi 51 | 52 | ## script is finished 53 | echo "basher is uninstalled" 54 | -------------------------------------------------------------------------------- /libexec/basher-install: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Summary: Installs a package from github (or a custom site) 4 | # 5 | # Usage: basher install [--ssh] [site]/[@ref] [folder] 6 | 7 | set -e 8 | 9 | use_ssh="false" 10 | 11 | case $1 in 12 | --ssh) 13 | use_ssh="true" 14 | shift 15 | ;; 16 | esac 17 | 18 | if [ "$#" -lt 1 -o "$#" -gt 2 ]; then 19 | basher-help install 20 | exit 1 21 | fi 22 | 23 | if [[ "$1" = */*/* ]]; then 24 | IFS=/ read -r site user name <<< "$1" 25 | package="${user}/${name}" 26 | else 27 | IFS=/ read -r user name <<< "$1" 28 | package="${user}/${name}" 29 | site="github.com" 30 | fi 31 | 32 | # defaults to package's name, but allows custom folder name 33 | folder="$package" 34 | custom_folder=false 35 | if [ -n "$2" ]; then 36 | if ! [[ "$2" =~ ^[^/]+/[^/]+$ ]]; then 37 | basher-help install 38 | echo "Optional argunment [folder] must be in the format <...>/<...>" 39 | exit 1 40 | fi 41 | folder="$2" 42 | custom_folder=true 43 | fi 44 | 45 | if [ -z "$package" ]; then 46 | basher-help install 47 | exit 1 48 | fi 49 | 50 | IFS=/ read -r user name <<< "$package" 51 | 52 | if [ -z "$user" ]; then 53 | basher-help install 54 | exit 1 55 | fi 56 | 57 | if [ -z "$name" ]; then 58 | basher-help install 59 | exit 1 60 | fi 61 | 62 | if [[ "$package" = */*@* ]]; then 63 | IFS=@ read -r package ref <<< "$package" 64 | if [ "$custom_folder" = "false" ]; then 65 | folder="$package" 66 | fi 67 | else 68 | ref="" 69 | fi 70 | 71 | # Call basher-_clone with appropriate number of parameters 72 | if [ "$custom_folder" = "false" ]; then 73 | # No custom folder - use original behavior 74 | if [ -z "$ref" ]; then 75 | basher-_clone "$use_ssh" "$site" "$package" 76 | else 77 | basher-_clone "$use_ssh" "$site" "$package" "$ref" 78 | fi 79 | else 80 | # Custom folder specified - pass all parameters 81 | basher-_clone "$use_ssh" "$site" "$package" "$ref" "$folder" 82 | fi 83 | 84 | # Use folder for subsequent operations 85 | basher-_deps "$folder" 86 | basher-_link-bins "$folder" 87 | basher-_link-man "$folder" 88 | basher-_link-completions "$folder" 89 | -------------------------------------------------------------------------------- /libexec/basher-init: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Summary: Configure the shell environment for basher 3 | # Usage: eval "$(basher init -)" 4 | 5 | set -e 6 | 7 | shell="$2" 8 | if [ -z "$shell" ]; then 9 | echo "echo 'basher init usage has changed, please specify the name of your shell as an argument: 10 | 11 | eval \"\$(basher init - bash)\" # or zsh, fish, sh etc 12 | 13 | For more information, check this PR: https://github.com/basherpm/basher/pull/77 14 | '" 15 | exit 1 16 | fi 17 | 18 | print_fish_commands() { 19 | echo "set -gx BASHER_SHELL $shell" 20 | echo "set -gx BASHER_ROOT $BASHER_ROOT" 21 | echo "set -gx BASHER_PREFIX $BASHER_PREFIX" 22 | echo "set -gx BASHER_PACKAGES_PATH $BASHER_PACKAGES_PATH" 23 | 24 | echo 'if not contains $BASHER_ROOT/cellar/bin $PATH' 25 | echo ' set -gx PATH $BASHER_ROOT/cellar/bin $PATH' 26 | echo 'end' 27 | } 28 | 29 | print_sh_commands(){ 30 | echo "export BASHER_SHELL=$shell" 31 | echo "export BASHER_ROOT=$BASHER_ROOT" 32 | echo "export BASHER_PREFIX=$BASHER_PREFIX" 33 | echo "export BASHER_PACKAGES_PATH=$BASHER_PACKAGES_PATH" 34 | 35 | echo 'if [ "${PATH#*$BASHER_ROOT/cellar/bin}" = "${PATH}" ]; then' 36 | echo ' export PATH="$BASHER_ROOT/cellar/bin:$PATH"' 37 | echo 'fi' 38 | } 39 | 40 | load_bash_package_completions() { 41 | echo 'for f in $(command ls "$BASHER_ROOT/cellar/completions/bash"); do source "$BASHER_ROOT/cellar/completions/bash/$f"; done' 42 | } 43 | 44 | load_zsh_package_completions() { 45 | echo 'fpath=("$BASHER_ROOT/cellar/completions/zsh/compsys" $fpath)' 46 | echo 'for f in $(command ls "$BASHER_ROOT/cellar/completions/zsh/compctl"); do source "$BASHER_ROOT/cellar/completions/zsh/compctl/$f"; done' 47 | } 48 | 49 | case "$shell" in 50 | fish ) 51 | print_fish_commands 52 | ;; 53 | * ) 54 | print_sh_commands 55 | ;; 56 | esac 57 | 58 | if [ -e "$BASHER_ROOT/lib/include.$shell" ]; then 59 | echo ". \"\$BASHER_ROOT/lib/include.$shell\"" 60 | fi 61 | 62 | if [ -e "$BASHER_ROOT/completions/basher.$shell" ]; then 63 | echo ". \"\$BASHER_ROOT/completions/basher.$shell\"" 64 | fi 65 | 66 | case "$shell" in 67 | bash ) 68 | load_bash_package_completions 69 | ;; 70 | zsh ) 71 | load_zsh_package_completions 72 | ;; 73 | esac 74 | -------------------------------------------------------------------------------- /tests/basher-upgrade.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load test_helper 4 | 5 | @test "without arguments shows usage" { 6 | run basher-upgrade 7 | assert_failure 8 | assert_line "Usage: basher upgrade " 9 | } 10 | 11 | @test "with invalid argument, shows usage" { 12 | run basher-upgrade lol 13 | assert_failure 14 | assert_line "Usage: basher upgrade " 15 | } 16 | 17 | @test "with too many arguments, shows usage" { 18 | run basher-upgrade a/b wrong 19 | assert_failure 20 | assert_line "Usage: basher upgrade " 21 | } 22 | 23 | @test "upgrades a package to the latest version" { 24 | mock_clone 25 | create_package username/package 26 | basher-install username/package 27 | create_exec username/package "second" 28 | 29 | basher-upgrade username/package 30 | 31 | run basher-outdated 32 | assert_output "" 33 | } 34 | 35 | @test "upgrade includes man page changes" { 36 | mock_clone 37 | create_package username/package 38 | basher-install username/package 39 | create_man username/package exec.1 40 | 41 | assert [ ! -d "$BASHER_INSTALL_MAN" ] 42 | basher-upgrade username/package 43 | 44 | 45 | run basher-outdated 46 | assert_output "" 47 | 48 | assert [ -d "$BASHER_INSTALL_MAN" ] 49 | assert [ -e "$BASHER_INSTALL_MAN/man1/exec.1" ] 50 | } 51 | 52 | @test "upgrade removes old binaries" { 53 | mock_clone 54 | create_package username/package 55 | basher-install username/package 56 | create_exec username/package "second" 57 | 58 | basher-upgrade username/package 59 | 60 | run basher-outdated 61 | assert_output "" 62 | 63 | assert [ -e "${BASHER_INSTALL_BIN}/second" ] 64 | 65 | remove_exec username/package "second" 66 | run basher-outdated 67 | assert_output "username/package" 68 | basher-upgrade username/package 69 | 70 | assert [ ! -e "${BASHER_INSTALL_BIN}/second" ] 71 | } 72 | 73 | @test "upgrade -all upgrades all packages" { 74 | mock_clone 75 | create_package username/package1 76 | basher-install username/package1 77 | create_package username/package2 78 | basher-install username/package2 79 | create_exec username/package1 "second" 80 | create_exec username/package2 "second" 81 | 82 | basher-upgrade --all 83 | 84 | run basher-outdated 85 | assert_output "" 86 | 87 | assert [ -e "${BASHER_INSTALL_BIN}/second" ] 88 | 89 | remove_exec username/package1 "second" 90 | run basher-outdated 91 | assert_output "username/package1" 92 | basher-upgrade username/package1 93 | 94 | assert [ ! -e "${BASHER_INSTALL_BIN}/second" ] 95 | } 96 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | die() { 4 | echo "!! $1 " >&2 5 | echo "!! -----------------------------" >&2 6 | exit 1 7 | } 8 | 9 | xdg_basher_dir="${XDG_DATA_HOME:-$HOME/.local/share}/basher" 10 | 11 | ## stop if basher is already installed 12 | [[ -d "$HOME/.basher" ]] && die "basher is already installed on [$HOME/.basher]" 13 | [[ -d "$xdg_basher_dir" ]] && die "basher is already installed on [$xdg_basher_dir]" 14 | 15 | ## stop if git is not installed 16 | git version >/dev/null 2>&1 || die "git is not installed on this machine" 17 | 18 | ## install the scripts on ~/.basher 19 | echo ". download basher code to ~/.basher" 20 | git clone https://github.com/basherpm/basher.git ~/.basher 2> /dev/null 21 | 22 | ## now check what shell is running 23 | shell_type=$(basename "$SHELL") 24 | echo ". detected shell type: $shell_type" 25 | case "$shell_type" in 26 | bash) startup_type="simple" ; startup_script="$HOME/.bashrc" ;; 27 | zsh) startup_type="simple" ; startup_script="$HOME/.zshrc" ;; 28 | sh) startup_type="simple" ; startup_script="$HOME/.profile";; 29 | fish) startup_type="fish" ; startup_script="$HOME/.config/fish/config.fish" ;; 30 | *) startup_type="?" ; startup_script="" ; ;; 31 | esac 32 | 33 | ## startup script should exist already 34 | [[ -n "$startup_script" && ! -f "$startup_script" ]] && die "startup script [$startup_script] does not exist" 35 | 36 | ## basher_keyword will allow us to remove the lines upon uninstall 37 | basher_keyword="basher5ea843" 38 | 39 | ## now add the basher initialisation lines to the user's startup script 40 | echo ". add basher initialisation to [$startup_script]" 41 | if [[ "$startup_type" == "simple" ]]; then 42 | ( 43 | echo "export PATH=\"\$HOME/.basher/bin:\$PATH\" ##$basher_keyword" 44 | # shellcheck disable=SC2086 45 | echo "eval \"\$(basher init - $shell_type)\" ##$basher_keyword" 46 | ) >>"$startup_script" 47 | elif [[ "$startup_type" == "fish" ]]; then 48 | ( 49 | echo "if test -d ~/.basher ##$basher_keyword" 50 | echo " set basher ~/.basher/bin ##$basher_keyword" 51 | echo "end ##$basher_keyword" 52 | # shellcheck disable=SC2154 53 | echo "set -gx PATH \$basher \$PATH ##$basher_keyword" 54 | echo "status --is-interactive; and . (basher init - $shell_type | psub) ##$basher_keyword" 55 | ) >>"$startup_script" 56 | else 57 | die "unknown shell [$shell_type] - can't initialise" 58 | fi 59 | 60 | ## script is finished 61 | echo "basher is installed - open a new terminal window to start using it" 62 | -------------------------------------------------------------------------------- /tests/basher.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load test_helper 4 | 5 | @test "default BASHER_ROOT" { 6 | BASHER_ROOT= run basher echo BASHER_ROOT 7 | assert_output "$HOME/.basher" 8 | } 9 | 10 | @test "inherited BASHER_ROOT" { 11 | BASHER_ROOT=/tmp/basher run basher echo BASHER_ROOT 12 | assert_output "/tmp/basher" 13 | } 14 | 15 | @test "inherited XDG_DATA_HOME" { 16 | mkdir -p "/tmp/local/share/basher" 17 | BASHER_ROOT= XDG_DATA_HOME=/tmp/local/share run basher echo BASHER_ROOT 18 | assert_output "/tmp/local/share/basher" 19 | } 20 | 21 | @test "inherited XDG_DATA_PREFIX overriden by inherited BASHER_ROOT" { 22 | mkdir -p "/tmp/local/share/basher" 23 | BASHER_ROOT=/tmp/basher XDG_DATA_HOME=/tmp/local/share run basher echo BASHER_ROOT 24 | assert_output "/tmp/basher" 25 | } 26 | 27 | @test "default BASHER_PREFIX" { 28 | BASHER_ROOT= BASHER_PREFIX= run basher echo BASHER_PREFIX 29 | assert_output "$HOME/.basher/cellar" 30 | } 31 | 32 | @test "inherited BASHER_PREFIX" { 33 | BASHER_PREFIX=/usr/local run basher echo BASHER_PREFIX 34 | assert_output "/usr/local" 35 | } 36 | 37 | @test "BASHER_PREFIX based on BASHER_ROOT" { 38 | BASHER_ROOT=/tmp/basher BASHER_PREFIX= run basher echo BASHER_PREFIX 39 | assert_output "/tmp/basher/cellar" 40 | } 41 | 42 | @test "inherited BASHER_PACKAGES_PATH" { 43 | BASHER_PACKAGES_PATH=/usr/local/packages run basher echo BASHER_PACKAGES_PATH 44 | assert_output "/usr/local/packages" 45 | } 46 | 47 | @test "BASHER_PACKAGES_PATH based on BASHER_PREFIX" { 48 | BASHER_PREFIX=/tmp/basher BASHER_PACKAGES_PATH= run basher echo BASHER_PACKAGES_PATH 49 | assert_output "/tmp/basher/packages" 50 | } 51 | 52 | @test "default BASHER_INSTALL_BIN" { 53 | BASHER_ROOT= BASHER_PREFIX= BASHER_INSTALL_BIN= run basher echo BASHER_INSTALL_BIN 54 | assert_output "$HOME/.basher/cellar/bin" 55 | } 56 | 57 | @test "inherited BASHER_INSTALL_BIN" { 58 | BASHER_INSTALL_BIN=/opt/bin run basher echo BASHER_INSTALL_BIN 59 | assert_output "/opt/bin" 60 | } 61 | 62 | @test "BASHER_INSTALL_BIN based on BASHER_PREFIX" { 63 | BASHER_INSTALL_BIN= BASHER_ROOT=/tmp/basher BASHER_PREFIX=/usr/local run basher echo BASHER_INSTALL_BIN 64 | assert_output "/usr/local/bin" 65 | } 66 | 67 | @test "default BASHER_INSTALL_MAN" { 68 | BASHER_ROOT= BASHER_PREFIX= BASHER_INSTALL_MAN= run basher echo BASHER_INSTALL_MAN 69 | assert_output "$HOME/.basher/cellar/man" 70 | } 71 | 72 | @test "inherited BASHER_INSTALL_MAN" { 73 | BASHER_INSTALL_MAN=/opt/man run basher echo BASHER_INSTALL_MAN 74 | assert_output "/opt/man" 75 | } 76 | 77 | @test "BASHER_INSTALL_MAN based on BASHER_PREFIX" { 78 | BASHER_INSTALL_MAN= BASHER_PREFIX=/usr/local run basher echo BASHER_INSTALL_MAN 79 | assert_output "/usr/local/man" 80 | } 81 | -------------------------------------------------------------------------------- /tests/basher-help.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load test_helper 4 | 5 | @test "without args shows summary of common commands" { 6 | run basher-help 7 | assert_success 8 | assert_line "Usage: basher []" 9 | assert_line "Some useful basher commands are:" 10 | } 11 | 12 | @test "invalid command" { 13 | run basher-help hello 14 | assert_failure "basher: no such command 'hello'" 15 | } 16 | 17 | @test "shows help for a specific command" { 18 | cat > "${BASHER_TEST_DIR}/bin/basher-hello" < 21 | # Summary: Says "hello" to you, from basher 22 | # This command is useful for saying hello. 23 | echo hello 24 | SH 25 | 26 | run basher-help hello 27 | assert_success 28 | assert_output < 30 | 31 | This command is useful for saying hello. 32 | SH 33 | } 34 | 35 | @test "replaces missing extended help with summary text" { 36 | cat > "${BASHER_TEST_DIR}/bin/basher-hello" < 39 | # Summary: Says "hello" to you, from basher 40 | echo hello 41 | SH 42 | 43 | run basher-help hello 44 | assert_success 45 | assert_output < 47 | 48 | Says "hello" to you, from basher 49 | SH 50 | } 51 | 52 | @test "extracts only usage" { 53 | cat > "${BASHER_TEST_DIR}/bin/basher-hello" < 56 | # Summary: Says "hello" to you, from basher 57 | # This extended help won't be shown. 58 | echo hello 59 | SH 60 | 61 | run basher-help --usage hello 62 | assert_success "Usage: basher hello " 63 | } 64 | 65 | @test "multiline usage section" { 66 | cat > "${BASHER_TEST_DIR}/bin/basher-hello" < 69 | # basher hi [everybody] 70 | # basher hola --translate 71 | # Summary: Says "hello" to you, from basher 72 | # Help text. 73 | echo hello 74 | SH 75 | 76 | run basher-help hello 77 | assert_success 78 | assert_output < 80 | basher hi [everybody] 81 | basher hola --translate 82 | 83 | Help text. 84 | SH 85 | } 86 | 87 | @test "multiline extended help section" { 88 | cat > "${BASHER_TEST_DIR}/bin/basher-hello" < 91 | # Summary: Says "hello" to you, from basher 92 | # This is extended help text. 93 | # It can contain multiple lines. 94 | # 95 | # And paragraphs. 96 | 97 | echo hello 98 | SH 99 | 100 | run basher-help hello 101 | assert_success 102 | assert_output < 104 | 105 | This is extended help text. 106 | It can contain multiple lines. 107 | 108 | And paragraphs. 109 | SH 110 | } 111 | -------------------------------------------------------------------------------- /tests/basher-init.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load test_helper 4 | 5 | @test "without enough arguments, prints a useful message" { 6 | run basher-init - 7 | assert_failure 8 | assert_output "echo 'basher init usage has changed, please specify the name of your shell as an argument: 9 | 10 | eval \"\$(basher init - bash)\" # or zsh, fish, sh etc 11 | 12 | For more information, check this PR: https://github.com/basherpm/basher/pull/77 13 | '" 14 | } 15 | 16 | @test "exports BASHER_ROOT" { 17 | BASHER_ROOT=/lol run basher-init - bash 18 | assert_success 19 | assert_line -n 1 'export BASHER_ROOT=/lol' 20 | } 21 | 22 | @test "exports BASHER_PREFIX" { 23 | BASHER_PREFIX=/lol run basher-init - bash 24 | assert_success 25 | assert_line -n 2 'export BASHER_PREFIX=/lol' 26 | } 27 | 28 | @test "exports BASHER_PACKAGES_PATH" { 29 | BASHER_PACKAGES_PATH=/lol/packages run basher-init - bash 30 | assert_success 31 | assert_line -n 3 'export BASHER_PACKAGES_PATH=/lol/packages' 32 | } 33 | 34 | @test "adds cellar/bin to path" { 35 | run basher-init - bash 36 | assert_success 37 | assert_line -n 4 'if [ "${PATH#*$BASHER_ROOT/cellar/bin}" = "${PATH}" ]; then' 38 | assert_line -n 5 ' export PATH="$BASHER_ROOT/cellar/bin:$PATH"' 39 | assert_line -n 6 'fi' 40 | } 41 | 42 | @test "setup include function if it exists" { 43 | run basher-init - bash 44 | assert_line -n 7 '. "$BASHER_ROOT/lib/include.bash"' 45 | } 46 | 47 | @test "doesn't setup include function if it doesn't exist" { 48 | run basher-init - fakesh 49 | refute_line 'source "$BASHER_ROOT/lib/include.fakesh"' 50 | } 51 | 52 | @test "setup basher completions if available" { 53 | run basher-init - bash 54 | assert_success 55 | assert_line -n 8 '. "$BASHER_ROOT/completions/basher.bash"' 56 | } 57 | 58 | @test "does not setup basher completions if not available" { 59 | run basher-init - fakesh 60 | assert_success 61 | refute_line 'source "$BASHER_ROOT/completions/basher.fakesh"' 62 | refute_line 'source "$BASHER_ROOT/completions/basher.other"' 63 | } 64 | 65 | @test "setup package completions (bash)" { 66 | run basher-init - bash 67 | assert_success 68 | assert_line -n 9 'for f in $(command ls "$BASHER_ROOT/cellar/completions/bash"); do source "$BASHER_ROOT/cellar/completions/bash/$f"; done' 69 | } 70 | 71 | @test "setup package completions (zsh)" { 72 | run basher-init - zsh 73 | assert_success 74 | assert_line -n 9 'fpath=("$BASHER_ROOT/cellar/completions/zsh/compsys" $fpath)' 75 | assert_line -n 10 'for f in $(command ls "$BASHER_ROOT/cellar/completions/zsh/compctl"); do source "$BASHER_ROOT/cellar/completions/zsh/compctl/$f"; done' 76 | } 77 | 78 | hasShell() { 79 | which "$1" >>/dev/null 2>&1 80 | } 81 | 82 | @test "is sh-compatible" { 83 | hasShell sh || skip "sh was not found in path." 84 | run sh -ec 'eval "$(basher init - sh)"' 85 | assert_success 86 | } 87 | -------------------------------------------------------------------------------- /tests/basher-list.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load test_helper 4 | 5 | @test "with arguments shows usage" { 6 | run basher-list a_arg 7 | assert_failure 8 | assert_line "Usage: basher list [-v]" 9 | } 10 | 11 | @test "list installed packages" { 12 | mock_clone 13 | create_package username/p1 14 | create_package username2/p2 15 | create_package username2/p3 16 | basher-install username/p1 17 | basher-install username2/p2 18 | 19 | run basher-list 20 | assert_success 21 | assert_line "username/p1" 22 | assert_line "username2/p2" 23 | refute_line "username2/p3" 24 | } 25 | 26 | @test "verbose flag with arguments shows usage" { 27 | run basher-list -v extra_arg 28 | assert_failure 29 | assert_line "Usage: basher list [-v]" 30 | } 31 | 32 | @test "displays nothing if there are no packages" { 33 | run basher-list 34 | assert_success 35 | assert_output "" 36 | } 37 | 38 | @test "displays nothing if there are no packages with verbose flag" { 39 | run basher-list -v 40 | assert_success 41 | assert_output "" 42 | } 43 | 44 | @test "verbose flag shows complete output format for single package" { 45 | mock_clone 46 | create_package username/package 47 | create_exec username/package tool1 48 | create_exec username/package tool2.sh 49 | basher-install username/package 50 | 51 | run basher-list -v 52 | assert_success 53 | 54 | # Check exact output format 55 | assert_line --index 0 --regexp "username/package +\(${BASHER_ORIGIN_DIR}/username/package\)" 56 | assert_line --index 1 "- tool1" 57 | assert_line --index 2 "- tool2.sh" 58 | } 59 | 60 | @test "verbose flag with multiple packages shows each with executables" { 61 | mock_clone 62 | 63 | # First package with executables 64 | create_package user1/pkg1 65 | create_exec user1/pkg1 cmd1 66 | basher-install user1/pkg1 67 | 68 | # Second package with different executables 69 | create_package user2/pkg2 70 | create_exec user2/pkg2 cmd2 71 | create_exec user2/pkg2 cmd3 72 | basher-install user2/pkg2 73 | 74 | # Third package without executables 75 | create_package user3/pkg3 76 | echo "readme" > "$BASHER_ORIGIN_DIR/user3/pkg3/README" 77 | basher-install user3/pkg3 78 | 79 | run basher-list -v 80 | assert_success 81 | 82 | # Verify all packages are listed 83 | assert_output --partial "user1/pkg1" 84 | assert_output --partial "user2/pkg2" 85 | assert_output --partial "user3/pkg3" 86 | 87 | # Verify executables are shown 88 | assert_output --partial "- cmd1" 89 | assert_output --partial "- cmd2" 90 | assert_output --partial "- cmd3" 91 | } 92 | 93 | @test "verbose flag respects REMOVE_EXTENSION config" { 94 | mock_clone 95 | create_package username/package 96 | create_exec username/package script.sh 97 | create_exec username/package tool.py 98 | set_remove_extension username/package true 99 | basher-install username/package 100 | 101 | run basher-list -v 102 | assert_success 103 | 104 | # Should show names without extensions 105 | assert_output --partial "- script" 106 | assert_output --partial "- tool" 107 | 108 | # Should NOT show the extensions 109 | refute_output --partial "script.sh" 110 | refute_output --partial "tool.py" 111 | } 112 | -------------------------------------------------------------------------------- /libexec/basher-help: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Summary: Display help for a command 4 | # 5 | # Usage: basher help [--usage] COMMAND 6 | # 7 | # Parses and displays help contents from a command's source file. 8 | # 9 | # A command is considered documented if it starts with a comment block 10 | # that has a `Summary:' or `Usage:' section. Usage instructions can 11 | # span multiple lines as long as subsequent lines are indented. 12 | # The remainder of the comment block is displayed as extended 13 | # documentation. 14 | 15 | command_path() { 16 | local command="$1" 17 | command -v basher-"$command" || true 18 | } 19 | 20 | extract_initial_comment_block() { 21 | sed -ne " 22 | /^#/ !{ 23 | q 24 | } 25 | 26 | s/^#$/# / 27 | 28 | /^# / { 29 | s/^# // 30 | p 31 | } 32 | " 33 | } 34 | 35 | collect_documentation() { 36 | awk ' 37 | /^Summary:/ { 38 | summary = substr($0, 10) 39 | next 40 | } 41 | 42 | /^Usage:/ { 43 | reading_usage = 1 44 | usage = usage "\n" $0 45 | next 46 | } 47 | 48 | /^( *$| )/ && reading_usage { 49 | usage = usage "\n" $0 50 | next 51 | } 52 | 53 | { 54 | reading_usage = 0 55 | help = help "\n" $0 56 | } 57 | 58 | function escape(str) { 59 | gsub(/[`\\$"]/, "\\\\&", str) 60 | return str 61 | } 62 | 63 | function trim(str) { 64 | sub(/^\n*/, "", str) 65 | sub(/\n*$/, "", str) 66 | return str 67 | } 68 | 69 | END { 70 | if (usage || summary) { 71 | print "summary=\"" escape(summary) "\"" 72 | print "usage=\"" escape(trim(usage)) "\"" 73 | print "help=\"" escape(trim(help)) "\"" 74 | } 75 | } 76 | ' 77 | } 78 | 79 | documentation_for() { 80 | local filename= 81 | filename="$(command_path "$1")" 82 | if [ -n "$filename" ]; then 83 | extract_initial_comment_block < "$filename" | collect_documentation 84 | fi 85 | } 86 | 87 | print_summary() { 88 | local command="$1" 89 | local summary usage help 90 | eval "$(documentation_for "$command")" 91 | 92 | if [ -n "$summary" ]; then 93 | printf " %-9s %s\n" "$command" "$summary" 94 | fi 95 | } 96 | 97 | print_summaries() { 98 | for command; do 99 | print_summary "$command" 100 | done 101 | } 102 | 103 | print_help() { 104 | local command="$1" 105 | local summary usage help 106 | eval "$(documentation_for "$command")" 107 | [ -n "$help" ] || help="$summary" 108 | 109 | if [ -n "$usage" -o -n "$summary" ]; then 110 | if [ -n "$usage" ]; then 111 | echo "$usage" 112 | else 113 | echo "Usage: basher ${command}" 114 | fi 115 | if [ -n "$help" ]; then 116 | echo 117 | echo "$help" 118 | echo 119 | fi 120 | else 121 | echo "Sorry, this command isn't documented yet." >&2 122 | return 1 123 | fi 124 | } 125 | 126 | print_usage() { 127 | local command="$1" 128 | local summary usage help 129 | eval "$(documentation_for "$command")" 130 | [ -z "$usage" ] || echo "$usage" 131 | } 132 | 133 | unset usage 134 | if [ "$1" = "--usage" ]; then 135 | usage="1" 136 | shift 137 | fi 138 | 139 | # TAG completions 140 | if [ "$1" == "--complete" ]; then 141 | exec basher-commands 142 | fi 143 | 144 | if [ -z "$1" ] || [ "$1" == "basher" ]; then 145 | echo "Usage: basher []" 146 | [ -z "$usage" ] || exit 147 | echo 148 | echo "Some useful basher commands are:" 149 | print_summaries help commands init new-command 150 | echo 151 | echo "See 'basher help ' for information on a specific command." 152 | else 153 | command="$1" 154 | if [ -n "$(command_path "$command")" ]; then 155 | if [ -n "$usage" ]; then 156 | print_usage "$command" 157 | else 158 | print_help "$command" 159 | fi 160 | else 161 | echo "basher: no such command '$command'" >&2 162 | exit 1 163 | fi 164 | fi 165 | -------------------------------------------------------------------------------- /tests/basher-_unlink-bins.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load test_helper 4 | 5 | @test "removes each binary in BINS config from the install bin" { 6 | create_package username/package 7 | create_package_exec username/package exec1 8 | create_package_exec username/package exec2.sh 9 | mock_clone 10 | basher-install username/package 11 | 12 | run basher-_unlink-bins username/package 13 | assert_success 14 | assert [ ! -e "$(readlink $BASHER_INSTALL_BIN/exec1)" ] 15 | assert [ ! -e "$(readlink $BASHER_INSTALL_BIN/exec2.sh)" ] 16 | } 17 | 18 | @test "removes each binary from the install bin" { 19 | create_package username/package 20 | create_exec username/package exec1 21 | create_exec username/package exec2.sh 22 | mock_clone 23 | basher-install username/package 24 | 25 | run basher-_unlink-bins username/package 26 | assert_success 27 | assert [ ! -e "$(readlink $BASHER_INSTALL_BIN/exec1)" ] 28 | assert [ ! -e "$(readlink $BASHER_INSTALL_BIN/exec2.sh)" ] 29 | } 30 | 31 | @test "removes root binaries from the install bin" { 32 | create_package username/package 33 | create_root_exec username/package exec3 34 | create_root_exec username/package exec4.sh 35 | mock_clone 36 | basher-install username/package 37 | 38 | run basher-_unlink-bins username/package 39 | assert_success 40 | assert [ ! -e "$(readlink $BASHER_INSTALL_BIN/exec3)" ] 41 | assert [ ! -e "$(readlink $BASHER_INSTALL_BIN/exec4.sh)" ] 42 | } 43 | 44 | @test "doesn't remove root binaries if there is a bin folder" { 45 | create_package username/package 46 | create_root_exec username/package exec3 47 | mock_clone 48 | basher-install username/package 49 | mkdir "$BASHER_PACKAGES_PATH/username/package/bin" 50 | 51 | run basher-_unlink-bins username/package 52 | assert_success 53 | assert [ -e "$(readlink $BASHER_INSTALL_BIN/exec3)" ] 54 | } 55 | 56 | @test "doesn't remote root bins or files in bin folder if there is a BINS config on package.sh" { 57 | mock_clone 58 | 59 | create_package username/package 60 | create_package_exec username/package exec1 61 | create_exec username/package exec2 62 | create_root_exec username/package exec3 63 | basher-install username/package 64 | 65 | 66 | create_package username/package2 67 | create_root_exec username/package2 exec2 68 | basher-install username/package2 69 | 70 | create_package username/package3 71 | create_exec username/package3 exec3 72 | basher-install username/package3 73 | 74 | run basher-_unlink-bins username/package 75 | 76 | assert_success 77 | assert [ ! -e "$(readlink $BASHER_INSTALL_BIN/exec1)" ] 78 | assert [ -e "$(readlink $BASHER_INSTALL_BIN/exec2)" ] 79 | assert [ -e "$(readlink $BASHER_INSTALL_BIN/exec3)" ] 80 | } 81 | 82 | @test "does not fail if there are no binaries" { 83 | create_package username/package 84 | mock_clone 85 | basher-install username/package 86 | 87 | run basher-_unlink-bins username/package 88 | 89 | assert_success 90 | } 91 | 92 | @test "removes binary when REMOVE_EXTENSION is true" { 93 | create_package username/package 94 | create_exec username/package exec1 95 | create_exec username/package exec2.sh 96 | set_remove_extension username/package true 97 | mock_clone 98 | basher-install username/package 99 | 100 | run basher-_unlink-bins username/package 101 | assert_success 102 | assert [ ! -e "$(readlink $BASHER_INSTALL_BIN/exec1)" ] 103 | assert [ ! -e "$(readlink $BASHER_INSTALL_BIN/exec2)" ] 104 | } 105 | 106 | @test "removes binary when REMOVE_EXTENSION is false" { 107 | create_package username/package 108 | create_exec username/package exec1 109 | create_exec username/package exec2.sh 110 | set_remove_extension username/package false 111 | mock_clone 112 | basher-install username/package 113 | 114 | run basher-_unlink-bins username/package 115 | assert_success 116 | assert [ ! -e "$(readlink $BASHER_INSTALL_BIN/exec1)" ] 117 | assert [ ! -e "$(readlink $BASHER_INSTALL_BIN/exec2.sh)" ] 118 | } 119 | -------------------------------------------------------------------------------- /tests/basher-_clone.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load test_helper 4 | 5 | @test "without arguments prints usage" { 6 | run basher-_clone 7 | assert_failure 8 | assert_line "Usage: basher _clone [] [folder]" 9 | } 10 | 11 | @test "invalid package prints usage" { 12 | run basher-_clone false github.com invalid_package 13 | assert_failure 14 | assert_line "Usage: basher _clone [] [folder]" 15 | } 16 | 17 | @test "too many arguments prints usage" { 18 | run basher-_clone false site a/b ref folder fifth_arg 19 | assert_failure 20 | assert_line "Usage: basher _clone [] [folder]" 21 | } 22 | 23 | @test "install a specific version" { 24 | mock_command git 25 | 26 | run basher-_clone false site username/package version 27 | assert_success 28 | assert_output "git clone --depth=1 -b version --recursive https://site/username/package.git ${BASHER_PACKAGES_PATH}/username/package" 29 | } 30 | 31 | @test "does nothing if package is already present" { 32 | mkdir -p "$BASHER_PACKAGES_PATH/username/package" 33 | 34 | run basher-_clone false github.com username/package 35 | 36 | assert_success 37 | assert_output "Folder 'username/package' already exists" 38 | } 39 | 40 | @test "using a different site" { 41 | mock_command git 42 | 43 | run basher-_clone false site username/package 44 | assert_success 45 | assert_output "git clone --depth=1 --recursive https://site/username/package.git ${BASHER_PACKAGES_PATH}/username/package" 46 | } 47 | 48 | @test "without setting BASHER_FULL_CLONE, clones a package with depth option" { 49 | export BASHER_FULL_CLONE= 50 | mock_command git 51 | 52 | run basher-_clone false github.com username/package 53 | assert_success 54 | assert_output "git clone --depth=1 --recursive https://github.com/username/package.git ${BASHER_PACKAGES_PATH}/username/package" 55 | } 56 | 57 | @test "setting BASHER_FULL_CLONE to true, clones a package without depth option" { 58 | export BASHER_FULL_CLONE=true 59 | mock_command git 60 | 61 | run basher-_clone false github.com username/package 62 | assert_success 63 | assert_output "git clone --recursive https://github.com/username/package.git ${BASHER_PACKAGES_PATH}/username/package" 64 | } 65 | 66 | @test "setting BASHER_FULL_CLONE to false, clones a package with depth option" { 67 | export BASHER_FULL_CLONE=false 68 | mock_command git 69 | 70 | run basher-_clone false github.com username/package 71 | assert_success 72 | assert_output "git clone --depth=1 --recursive https://github.com/username/package.git ${BASHER_PACKAGES_PATH}/username/package" 73 | } 74 | 75 | @test "using ssh protocol" { 76 | mock_command git 77 | 78 | run basher-_clone true site username/package 79 | assert_success 80 | assert_output "git clone --depth=1 --recursive git@site:username/package.git ${BASHER_PACKAGES_PATH}/username/package" 81 | } 82 | 83 | @test "clones to custom folder" { 84 | mock_command git 85 | 86 | run basher-_clone false github.com username/package "" custom/folder 87 | assert_success 88 | assert_output "git clone --depth=1 --recursive https://github.com/username/package.git ${BASHER_PACKAGES_PATH}/custom/folder" 89 | } 90 | 91 | @test "clones to custom folder with version" { 92 | mock_command git 93 | 94 | run basher-_clone false github.com username/package v1.2.3 custom/folder 95 | assert_success 96 | assert_output "git clone --depth=1 -b v1.2.3 --recursive https://github.com/username/package.git ${BASHER_PACKAGES_PATH}/custom/folder" 97 | } 98 | 99 | @test "custom folder defaults to package name when not specified" { 100 | mock_command git 101 | 102 | run basher-_clone false github.com username/package "" "" 103 | assert_success 104 | assert_output "git clone --depth=1 --recursive https://github.com/username/package.git ${BASHER_PACKAGES_PATH}/username/package" 105 | } 106 | 107 | @test "does nothing if custom folder already exists" { 108 | mkdir -p "$BASHER_PACKAGES_PATH/custom/folder" 109 | 110 | run basher-_clone false github.com username/package "" custom/folder 111 | 112 | assert_success 113 | assert_output "Folder 'custom/folder' already exists" 114 | } 115 | -------------------------------------------------------------------------------- /tests/basher-link.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load test_helper 4 | 5 | resolve_link() { 6 | if type -p realpath >/dev/null; then 7 | realpath "$1" 8 | else 9 | readlink -f "$1" 10 | fi 11 | } 12 | 13 | @test "without arguments prints usage" { 14 | run basher-link 15 | assert_failure 16 | assert_line "Usage: basher link [--no-deps] " 17 | } 18 | 19 | @test "fails with only one argument" { 20 | run basher-link invalid 21 | assert_failure 22 | assert_line "Usage: basher link [--no-deps] " 23 | } 24 | 25 | @test "fails with an invalid path" { 26 | run basher-link invalid namespace/name 27 | assert_failure 28 | assert_output "Directory 'invalid' not found." 29 | } 30 | 31 | @test "fails with a file path instead of a directory path" { 32 | touch file1 33 | run basher-link file1 namespace/name 34 | assert_failure 35 | assert_output "Directory 'file1' not found." 36 | } 37 | 38 | @test "fails with an invalid package name" { 39 | mkdir package1 40 | 41 | run basher-link package1 invalid 42 | assert_failure 43 | assert_line "Usage: basher link [--no-deps] " 44 | 45 | run basher-link package1 namespace1/ 46 | assert_failure 47 | assert_line "Missing from package 'namespace1/', /" 48 | assert_line "Usage: basher link [--no-deps] " 49 | 50 | run basher-link package1 /package1 51 | assert_failure 52 | assert_line "Missing from package '/package1', /" 53 | assert_line "Usage: basher link [--no-deps] " 54 | } 55 | 56 | @test "links the package to packages under the correct namespace" { 57 | mock_command basher-_link-bins 58 | mock_command basher-_link-completions 59 | mock_command basher-_link-man 60 | mock_command basher-_deps 61 | mkdir package1 62 | run basher-link package1 namespace1/package1 63 | assert_success 64 | assert [ "$(resolve_link $BASHER_PACKAGES_PATH/namespace1/package1)" = "$(resolve_link "$(pwd)/package1")" ] 65 | } 66 | 67 | @test "calls link-bins, link-completions, link-man and deps" { 68 | mock_command basher-_link-bins 69 | mock_command basher-_link-completions 70 | mock_command basher-_link-man 71 | mock_command basher-_deps 72 | mkdir package2 73 | run basher-link package2 namespace2/package2 74 | assert_success 75 | assert_line "basher-_link-bins namespace2/package2" 76 | assert_line "basher-_link-completions namespace2/package2" 77 | assert_line "basher-_link-man namespace2/package2" 78 | assert_line "basher-_deps namespace2/package2" 79 | } 80 | 81 | @test "respects --no-deps option" { 82 | mock_command basher-_link-bins 83 | mock_command basher-_link-completions 84 | mock_command basher-_link-man 85 | mock_command basher-_deps 86 | mkdir package2 87 | run basher-link --no-deps package2 namespace2/package2 88 | assert_success 89 | refute_line "basher-_deps namespace2/package2" 90 | } 91 | 92 | @test "resolves current directory (dot) path" { 93 | mock_command basher-_link-bins 94 | mock_command basher-_link-completions 95 | mock_command basher-_link-man 96 | mock_command basher-_deps 97 | mkdir package3 98 | cd package3 99 | run basher-link . namespace3/package3 100 | assert_success 101 | assert [ "$(resolve_link $BASHER_PACKAGES_PATH/namespace3/package3)" = "$(resolve_link "$(pwd)")" ] 102 | } 103 | 104 | @test "resolves parent directory (dotdot) path" { 105 | mock_command basher-_link-bins 106 | mock_command basher-_link-completions 107 | mock_command basher-_link-man 108 | mock_command basher-_deps 109 | mkdir package3 110 | cd package3 111 | run basher-link ../package3 namespace3/package3 112 | assert_success 113 | assert [ "$(resolve_link $BASHER_PACKAGES_PATH/namespace3/package3)" = "$(resolve_link "$(pwd)")" ] 114 | } 115 | 116 | @test "resolves arbitrary complex relative path" { 117 | mock_command basher-_link-bins 118 | mock_command basher-_link-completions 119 | mock_command basher-_link-man 120 | mock_command basher-_deps 121 | mkdir package3 122 | run basher-link ./package3/.././package3 namespace3/package3 123 | assert_success 124 | assert [ "$(resolve_link $BASHER_PACKAGES_PATH/namespace3/package3)" = "$(resolve_link "$(pwd)/package3")" ] 125 | } 126 | -------------------------------------------------------------------------------- /tests/basher-_link-bins.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load test_helper 4 | 5 | @test "links each file on the BINS config on package.sh to the install bin" { 6 | create_package username/package 7 | create_package_exec username/package exec1 8 | create_package_exec username/package exec2.sh 9 | mock_clone 10 | basher-_clone false site username/package 11 | 12 | run basher-_link-bins username/package 13 | 14 | assert_success 15 | assert [ "$(readlink $BASHER_INSTALL_BIN/exec1)" = "${BASHER_PACKAGES_PATH}/username/package/package_bin/exec1" ] 16 | assert [ "$(readlink $BASHER_INSTALL_BIN/exec2.sh)" = "${BASHER_PACKAGES_PATH}/username/package/package_bin/exec2.sh" ] 17 | } 18 | 19 | @test "links each file inside bin folder to install bin" { 20 | create_package username/package 21 | create_exec username/package exec1 22 | create_exec username/package exec2.sh 23 | mock_clone 24 | basher-_clone false site username/package 25 | 26 | run basher-_link-bins username/package 27 | 28 | assert_success 29 | assert [ "$(readlink $BASHER_INSTALL_BIN/exec1)" = "${BASHER_PACKAGES_PATH}/username/package/bin/exec1" ] 30 | assert [ "$(readlink $BASHER_INSTALL_BIN/exec2.sh)" = "${BASHER_PACKAGES_PATH}/username/package/bin/exec2.sh" ] 31 | } 32 | 33 | @test "links each exec file in package root to install bin" { 34 | create_package username/package 35 | create_root_exec username/package exec3 36 | create_root_exec username/package exec4.sh 37 | mock_clone 38 | basher-_clone false site username/package 39 | 40 | run basher-_link-bins username/package 41 | 42 | assert_success 43 | assert [ "$(readlink $BASHER_INSTALL_BIN/exec3)" = "${BASHER_PACKAGES_PATH}/username/package/exec3" ] 44 | assert [ "$(readlink $BASHER_INSTALL_BIN/exec4.sh)" = "${BASHER_PACKAGES_PATH}/username/package/exec4.sh" ] 45 | } 46 | 47 | @test "doesn't link root bins if there is a bin folder" { 48 | create_package username/package 49 | create_exec username/package exec1 50 | create_root_exec username/package exec2 51 | mock_clone 52 | basher-_clone false site username/package 53 | 54 | run basher-_link-bins username/package 55 | 56 | assert_success 57 | assert [ "$(readlink $BASHER_INSTALL_BIN/exec1)" = "${BASHER_PACKAGES_PATH}/username/package/bin/exec1" ] 58 | assert [ ! -e "$(readlink $BASHER_INSTALL_BIN/exec2)" ] 59 | } 60 | 61 | @test "doesn't link root bins or files in bin folder if there is a BINS config on package.sh" { 62 | create_package username/package 63 | create_exec username/package exec1 64 | create_root_exec username/package exec2 65 | create_package_exec username/package exec3 66 | mock_clone 67 | basher-_clone false site username/package 68 | 69 | run basher-_link-bins username/package 70 | 71 | assert_success 72 | assert [ ! -e "$(readlink $BASHER_INSTALL_BIN/exec1)" ] 73 | assert [ ! -e "$(readlink $BASHER_INSTALL_BIN/exec2)" ] 74 | assert [ "$(readlink $BASHER_INSTALL_BIN/exec3)" = "${BASHER_PACKAGES_PATH}/username/package/package_bin/exec3" ] 75 | } 76 | 77 | @test "does not fail if there are no binaries" { 78 | create_package username/package 79 | mock_clone 80 | basher-_clone false site username/package 81 | 82 | run basher-_link-bins username/package 83 | 84 | assert_success 85 | } 86 | 87 | @test "remove extension if REMOVE_EXTENSION is true" { 88 | create_package username/package 89 | create_exec username/package exec1 90 | create_exec username/package exec2.sh 91 | set_remove_extension username/package true 92 | mock_clone 93 | basher-_clone false site username/package 94 | 95 | run basher-_link-bins username/package 96 | 97 | assert_success 98 | assert [ "$(readlink $BASHER_INSTALL_BIN/exec1)" = "${BASHER_PACKAGES_PATH}/username/package/bin/exec1" ] 99 | assert [ "$(readlink $BASHER_INSTALL_BIN/exec2)" = "${BASHER_PACKAGES_PATH}/username/package/bin/exec2.sh" ] 100 | } 101 | 102 | @test "does not remove extension if REMOVE_EXTENSION is false" { 103 | create_package username/package 104 | create_exec username/package exec1 105 | create_exec username/package exec2.sh 106 | set_remove_extension username/package false 107 | mock_clone 108 | basher-_clone false site username/package 109 | 110 | run basher-_link-bins username/package 111 | 112 | assert_success 113 | assert [ "$(readlink $BASHER_INSTALL_BIN/exec1)" = "${BASHER_PACKAGES_PATH}/username/package/bin/exec1" ] 114 | assert [ "$(readlink $BASHER_INSTALL_BIN/exec2.sh)" = "${BASHER_PACKAGES_PATH}/username/package/bin/exec2.sh" ] 115 | } 116 | 117 | @test "does not symlink package itself as bin when linked with basher link" { 118 | mkdir package 119 | # implicit call to basher-_link-bins 120 | run basher-link package username/package 121 | assert_success 122 | assert [ ! -e "${BASHER_PREFIX}/bin/package" ] 123 | } 124 | -------------------------------------------------------------------------------- /tests/basher-install.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load test_helper 4 | 5 | @test "without arguments prints usage" { 6 | run basher-install 7 | assert_failure 8 | assert_line "Usage: basher install [--ssh] [site]/[@ref] [folder]" 9 | } 10 | 11 | @test "incorrect argument prints usage" { 12 | run basher-install first_arg 13 | assert_failure 14 | assert_line "Usage: basher install [--ssh] [site]/[@ref] [folder]" 15 | } 16 | 17 | @test "too many arguments prints usage" { 18 | run basher-install a/b folder wrong 19 | assert_failure 20 | assert_line "Usage: basher install [--ssh] [site]/[@ref] [folder]" 21 | } 22 | 23 | @test "executes install steps in right order" { 24 | mock_command basher-_clone 25 | mock_command basher-_deps 26 | mock_command basher-_link-bins 27 | mock_command basher-_link-man 28 | mock_command basher-_link-completions 29 | 30 | run basher-install username/package 31 | assert_success "basher-_clone false github.com username/package 32 | basher-_deps username/package 33 | basher-_link-bins username/package 34 | basher-_link-man username/package 35 | basher-_link-completions username/package" 36 | } 37 | 38 | @test "with site, overwrites site" { 39 | mock_command basher-_clone 40 | mock_command basher-_deps 41 | mock_command basher-_link-bins 42 | mock_command basher-_link-man 43 | mock_command basher-_link-completions 44 | 45 | run basher-install site/username/package 46 | 47 | assert_line "basher-_clone false site username/package" 48 | } 49 | 50 | @test "without site, uses github as default site" { 51 | mock_command basher-_clone 52 | mock_command basher-_deps 53 | mock_command basher-_link-bins 54 | mock_command basher-_link-man 55 | mock_command basher-_link-completions 56 | 57 | run basher-install username/package 58 | 59 | assert_line "basher-_clone false github.com username/package" 60 | } 61 | 62 | @test "using ssh protocol" { 63 | mock_command basher-_clone 64 | mock_command basher-_deps 65 | mock_command basher-_link-bins 66 | mock_command basher-_link-man 67 | mock_command basher-_link-completions 68 | 69 | run basher-install --ssh username/package 70 | 71 | assert_line "basher-_clone true github.com username/package" 72 | } 73 | 74 | @test "installs with custom version" { 75 | mock_command basher-_clone 76 | mock_command basher-_deps 77 | mock_command basher-_link-bins 78 | mock_command basher-_link-man 79 | mock_command basher-_link-completions 80 | 81 | run basher-install username/package@v1.2.3 82 | 83 | assert_line "basher-_clone false github.com username/package v1.2.3" 84 | assert_line "basher-_deps username/package" 85 | assert_line "basher-_link-bins username/package" 86 | assert_line "basher-_link-man username/package" 87 | assert_line "basher-_link-completions username/package" 88 | } 89 | 90 | @test "empty version is ignored" { 91 | mock_command basher-_clone 92 | mock_command basher-_deps 93 | mock_command basher-_link-bins 94 | mock_command basher-_link-man 95 | mock_command basher-_link-completions 96 | 97 | run basher-install username/package@ 98 | 99 | assert_line "basher-_clone false github.com username/package" 100 | } 101 | 102 | @test "doesn't fail" { 103 | create_package username/package 104 | mock_clone 105 | 106 | run basher-install username/package 107 | assert_success 108 | } 109 | 110 | @test "installs package with custom folder name" { 111 | mock_command basher-_clone 112 | mock_command basher-_deps 113 | mock_command basher-_link-bins 114 | mock_command basher-_link-man 115 | mock_command basher-_link-completions 116 | 117 | run basher-install username/package my/folder 118 | 119 | assert_line "basher-_clone false github.com username/package my/folder" 120 | assert_line "basher-_deps my/folder" 121 | assert_line "basher-_link-bins my/folder" 122 | assert_line "basher-_link-man my/folder" 123 | assert_line "basher-_link-completions my/folder" 124 | } 125 | 126 | @test "installs package with custom folder name and version" { 127 | mock_command basher-_clone 128 | mock_command basher-_deps 129 | mock_command basher-_link-bins 130 | mock_command basher-_link-man 131 | mock_command basher-_link-completions 132 | 133 | run basher-install username/package@v1.2.3 my/folder 134 | 135 | assert_line "basher-_clone false github.com username/package v1.2.3 my/folder" 136 | assert_line "basher-_deps my/folder" 137 | assert_line "basher-_link-bins my/folder" 138 | assert_line "basher-_link-man my/folder" 139 | assert_line "basher-_link-completions my/folder" 140 | } 141 | 142 | 143 | @test "rejects invalid custom folder name format" { 144 | run basher-install username/package invalid-folder 145 | assert_failure 146 | assert_line "Optional argunment [folder] must be in the format <...>/<...>" 147 | } 148 | -------------------------------------------------------------------------------- /tests/lib/package_helpers.bash: -------------------------------------------------------------------------------- 1 | create_package() { 2 | local package="$1" 3 | mkdir -p "${BASHER_ORIGIN_DIR}/$package" 4 | cd "${BASHER_ORIGIN_DIR}/$package" 5 | git init . 6 | touch README 7 | touch package.sh 8 | git add . 9 | git commit -m "Initial commit" 10 | cd "${BASHER_CWD}" 11 | } 12 | 13 | create_file() { 14 | local package="$1" 15 | local filename="$2" 16 | local content="$3" 17 | 18 | cd "${BASHER_ORIGIN_DIR}/$package" 19 | echo "$content" > "$filename" 20 | 21 | git add . 22 | git commit -m "Add $filename" 23 | cd "${BASHER_CWD}" 24 | } 25 | 26 | create_man() { 27 | local package="$1" 28 | local man="$2" 29 | cd "${BASHER_ORIGIN_DIR}/$package" 30 | mkdir -p man 31 | touch "man/$man" 32 | 33 | git add . 34 | git commit -m "Add $man" 35 | cd "${BASHER_CWD}" 36 | } 37 | 38 | create_package_exec() { 39 | local package="$1" 40 | local exec="package_bin/$2" 41 | cd "${BASHER_ORIGIN_DIR}/$package" 42 | mkdir -p package_bin 43 | touch $exec 44 | 45 | touch "package.sh" 46 | 47 | if grep -sq "BINS=" "package.sh"; then 48 | sed -e "/^BINS=/ s;$;:$exec;" package.sh > package.sh.tmp 49 | mv package.sh.tmp package.sh 50 | else 51 | echo "BINS=$exec" >> package.sh 52 | fi 53 | 54 | git add . 55 | git commit -m "Add package exec: $exec" 56 | cd ${BASHER_CWD} 57 | } 58 | 59 | create_exec() { 60 | local package="$1" 61 | local exec="$2" 62 | cd "${BASHER_ORIGIN_DIR}/$package" 63 | mkdir -p bin 64 | touch bin/$exec 65 | chmod +x bin/$exec 66 | 67 | git add . 68 | git commit -m "Add $exec" 69 | cd ${BASHER_CWD} 70 | } 71 | 72 | remove_exec() { 73 | local package="$1" 74 | local exec="$2" 75 | cd "${BASHER_ORIGIN_DIR}/$package" 76 | git rm bin/$exec 77 | git commit -a -m "Remove $exec" 78 | cd ${BASHER_CWD} 79 | } 80 | 81 | create_root_exec() { 82 | local package="$1" 83 | local exec="$2" 84 | cd "${BASHER_ORIGIN_DIR}/$package" 85 | touch $exec 86 | chmod +x "$exec" 87 | 88 | git add . 89 | git commit -m "Add root exec: $exec" 90 | cd ${BASHER_CWD} 91 | } 92 | 93 | set_remove_extension() { 94 | local package="$1" 95 | local remove_extension="$2" 96 | cd "${BASHER_ORIGIN_DIR}/$package" 97 | 98 | touch "package.sh" 99 | 100 | if grep -sq "REMOVE_EXTENSION=" "package.sh"; then 101 | sed -e "s/^REMOVE_EXTENSION=$remove_extension//" package.sh > package.sh.tmp 102 | mv package.sh.tmp package.sh 103 | else 104 | echo "REMOVE_EXTENSION=$remove_extension" >> package.sh 105 | fi 106 | 107 | git add . 108 | git commit -m "Set REMOVE_EXTENSION to $remove_extension." 109 | cd ${BASHER_CWD} 110 | } 111 | 112 | create_dep() { 113 | local package="$1" 114 | local dep="$2" 115 | cd "${BASHER_ORIGIN_DIR}/$package" 116 | 117 | touch "package.sh" 118 | 119 | if grep -sq "DEPS=" "package.sh"; then 120 | sed -e "/^DEPS=/ s;$;:$dep;" package.sh > package.sh.tmp 121 | mv package.sh.tmp package.sh 122 | else 123 | echo "DEPS=$dep" >> package.sh 124 | fi 125 | 126 | git add . 127 | git commit -m "Add dependency on $dep" 128 | cd ${BASHER_CWD} 129 | } 130 | 131 | create_bash_completions() { 132 | local package="$1" 133 | local comp="$2" 134 | cd "${BASHER_ORIGIN_DIR}/$package" 135 | mkdir -p completions 136 | touch completions/$comp 137 | 138 | touch "package.sh" 139 | 140 | if grep -sq "BASH_COMPLETIONS=" "package.sh"; then 141 | sed -e "/^BASH_COMPLETIONS=/ s/$/:completions\/$comp/" package.sh > package.sh.tmp 142 | mv package.sh.tmp package.sh 143 | else 144 | echo "BASH_COMPLETIONS=completions/$comp" >> package.sh 145 | fi 146 | 147 | git add . 148 | git commit -m "Add bash completions" 149 | cd ${BASHER_CWD} 150 | } 151 | 152 | create_zsh_compsys_completions() { 153 | local package="$1" 154 | local comp="$2" 155 | cd "${BASHER_ORIGIN_DIR}/$package" 156 | mkdir -p completions 157 | echo "#compdef $2" > completions/$comp 158 | 159 | touch "package.sh" 160 | 161 | if grep -sq "ZSH_COMPLETIONS=" "package.sh"; then 162 | sed -e "/^ZSH_COMPLETIONS=/ s/$/:completions\/$comp/" package.sh > package.sh.tmp 163 | mv package.sh.tmp package.sh 164 | else 165 | echo "ZSH_COMPLETIONS=completions/$comp" >> package.sh 166 | fi 167 | 168 | git add . 169 | git commit -m "Add bash completions" 170 | cd ${BASHER_CWD} 171 | } 172 | 173 | create_zsh_compctl_completions() { 174 | local package="$1" 175 | local comp="$2" 176 | cd "${BASHER_ORIGIN_DIR}/$package" 177 | mkdir -p completions 178 | touch completions/$comp 179 | 180 | touch "package.sh" 181 | 182 | if grep -sq "ZSH_COMPLETIONS=" "package.sh"; then 183 | sed -e "/^ZSH_COMPLETIONS=/ s/$/:completions\/$comp/" package.sh > package.sh.tmp 184 | mv package.sh.tmp package.sh 185 | else 186 | echo "ZSH_COMPLETIONS=completions/$comp" >> package.sh 187 | fi 188 | 189 | git add . 190 | git commit -m "Add bash completions" 191 | cd ${BASHER_CWD} 192 | } 193 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # basher 2 | 3 | A package manager for shell scripts and functions. 4 | 5 | Basher allows you to quickly install shell packages directly from github (or 6 | other sites). Instead of looking for specific install instructions for each 7 | package and messing with your path, basher will create a central location for 8 | all packages and manage their binaries for you. 9 | 10 | Even though it is called basher, it also works with zsh and fish. 11 | 12 | [![Build Status](https://travis-ci.org/basherpm/basher.svg?branch=master)](https://travis-ci.org/basherpm/basher) 13 | 14 | ## Installation 15 | 16 | You can install Basher in 1 line. This will install Basher, and add it to your .bashrc/.zshrc file - in a way that can automatically be uninstalled later: 17 | 18 | curl -s https://raw.githubusercontent.com/basherpm/basher/master/install.sh | bash 19 | 20 | #### Install on Mac OSX 21 | 22 | Basher requires `bash >= 4`, and the `realpath` utility from `coreutils`. On 23 | osx you can install both with brew: 24 | 25 | ``` 26 | $ brew install bash coreutils 27 | ``` 28 | 29 | #### Manual method to Install 30 | 31 | 1. Checkout basher on `~/.basher` 32 | 33 | ~~~ sh 34 | $ git clone --depth=1 https://github.com/basherpm/basher.git ~/.basher 35 | ~~~ 36 | 37 | 2. Initialize basher in your shell initialization 38 | 39 | ~~~ sh 40 | export PATH="$HOME/.basher/bin:$PATH" 41 | eval "$(basher init - bash)" # replace `bash` with `zsh` if you use zsh 42 | ~~~ 43 | 44 | **Fish**: Use the following commands instead: 45 | 46 | ~~~ sh 47 | if test -d ~/.basher 48 | set basher ~/.basher/bin 49 | end 50 | set -gx PATH $basher $PATH 51 | status --is-interactive; and . (basher init - fish|psub) 52 | ~~~ 53 | 54 | 55 | ## Updating 56 | 57 | Go to the directory where you cloned basher and pull the latest changes: 58 | 59 | ~~~ sh 60 | $ cd ~/.basher 61 | $ git pull 62 | ~~~ 63 | 64 | ## Usage 65 | 66 | ### Installing packages from Github 67 | 68 | ~~~ sh 69 | $ basher install sstephenson/bats 70 | ~~~ 71 | 72 | This will install bats from https://github.com/sstephenson/bats and add `bin/bats` to the PATH. 73 | 74 | ### Installing packages from other sites 75 | 76 | ~~~ sh 77 | $ basher install bitbucket.org/user/repo_name 78 | ~~~ 79 | 80 | This will install `repo_name` from https://bitbucket.org/user/repo_name 81 | 82 | ### Using ssh instead of https 83 | 84 | If you want to do local development on installed packages and you have ssh 85 | access to the site, use `--ssh` to override the protocol: 86 | 87 | ~~~ sh 88 | $ basher install --ssh juanibiapina/gg 89 | ~~~ 90 | 91 | ### Installing with a custom folder name 92 | 93 | You can install a package with a custom folder name by providing a second argument: 94 | 95 | ~~~ sh 96 | $ basher install sstephenson/bats bats-core/bats 97 | ~~~ 98 | 99 | This will install the package into `~/.basher/packages/bats-core/bats` instead of the default location. And it will appear in `basher list` as `bats-core/bats`. 100 | 101 | ### Installing a local package 102 | 103 | If you develop a package locally and want to try it through basher, 104 | use the `link` command: 105 | 106 | ~~~ sh 107 | $ basher link directory my_namespace/my_package 108 | ~~~ 109 | 110 | The `link` command will install the dependencies of the local package. 111 | You can prevent that with the `--no-deps` option: 112 | 113 | ~~~ sh 114 | $ basher link --no-deps directory my_namespace/my_package 115 | ~~~ 116 | 117 | ### Sourcing files from a package into current shell 118 | 119 | Basher provides an `include` function that allows sourcing files into the 120 | current shell. After installing a package, you can run: 121 | 122 | ``` 123 | include username/repo lib/file.sh 124 | ``` 125 | 126 | This will source a file `lib/file.sh` under the package `username/repo`. 127 | 128 | ### Command summary 129 | 130 | - `basher commands` - List commands 131 | - `basher help ` - Display help for a command 132 | - `basher install [--ssh] [site]/[@ref] [folder]` - Install a package 133 | - `basher uninstall ` - Uninstall a package 134 | - `basher list [-v]` - List installed packages 135 | - `basher outdated` - List packages which are not in the latest version 136 | - `basher upgrade ` - Upgrade a package to the latest version 137 | 138 | ### Configuration options 139 | 140 | To change the behavior of basher, you can set the following variables either 141 | globally or before each command: 142 | 143 | - If `$XDG_DATA_HOME/basher` is a directory, then `$BASHER_ROOT` will be set to `$XDG_DATA_HOME/basher` instead of the usual `$HOME/.basher`. If `$XDG_DATA_HOME` is not set or is empty, then it defaults to `~/.local/share`. 144 | - `BASHER_FULL_CLONE=true` - Clones the full repo history instead of only the last commit (useful for package development) 145 | - `BASHER_PREFIX` - set the installation and package checkout prefix (default is `$BASHER_ROOT/cellar`). Setting this to `/usr/local`, for example, will install binaries to `/usr/local/bin`, manpages to `/usr/local/man`, completions to `/usr/local/completions`, and clone packages to `/usr/local/packages`. This allows you to manage "global packages", distinct from individual user packages. 146 | 147 | ## Packages 148 | 149 | Packages are simply repos (username/repo). You may also specify a site 150 | (site/username/repo). 151 | 152 | Any files inside a bin directory are added to the path. If there is no bin 153 | directory, any executable files in the package root are added to the path. 154 | 155 | Any manpages (files ended in `\.[0-9]`) inside a `man` directory are added 156 | to the manpath. 157 | 158 | Optionally, a repo might contain a `package.sh` file which specifies binaries, 159 | dependencies and completions in the following format: 160 | 161 | ~~~ sh 162 | BINS=folder/file1:folder/file2.sh 163 | DEPS=user1/repo1:user2/repo2 164 | BASH_COMPLETIONS=completions/package 165 | ZSH_COMPLETIONS=completions/_package 166 | ~~~ 167 | 168 | BINS specified in this fashion have higher precedence then the inference rules 169 | above. 170 | 171 | ### Package Directory 172 | 173 | A list of working packages can be found on https://www.basher.it/. There 174 | you can also find a badge if you want to include it in your readme: 175 | 176 | [![basher install](https://img.shields.io/badge/basher-install-white?logo=gnu-bash&style=flat)](https://www.basher.it/package/) 177 | --------------------------------------------------------------------------------