#!/usr/bin/env bash # # Copyright 2014 David Vazgenovich Shakaryan # Distributed under the terms of the MIT License. # # gbpm # https://git.potato.am/gbpm.git/ # Manage multiple Python/Ruby/etc. installations with no black magic. # # Output must be executed: gbpm() { . <(/path/to/gbpm.sh "${@}"); } GBPM_VERSION='0.4' : ${GBPM_CMD:='gbpm'} : ${GBPM_LABEL:='version'} : ${GBPM_LABEL_PL:="${GBPM_LABEL}s"} : ${GBPM_PATH:="${HOME}/.pythons"} _echo() { echo "echo '${@//\'/\'\\\'\'}'" } _export() { echo "export PATH='${PATH//\'/\'\\\'\'}'" echo 'hash -r' } _warn() { echo "echo '${GBPM_CMD}: ${@//\'/\'\\\'\'}' >&2" } _die() { [[ -n "${@}" ]] && _warn "${@}" echo 'false' exit 1 } _populate_dirs() { [[ -d "${GBPM_PATH}" ]] || _die "directory ${GBPM_PATH} does not exist" mapfile -td $'\0' dirs < <(shopt -s nullglob; printf '%s\0' "${GBPM_PATH}/"* | sort -zV) [[ -n "${dirs}" ]] || _die "directory ${GBPM_PATH} is empty" } _populate_selected() { local dir dirs mapfile -td : dirs < <(printf %s "${PATH}") selected=() for dir in "${dirs[@]}"; do if [[ "${dir}" == "${GBPM_PATH}/"* ]]; then dir="${dir%/bin}" selected+=("${dir##*/}") fi done [[ "${#selected[@]}" -ne 0 ]] || return 1 [[ "${#selected[@]}" -eq 1 ]] \ || _warn 'warning: multiple managed paths found in PATH' } _clear() { local dir dirs cdirs mapfile -td : dirs < <(printf %s "${PATH}") for dir in "${dirs[@]}"; do if [[ "${dir}" == "${GBPM_PATH}/"* ]]; then _echo "removing ${dir} from PATH" else cdirs+=("${dir}") fi done [[ "${#cdirs[@]}" -ne "${#dirs[@]}" ]] || return 1 PATH="$(IFS=':'; echo "${cdirs[*]}")" } _add() { _echo "adding ${1}/bin to PATH" PATH="${1}/bin:${PATH}" } cmd_ls() { local dir dirs selected str _populate_dirs _populate_selected for dir in "${dirs[@]}"; do str=" ${dir##*/}" [[ "${dir##*/}" == "${selected}" ]] && str="${str/ /*}" _echo "${str}" done } cmd_get() { local dir selected _populate_selected || _die 'no managed path found in PATH' for dir in "${selected[@]}"; do _echo "${dir}" done } cmd_set() { [[ "${#}" -eq 0 ]] && _die 'set command requires an argument' local dir dirs bn prefix pri match match_pri _populate_dirs for dir in "${dirs[@]}"; do bn="${dir##*/}" [[ "${bn}" == *"${1}"* ]] || continue # lower depth (i.e. fewer hyphens before match) is preferred, # e.g. `2' matches `python-2.0' over `python-3.0-2'. # # priority at the same depth, using `2.1' as an example: # - from start/hyphen to end (python-2.1) # - from start/hyphen to hyphen (python-2.1-2) # - from start/hyphen to dot (python-2.1.3) # - from start/hyphen (python-2.11) # - other (python-3.2.1) # # at the same priority, the last match (i.e. greatest # version-sorted string) is used, e.g. `3' matches # `python-3.14' over `python-3.2'. prefix="${bn%%${1}*}" pri="${prefix//[^-]}" pri="$((${#pri} * 5))" if [[ "${prefix}" == @(|*-) ]]; then case "${bn#*${1}}" in '') ;; -*) ((pri++)) ;; .*) ((pri += 2)) ;; *) ((pri += 3)) ;; esac else ((pri += 4)) fi [[ "${pri}" -le "${match_pri:-${pri}}" ]] || continue match="${dir}" match_pri="${pri}" done [[ -n "${match}" ]] || _die "no matching ${GBPM_LABEL} found" _clear _add "${match}" _export } cmd_clear() { _clear || _die 'no managed path found in PATH' _export } cmd_version() { _echo "${GBPM_CMD} ${GBPM_VERSION}" } cmd_help() { _echo "Usage: ${GBPM_CMD} [args]" _echo _echo 'Commands:' _echo " ls List available ${GBPM_LABEL_PL}" _echo " get Print currently selected ${GBPM_LABEL}" _echo " set Select specified ${GBPM_LABEL}" _echo " clear Clear any selected ${GBPM_LABEL_PL}" _echo " version Print ${GBPM_CMD} version" } case "${1}" in 'ls') cmd_ls ;; 'get') cmd_get ;; 'set') cmd_set "${@:2}" ;; 'clear') cmd_clear ;; 'version') cmd_version ;; *) cmd_help ;; esac