-
-
Save roktas/895fbedc328e23d98ba7734be344d0ba to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
# Copyright (c) 2020, Recai Oktaş | |
# All rights reserved. | |
# | |
# Redistribution and use in source and binary forms, with or without | |
# modification, are permitted provided that the following conditions are met: | |
# | |
# * Redistributions of source code must retain the above copyright notice, this | |
# list of conditions and the following disclaimer. | |
# | |
# * Redistributions in binary form must reproduce the above copyright notice, | |
# this list of conditions and the following disclaimer in the documentation | |
# and/or other materials provided with the distribution. | |
# | |
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | |
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
[ -n "${BASH_VERSION:-}" ] || { echo >&2 'Bash required.'; exit 1; } | |
[[ ${BASH_VERSINFO[0]:-} -ge 4 ]] || { echo >&2 'Bash version 4 or higher required.'; exit 1; } | |
set -Eeuo pipefail; shopt -s nullglob; [[ -z ${TRACE:-} ]] || set -x; unset CDPATH; IFS=$' \t\n' | |
export LC_ALL=C.UTF-8 LANG=C.UTF-8 | |
# shellcheck disable=2034 | |
declare -gr PROGNAME=${0##*/} # Program name | |
# shellcheck disable=2120 | |
.cry() { | |
if [[ $# -gt 0 ]]; then | |
echo -e >&2 "W: $*" | |
else | |
echo >&2 "" | |
fi | |
} | |
# shellcheck disable=2120 | |
.die() { | |
if [[ $# -gt 0 ]]; then | |
echo -e >&2 "E: $*" | |
else | |
echo >&2 "" | |
fi | |
exit 1 | |
} | |
# shellcheck disable=2120 | |
.haw() { | |
echo -en "${@-""}" >&2 | |
} | |
# shellcheck disable=2120 | |
.say() { | |
echo -e "${@-""}" >&2 | |
} | |
.available() { | |
command -v "${1?${FUNCNAME[0]}: missing argument}" &>/dev/null | |
} | |
.callable() { | |
[[ $(type -t "${1?${FUNCNAME[0]}: missing argument}" || true) == function ]] | |
} | |
.chmog() { | |
local mog=${1?${FUNCNAME[0]}: missing argument}; shift | |
local dst=${1?${FUNCNAME[0]}: missing argument}; shift | |
local mode owner group | |
IFS=: read -r mode owner group <<<"$mog" | |
[[ -z ${mode:-} ]] || chmod "$mode" "$dst" | |
[[ -z ${owner:-} ]] || chown "$owner" "$dst" | |
[[ -z ${group:-} ]] || chgrp "$group" "$dst" | |
} | |
.cry-() { | |
local default=${1?${FUNCNAME[0]}: missing argument}; shift | |
local mesg | |
while [[ $# -gt 0 ]]; do | |
case $1 in | |
--) | |
shift | |
mesg=$* | |
break | |
;; | |
esac | |
shift | |
done | |
.cry "${mesg:-$default}" | |
} | |
.contains() { | |
: "${1?${FUNCNAME[0]}: missing argument}" | |
local element | |
for element in "${@:2}"; do | |
if [[ $element = "$1" ]]; then | |
return 0 | |
fi | |
done | |
return 1 | |
} | |
.contains-() { | |
local needle="${1?${FUNCNAME[0]}: missing argument}"; shift | |
local -n contains_="${1?${FUNCNAME[0]}: missing argument}"; shift | |
local element | |
for element in "${contains_[@]}"; do | |
if [[ $element = "$needle" ]]; then | |
return 0 | |
fi | |
done | |
return 1 | |
} | |
.die-() { | |
local default=${1?${FUNCNAME[0]}: missing argument}; shift | |
local mesg | |
while [[ $# -gt 0 ]]; do | |
case $1 in | |
--) | |
shift | |
mesg=$* | |
break | |
;; | |
esac | |
shift | |
done | |
.die "${mesg:-$default}" | |
} | |
.expired() { | |
local -i expiry=${1?${FUNCNAME[0]}: missing argument}; shift | |
case $expiry in | |
-1) return 1 ;; | |
0) return 0 ;; | |
esac | |
local file | |
for file; do | |
local t=d | |
[[ -d $file ]] || t=f | |
if [[ -e $file ]] && [[ -z $(find "$file" -maxdepth 0 -type "$t" -mmin +"$expiry" 2>/dev/null) ]]; then | |
return 1 | |
fi | |
done | |
return 0 | |
} | |
.inside() { | |
local dir=${1?${FUNCNAME[0]}: missing argument}; shift | |
[[ $# -gt 0 ]] || return 0 | |
builtin pushd "$dir" >/dev/null || exit | |
"$@" | |
builtin popd >/dev/null || exit | |
if [[ $(type -t "$1" || true) == function ]] && [[ $1 =~ [.]$ ]]; then | |
unset -f "$1" | |
fi | |
} | |
# Execute command in a temp dir | |
.intemp() { | |
[[ $# -gt 0 ]] || return 0 | |
local tmp | |
tmp=$(mktemp -p "${TMPDIR:-/tmp}" -d "$PROGNAME".XXXXXXXX) || exit | |
local err | |
(builtin cd "$tmp" && "$@") || err=$? | |
rm -rf "$tmp" | |
if [[ $(type -t "$1" || true) == function ]] && [[ $1 =~ [.]$ ]]; then | |
unset -f "$1" | |
fi | |
return ${err:-0} | |
} | |
.interactive() { | |
[[ -t 1 ]] | |
} | |
# Join array with the given separator | |
.join() { | |
local IFS=${1?${FUNCNAME[0]}: missing argument}; shift | |
echo -n "$*" | |
} | |
# Join array ref with the given separator | |
.join-() { | |
local IFS=${1?${FUNCNAME[0]}: missing argument}; shift | |
local -n join_=${1?${FUNCNAME[0]}: missing argument}; shift | |
echo -n "${join_[*]}" | |
} | |
# shim.sh - Shims (mostly for UI) | |
# shellcheck disable=2120 | |
.bye() { | |
if [[ $# -gt 0 ]]; then | |
echo -e >&2 "$*" | |
else | |
echo >&2 "" | |
fi | |
exit 0 | |
} | |
.bug() { | |
if [[ $# -gt 0 ]]; then | |
echo -e >&2 "?: $*" | |
else | |
echo >&2 "" | |
fi | |
exit 127 | |
} | |
.calling() { | |
local message="${1?${FUNCNAME[0]}: missing argument}"; shift | |
.say "--> $message" | |
"$@" | |
} | |
.getting() { | |
local message="${1?${FUNCNAME[0]}: missing argument}"; shift | |
.say "... $message" | |
"$@" | |
} | |
.heading() { | |
local message="${1?${FUNCNAME[0]}: missing argument}"; shift | |
.say "==> $message" | |
"$@" | |
} | |
.running() { | |
local message="${1?${FUNCNAME[0]}: missing argument}"; shift | |
.say "--> $message" | |
"$@" | |
} | |
# git.sh - Git functions | |
git.clone() { | |
local src=${1?${FUNCNAME[0]}: missing argument}; shift | |
local dst=${1?${FUNCNAME[0]}: missing argument}; shift | |
local ref=${1:-} | |
[[ ! -e $dst ]] || .die "Destination already exists: $dst" | |
local options=( | |
'--single-branch' | |
'--quiet' | |
) | |
[[ -z ${ref:-} ]] || options+=( | |
'--branch' | |
"$ref" | |
) | |
.getting "Cloning $src" | |
git clone "${options[@]}" "$src" "$dst" | |
if [[ -n ${ref:-} ]] && git.ref.is "$ref" tag; then | |
git.set.immutable true | |
fi | |
} | |
git.default-branch() { | |
local repo=${1:-.} | |
git.must "$repo" sane | |
git -C "$repo" symbolic-ref refs/remotes/origin/HEAD | sed 's@^refs/remotes/origin/@@' | |
} | |
git.is() { | |
local repo=${1?${FUNCNAME[0]}: missing argument}; shift | |
local feature=${1?${FUNCNAME[0]}: missing argument}; shift | |
local func=git.is."$feature"_ | |
.callable "$func" || .bug "No such predicate: $feature" | |
"$func" "$repo" | |
} | |
git.must() { | |
local repo=${1?${FUNCNAME[0]}: missing argument}; shift | |
local feature=${1?${FUNCNAME[0]}: missing argument}; shift | |
git.is "$repo" "$feature" || .die "Repository is not ${feature//-/ }: $repo" | |
} | |
git.ref.is() { | |
local repo=${1?${FUNCNAME[0]}: missing argument}; shift | |
local ref=${1?${FUNCNAME[0]}: missing argument}; shift | |
local type=${1?${FUNCNAME[0]}: missing argument}; shift | |
case $type in | |
branch) | |
git -C "$repo" show-ref -q --verify "refs/heads/$ref" &>/dev/null | |
;; | |
tag) | |
git -C "$repo" show-ref -q --verify "refs/tags/$ref" &>/dev/null | |
;; | |
remote) | |
git -C "$repo" show-ref -q --verify "refs/remote/$ref" &>/dev/null | |
;; | |
hash|commit) | |
git -C "$repo" rev-parse --verify "$ref^{commit}" &>/dev/null | |
;; | |
*) | |
.bug "Unrecognized ref type: $type" | |
return 1 | |
esac | |
} | |
git.reset() { | |
local repo=${1:-.} | |
git -C "$repo" reset --hard | |
} | |
git.set.immutable() { | |
local repo=${1:-.} | |
git -C "$repo" config --type bool core.x-immutable true | |
} | |
git.set.mutable() { | |
local repo=${1:-.} | |
git -C "$repo" config --type bool core.x-immutable false | |
} | |
git.switch() { | |
local repo=${1?${FUNCNAME[0]}: missing argument}; shift | |
local branch=${1:-} | |
[[ -n $branch ]] || branch=$(git.default-branch "$repo") | |
git -C "$repo" checkout --quiet "$branch" | |
} | |
git.top() { | |
local path=${1:-} | |
git.must.sane "$path" | |
cd "$(git.topdir "$path")" || exit | |
} | |
git.topdir() { | |
local path=${1:-} | |
if [[ -n $path ]]; then | |
[[ -e $path ]] || .die "No such path found: $path" | |
local d | |
[[ -d $path ]] || d=${path%/*} | |
pushd "$d" >/dev/null || exit | |
fi | |
local dir | |
dir=$(git rev-parse --git-dir) && dir=$(cd "$dir" && pwd)/ && echo "${dir%%/.git/*}" | |
if [[ -n $path ]]; then | |
popd >/dev/null || exit | |
fi | |
} | |
git.update() { | |
local repo=${1:-.} | |
if ! git.is "$repo" immutable; then | |
.getting "Updating repository" | |
git pull -q | |
fi | |
} | |
# git - Private functions | |
git.is.clean_() { | |
local repo=${1:-.} | |
git -C "$repo" rev-parse --verify HEAD >/dev/null && | |
git -C "$repo" update-index -q --ignore-submodules --refresh && | |
git -C "$repo" diff-files --quiet --ignore-submodules && | |
git -C "$repo" diff-index --cached --quiet --ignore-submodules HEAD -- | |
} | |
git.is.git_() { | |
local repo=${1:-.} | |
[[ -d $repo/.git ]] && git rev-parse --resolve-git-dir "$repo/.git" &>/dev/null | |
} | |
git.is.immutable_() { | |
local repo=${1:-.} | |
[[ $(git -C "$repo" config --type bool core.x-immutable 2>/dev/null || true) = true ]] | |
} | |
git.is.sane_() { | |
local repo=${1:-.} | |
git -C "$repo" rev-parse --is-inside-work-tree &>/dev/null || return 1 | |
git -C "$repo" rev-parse --verify HEAD >/dev/null || return 1 | |
} | |
git.is.sane-and-clean_() { | |
git.is.sane_ "$@" && git.is.clean_ "$@" | |
} | |
# file.sh - File related operations | |
file.cp() { | |
local src=${1?${FUNCNAME[0]}: missing argument}; shift | |
local dst=${1?${FUNCNAME[0]}: missing argument}; shift | |
local mog=${1:-} | |
local dir=${dst%/*} | |
[[ -d $dir ]] || mkdir -p "$dir" | |
cp -a "$src" "$dst" | |
[[ -z ${mog:-} ]] || .chmog "$mog" "$dst" | |
} | |
file.hid() { | |
local src=${1?${FUNCNAME[0]}: missing argument}; shift | |
local dir; dir=$(dirname "$src")/... | |
[[ -d $dir ]] || mkdir -p "$dir" | |
mv "$src" "$dir" | |
} | |
file.ln() { | |
local src=${1?${FUNCNAME[0]}: missing argument}; shift | |
local dst=${1?${FUNCNAME[0]}: missing argument}; shift | |
local mog=${1:-} | |
local dir=${dst%/*} | |
[[ -d $dir ]] || mkdir -p "$dir" | |
src=$(realpath -m --relative-base "${dst%/*}" "$src") | |
ln -sf "$src" "$dst" | |
[[ -z ${mog:-} ]] || .chmog "$mog" "$dst" | |
} | |
file.mv() { | |
local src=${1?${FUNCNAME[0]}: missing argument}; shift | |
local dst=${1?${FUNCNAME[0]}: missing argument}; shift | |
local mog=${1:-} | |
local dir=${dst%/*} | |
[[ -d $dir ]] || mkdir -p "$dir" | |
mv -f "$src" "$dst" | |
[[ -z ${mog:-} ]] || .chmog "$mog" "$dst" | |
} | |
file.upcd() { | |
local cwd=${1?${FUNCNAME[0]}: missing argument}; shift | |
cd "$cwd" || exit | |
while :; do | |
local try | |
for try; do | |
if [[ -e $try ]]; then | |
return 0 | |
fi | |
done | |
# shellcheck disable=2128 | |
if [[ $PWD == "/" ]]; then | |
break | |
fi | |
cd .. || exit | |
done | |
} | |
# shellcheck disable=2154 | |
.dispatch() { | |
[[ $# -ne 0 ]] || { .usage; .die 'Command required'; } | |
local help | |
if [[ $1 = help ]]; then | |
help=true | |
shift | |
[[ $# -ne 0 ]] || { .usage; .die 'Help topic required'; } | |
fi | |
local cmd rem=() | |
.resolve _dispatch_ cmd rem "$@" || .die "Wrong or incomplete command: $*" | |
local fun=${_dispatch_[$cmd]} | |
# shellcheck disable=2034 | |
declare -gr CMDNAME=$cmd | |
if [[ -n ${help:-} ]]; then | |
.say "${_document_[$fun]:-}" "" | |
"$fun" -help | |
else | |
"$fun" "${rem[@]}" | |
fi | |
} | |
# shellcheck disable=2034 | |
.resolve() { | |
local -n tab_=${1?${FUNCNAME[0]}: missing argument}; shift | |
local -n cmd_=${1?${FUNCNAME[0]}: missing argument}; shift | |
local -n rem_=${1?${FUNCNAME[0]}: missing argument}; shift | |
local try=() | |
while [[ $# -gt 0 ]]; do | |
try+=("$1") | |
shift | |
if [[ -n ${tab_[${try[*]}]:-} ]]; then | |
cmd_=${try[*]} | |
rem_=("$@") | |
return 0 | |
fi | |
done | |
return 1 | |
} | |
# shellcheck disable=2120 | |
.usage() { | |
case ${1:-} in | |
by-commands|"") | |
.say "${USAGE:-Usage: $PROGNAME <command>... [-<flag>=<value>...] [<args>...]\n\nCommands:}" | |
local cmd | |
# shellcheck disable=2154 | |
for cmd in "${!_dispatch_[@]}"; do | |
local fun=${_dispatch_[$cmd]} | |
local doc=${_document_[$fun]:-} | |
[[ -n $doc ]] || continue | |
printf "\\t%-24s %s\n" "$cmd" "$doc" | |
done | |
;; | |
by-tasks) | |
.say "${USAGE:-Usage: $PROGNAME <task>... [<name>=<value>...]\n\nTasks:}" | |
local fun | |
# shellcheck disable=2154 | |
for fun in "${!_document_[@]}"; do | |
local doc=${_document_[$fun]:-} | |
printf "\\t%-24s %s\n" "$fun" "$doc" | |
done | |
;; | |
*) | |
.die "Unrecognized listing mode: $1" | |
;; | |
esac | sort >&2 | |
} | |
# flag.sh - Flag handling | |
.bool() { | |
local value=${1:-} | |
value=${value,,} | |
case $value in | |
true|t|1|on|yes|y) | |
return 0 | |
;; | |
false|f|0|off|no|n|"") | |
return 1 | |
;; | |
*) | |
.bug "Invalid boolean: $value" | |
esac | |
} | |
flag.args() { | |
local keys=() | |
mapfile -t keys < <( | |
for key in "${!_[@]}"; do | |
[[ $key =~ ^[1-9][0-9]*$ ]] || continue | |
echo "$key" | |
done | sort -u | |
) | |
local key | |
if [[ $# -gt 0 ]]; then | |
# shellcheck disable=2178 | |
local -n _values_=$1 | |
for key in "${keys[@]}"; do | |
_values_+=("${_[$key]}") | |
done | |
else | |
for key in "${keys[@]}"; do | |
echo "${_[$key]}" | |
done | |
fi | |
} | |
flag.env() { | |
local keys=() | |
mapfile -t keys < <( | |
for key in "${!_[@]}"; do | |
[[ $key =~ ^[[:alpha:]_][[:alnum:]_]*$ ]] || continue | |
echo "$key" | |
done | sort -u | |
) | |
local key | |
if [[ $# -gt 0 ]]; then | |
# shellcheck disable=2178 | |
local -n _values_=$1 | |
for key in "${keys[@]}"; do | |
_values_+=("$key='${_[$key]}'") | |
done | |
else | |
for key in "${keys[@]}"; do | |
echo "$key='${_[$key]}'" | |
done | |
fi | |
} | |
flag.false() { | |
! flag.true "$@" | |
} | |
flag.load() { | |
local -n _load_src_=${1?${FUNCNAME[0]}: missing argument}; shift | |
local key | |
for key in "${!_load_src_[@]}"; do | |
# shellcheck disable=2034 | |
_[$key]=${_load_src_[$key]} | |
done | |
} | |
flag.nil() { | |
[[ ${_[$1]:-} = "$NIL" ]] | |
} | |
flag.parse_() { | |
if .contains -help "$@"; then | |
flag.usage-and-bye | |
fi | |
local -A flag_result_ | |
local -i argc=0 | |
while [[ $# -gt 0 ]]; do | |
local key value | |
if [[ $1 =~ ^-*[[:alpha:]_][[:alnum:]_]*= ]]; then | |
key=${1%%=*}; value=${1#*=} | |
if [[ $key =~ ^-.+$ ]] && [[ ! -v _[$key] ]]; then | |
.die "Unrecognized flag: $key" | |
fi | |
if [[ $key =~ ^-.+$ ]]; then | |
[[ -v _[$key] ]] || .die "Unrecognized flag: $key" | |
elif [[ -n ${_[.raw]:-} ]]; then | |
key=$((++argc)); value=$1 | |
fi | |
elif [[ $1 == '--' ]] && [[ -z ${_[.dash]:-} ]]; then | |
shift | |
break | |
else | |
key=$((++argc)); value=$1 | |
fi | |
# shellcheck disable=2034 | |
flag_result_["$key"]=${value:-${_["$key"]:-}} | |
shift | |
done | |
flag.load flag_result_ | |
flag.validate_ "$argc" | |
} | |
flag.peek_() { | |
if .contains -help "$@"; then | |
flag.usage-and-bye | |
fi | |
local -A flag_result_ | |
local -i argc=0 | |
while [[ $# -gt 0 ]]; do | |
local key value | |
if [[ $1 =~ ^-*[[:alpha:]_][[:alnum:]_]*= ]]; then | |
key=${1%%=*}; value=${1#*=} | |
elif [[ $1 == '--' ]] && [[ -z ${_[.dash]:-} ]]; then | |
shift | |
break | |
else | |
key=$((++argc)); value=$1 | |
fi | |
# shellcheck disable=2034 | |
flag_result_["$key"]=${value:-${_["$key"]:-}} | |
shift | |
done | |
flag.load flag_result_ | |
flag.validate_ "$argc" | |
} | |
flag.true() { | |
.bool "${_[$1]:-}" | |
} | |
flag.usage() { | |
local -a cmdname=("$PROGNAME") | |
[[ -z ${CMDNAME:-} ]] || cmdname+=("$CMDNAME") | |
if [[ -n ${_[.desc]:-} ]]; then | |
# shellcheck disable=2128 | |
.say "Usage: ${cmdname[*]} ${_[.desc]}" | |
else | |
# shellcheck disable=2128 | |
.say "Usage: ${cmdname[*]}" | |
fi | |
} | |
flag.usage-and-die() { | |
flag.usage | |
.die "$@" | |
} | |
# shellcheck disable=2120 | |
flag.usage-and-bye() { | |
flag.usage | |
.bye "$@" | |
} | |
# flag - Private functions | |
flag.args_() { | |
local n=${1?${FUNCNAME[0]}: missing argument}; shift | |
local argc=${_[.argc]:-0} | |
[[ $argc != '-' ]] || return 0 | |
local lo hi | |
if [[ $argc =~ ^[0-9]+$ ]]; then | |
lo=$argc; hi=$argc | |
elif [[ $argc =~ ^[0-9]*-[0-9]*$ ]]; then | |
IFS=- read -r lo hi <<<"$argc" | |
else | |
.bug "Incorrect range: $argc" | |
fi | |
local message | |
if [[ -n ${lo:-} ]] && [[ $n -lt $lo ]]; then | |
message='Too few arguments' | |
elif [[ -n ${hi:-} ]] && [[ $n -gt $hi ]]; then | |
message='Too many arguments' | |
else | |
return 0 | |
fi | |
flag.usage-and-die "$message" | |
} | |
flag.nils_() { | |
local required=() | |
local key | |
for key in "${!_[@]}"; do | |
if flag.nil "$key"; then | |
required+=("$key") | |
fi | |
done | |
[[ ${#required[@]} -eq 0 ]] || .die "Value missing for: ${required[*]}" | |
} | |
flag.validate_() { | |
flag.args_ "$@" | |
flag.nils_ | |
} | |
# flag - Init | |
flag.init_() { | |
shopt -s expand_aliases | |
# shellcheck disable=2142,2154 | |
alias flag.parse='flag.parse_ "$@"; local __argv__=() ARGV=("$@"); flag.args __argv__; set -- "${__argv__[@]}"; unset -v __argv__' | |
# shellcheck disable=2142,2154 | |
alias flag.peek='flag.peek_ "$@"; local __argv__=() ARGV=("$@"); flag.args __argv__; set -- "${__argv__[@]}"; unset -v __argv__' | |
# shellcheck disable=2034 | |
declare -gr NIL="\0" | |
} | |
flag.init_ | |
# shellcheck disable=2154 | |
foreach() { | |
local packdir=${1?${FUNCNAME[0]}: missing argument}; shift | |
local func=${1?${FUNCNAME[0]}: missing argument}; shift | |
local -a repos=() | |
local dir | |
for dir in "$packdir"/*/opt/*/*/* "$packdir"/*/start/*; do | |
if [[ -d $dir ]] && git.is "$dir" git; then | |
repos+=("$dir") | |
fi | |
done | |
[[ ${#repos[@]} -gt 0 ]] || .bye "No pack found." | |
for dir in "${repos[@]}"; do | |
local base=${dir#"$packdir"/} | |
local -a addr=() | |
IFS='/' read -ra addr <<< "$base" | |
# FIXME: Ugly workaround | |
if [[ ${#addr[@]} -eq 5 ]]; then | |
local -A _=( | |
[group]=${addr[0]} | |
[kind]=${addr[1]} | |
[provider]=${addr[2]} | |
[user]=${addr[3]} | |
[repo]=${addr[4]} | |
) | |
elif [[ ${#addr[@]} -eq 3 ]]; then | |
local -A _=( | |
[group]=${addr[0]} | |
[kind]=${addr[1]} | |
[provider]='' | |
[user]='' | |
[repo]=${addr[2]} | |
) | |
fi | |
"$func" "$dir" "$@" | |
done | |
} | |
packdirs() { | |
local mustexist=${1:-pack} | |
local -a paths=() | |
IFS=',' read -ra paths <<<"$( | |
nvim -u NONE --headless --cmd 'echo &packpath' --cmd q 2>&1 | |
)" | |
local path | |
if [[ -z $mustexist ]] || [[ $mustexist = - ]]; then | |
for path in "${paths[@]}"; do | |
echo "$path/pack" | |
done | |
else | |
for path in "${paths[@]}"; do | |
[[ -e $path/$mustexist ]] || continue | |
echo "$path/pack" | |
done | |
fi | |
} | |
# update - Update command | |
# Update plugins | |
# shellcheck disable=2154 | |
:update() { | |
# shellcheck disable=2192 | |
local -A _=( | |
[-prefix]=/usr/local/share | |
[.desc]='[-prefix=<dir>]' | |
[.argc]=0 | |
) | |
flag.parse | |
local packdir=${_[-prefix]}/nvim/site/pack | |
[[ -d $packdir ]] || .die "Package directory not found: $packdir" | |
[[ -w $packdir ]] || .die "Package directory not writable: $packdir" | |
local prev= | |
local -a changed=() | |
up() { | |
local pack=${1?${FUNCNAME[0]}: missing argument}; shift | |
local slug="${_[group]}/${_[kind]}" | |
if [[ $slug != "$prev" ]]; then | |
.say " > $slug" | |
prev=$slug | |
fi | |
.haw "\t${_[repo]} " | |
update "$pack" docs || return 0 | |
changed+=("$pack") | |
} | |
foreach "$packdir" up | |
helptags "${changed[@]}" | |
} | |
update() { | |
local dir=${1?${FUNCNAME[0]}: missing argument}; shift | |
[[ -w $dir ]] || { | |
.cry "Repository not writable: $dir" | |
return 1 | |
} | |
git.is "$dir" sane-and-clean || { | |
.cry "Repository not clean: $dir" | |
return 1 | |
} | |
local here there | |
here=$(git -C "$dir" rev-parse '@') | |
git -C "$dir" fetch -q || { | |
.cry "Error fetching plugin: $dir" | |
return 1 | |
} | |
there=$(git -C "$dir" rev-parse '@{u}') | |
[[ $here != "$there" ]] || { | |
.say '✗' | |
return 1 | |
} | |
git -C "$dir" merge -q || { | |
.cry "Error updating plugin: $dir" | |
return 1 | |
} | |
.say '✓' | |
return 0 | |
} | |
helptags() { | |
local -a docs=() | |
local dir | |
for dir; do | |
local doc=$dir/doc | |
[[ -d $doc ]] || continue | |
[[ -w $doc ]] || { | |
.cry "Directory not writable to generate helptags: $doc" | |
continue | |
} | |
docs+=("$doc") | |
done | |
[[ ${#docs[@]} -gt 0 ]] || return 0 | |
nvim -u NONE --cmd "helptags ${docs[*]}" --cmd q || cry "Error updating helptags" | |
} | |
declare -Ag _dispatch_=( | |
['update']=':update' | |
) | |
declare -Ag _document_=( | |
[':update']='Update plugins' | |
) | |
main() { | |
.dispatch "$@" | |
} | |
main "$@" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment