#!/bin/sh # shellcheck disable=SC2034 test || __() { :; } installer="https://git.io/shellspec" repo="https://github.com/shellspec/shellspec.git" archive="https://github.com/shellspec/shellspec/archive" project="shellspec" exec="shellspec" set -eu && :<<'USAGE' Usage: [sudo] ${0##*/} [VERSION] [OPTIONS...] or : wget -O- $installer | [sudo] sh or : wget -O- $installer | [sudo] sh -s -- [OPTIONS...] or : wget -O- $installer | [sudo] sh -s VERSION [OPTIONS...] or : curl -fsSL $installer | [sudo] sh or : curl -fsSL $installer | [sudo] sh -s -- [OPTIONS...] or : curl -fsSL $installer | [sudo] sh -s VERSION [OPTIONS...] VERSION: Specify install version and method e.g 1.0.0 Install 1.0.0 from git master Install master from git 1.0.0.tar.gz Install 1.0.0 from tar.gz archive . Install from local directory OPTIONS: -p, --prefix PREFIX Specify prefix [default: \$HOME/.local] -b, --bin BIN Specify bin directory [default: /bin] -d, --dir DIR Specify installation directory [default: /lib/$project] -s, --switch Switch version (requires installation via git) -l, --list List available versions (tags) --pre Include pre-release --fetch FETCH Force command to use when installing from archive (curl or wget) -y, --yes Automatic yes to prompts -h, --help You're looking at it USAGE usage() { while IFS= read -r line && [ ! "${line#*:}" = \<\<"'$1'" ]; do :; done while IFS= read -r line && [ ! "$line" = "$1" ]; do set "$@" "$line"; done shift && [ $# -eq 0 ] || printf '%s\n' cat\<\<"$line" "$@" "$line" } CDPATH='' [ "${ZSH_VERSION:-}" ] && setopt shwordsplit finish() { done=1; exit "${1:-0}"; } error() { printf '\033[31m%s\033[0m\n' "$1"; } abort() { [ "${1:-}" ] && error "$1" >&2; finish 1; } finished() { [ "$done" ] || error "Failed to install"; } exists() { type "$1" >/dev/null 2>&1 && return 0 ( IFS=:; for p in $PATH; do [ -x "${p%/}/$1" ] && return 0; done; return 1 ) } prompt() { set -- "$1" "$2" "${3:-/dev/tty}" printf "%s " "$1" if eval "[ \"\$$2\" ] && :"; then eval "printf \"%s\n\" \"\$$2\"" else IFS= read -r "$2" < "$3" || return 1 [ "$3" = "/dev/tty" ] || eval "printf \"%s\n\" \"\$$2\"" fi } is_yes() { case $1 in ( [Yy] | [Yy][Ee][Ss] ) return 0; esac return 1 } confirm() { prompt "$@" || return 1 eval "is_yes \"\$$2\" &&:" } fetch() { tmpfile="${TMPDIR:-${TMP:-/tmp}}/${1##*/}.$$" case $FETCH in curl) curl --head -sSfL -o /dev/null "$1" && curl -SfL "$1" ;; wget) wget --spider -q "$1" && wget -O- "$1" ;; esac > "$tmpfile" &&: error=$? if [ "$error" -eq 0 ]; then unarchive "$tmpfile" "$1" "$2" &&: error=$? [ "$error" -ne 0 ] && [ -d "$2" ] && rm -rf "$2" fi rm "$tmpfile" return "$error" } unarchive() { mkdir -p "${3%/*}" gunzip -c "$1" | (cd "${3%/*}"; tar xf -) set -- "$1" "${2##*/}" "$3" mv "$(components_path "${3%/*}/$project-${2%.tar.gz}"*)" "$3" } components_path() { ( set +u cd "${1%/*}" for p in *; do case $p in (${1##*/}*) echo "${1%/*}/$p"; break ; esac done ) } git_remote_tags() { git ls-remote --tags "$repo" | while read -r line; do # shellcheck disable=SC2295 tag=${line##*/} && pre=${tag#${tag%%[-+]*}} [ "${1:-}" = "--pre" ] || case $pre in (-*) continue; esac echo "${tag%\^\{\}}" done | uniq } get_versions() { git_remote_tags "${PRE:+--pre}" } sort_by_first_key() { # Retry if sort is Windows version ( export LC_ALL=C; sort -k 1 2>/dev/null || command -p sort -k 1 ) } version_sort() { while read -r version; do ver=${version%%+*} && num=${ver%%-*} && pre=${ver#"$num"} #shellcheck disable=SC2086 case $num in *[!0-9.]*) set -- 0 0 0 0 ;; *) IFS=. && set -- $num ;; esac printf '%08d%08d%08d%08d' "${1:-0}" "${2:-0}" "${3:-0}" "${4:-0}" printf '%s %s\n' "${pre:-=}" "$version" done | sort_by_first_key | while read -r kv; do echo "${kv#* }"; done } join() { s='' while read -r v; do s="$s$v$1" done echo "${s%"$1"}" } last() { version='' while read -r v; do version=$v done echo "$version" } list_versions() { get_versions | version_sort | join ", " } latest_version() { get_versions | version_sort | last } ${__SOURCED__:+false} : || return 0 trap finished EXIT VERSION='' PREFIX=$HOME/.local BIN='' DIR='' SWITCH='' PRE='' YES='' FETCH='' done='' mode=install __ parse_option __ while [ $# -gt 0 ]; do case $1 in -p | --prefix ) [ "${2:-}" ] || abort "PREFIX not specified" PREFIX=$2 && shift ;; -b | --bin ) [ "${2:-}" ] || abort "BIN not specified" BIN=$2 && shift ;; -d | --dir ) [ "${2:-}" ] || abort "DIR not specified" DIR=$2 && shift ;; -s | --switch ) SWITCH=1 ;; -y | --yes ) YES=y ;; -l | --list ) mode=list ;; --pre ) PRE=1 ;; --fetch ) [ "${2:-}" ] || abort "FETCH not specified" case $2 in ( curl | wget ) FETCH=$2 && shift ;; *) abort "FETCH must be 'curl' or 'wget'." esac ;; -h | --help ) eval "$(usage "USAGE" < "$0")" && finish ;; -* ) abort "Unknown option $1" ;; * ) VERSION=$1 ;; esac shift done if [ "$mode" = "list" ]; then list_versions finish fi BIN=${BIN:-${PREFIX%/}/bin} DIR=${DIR:-${PREFIX%/}/lib/$project} __ main __ case $VERSION in .) method=local DIR=$PWD [ -x "$DIR/$exec" ] || abort "Not found '$exec' in installation directory: '$DIR'" VERSION=$("$DIR/$exec" --version) ;; *.tar.gz) [ "$SWITCH" ] && abort "Can not switch version when install from archive" [ -e "$DIR" ] && abort "Already exists installation directory: '$DIR'" method=archive [ ! "$FETCH" ] && exists curl && FETCH=curl [ ! "$FETCH" ] && exists wget && FETCH=wget [ "$FETCH" ] || abort "Requires 'curl' or 'wget' when install from archive" exists tar || abort "Not found 'tar' when install from archive" ;; *) if [ "$SWITCH" ]; then method=switch [ -d "$DIR" ] || abort "Not found installation directory: '$DIR'" [ -d "$DIR/.git" ] || abort "Can't switch it's not a git repository: '$DIR'" else method=git [ -e "$DIR" ] && abort "Already exists installation directory: '$DIR'" fi # requires git >= 1.7.10.4 exists git || abort "Requires 'git' when install from git repository" [ "$VERSION" ] || VERSION=$(latest_version) esac echo "Executable file : $BIN/$exec" echo "Installation directory : $DIR" echo "Version (tag or commit): $VERSION" case $method in git) echo "[git] $repo" ;; archive) echo "[$FETCH] $archive/$VERSION" ;; esac echo confirm "Do you want to continue? [y/N]" YES || abort "Canceled" case $method in git) git init "$DIR" && cd "$DIR" git remote add origin "$repo" git fetch --depth=1 origin "$VERSION" git checkout -b "$VERSION" FETCH_HEAD ;; archive) fetch "$archive/$VERSION" "$DIR" ;; switch) cd "$DIR" if message=$(git checkout "$VERSION" 2>&1); then echo "$message" else git fetch --depth=1 origin "$VERSION" git checkout -b "$VERSION" FETCH_HEAD fi ;; local) # Do nothing esac mkdir -p "$BIN" ln -sf "$DIR/$exec" "$BIN/$exec" if [ ! -L "$BIN/$exec" ]; then rm "$BIN/$exec" printf '#!/bin/sh\nexec "%s" "$@"\n' "$DIR/$exec" > "$BIN/$exec" chmod +x "$BIN/$exec" fi echo "Done" finish