shellspec/shellspec

View on GitHub
install.sh

Summary

Maintainability
Test Coverage
F
53%
#!/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: <PREFIX>/bin]
  -d, --dir DIR         Specify installation directory [default: <PREFIX>/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