Permalink
Find file
Fetching contributors…
Cannot retrieve contributors at this time
executable file 351 lines (304 sloc) 9.54 KB
# vim: set filetype=sh :
## script helpers
# exit script with given stderr msg and (optionally) statuscode
exit_error() {
local errcode=${2:-$?}
echo "$1" >&2
exit "$errcode"
}; export -f exit_error
# check if first argument appears among the rest
# for use with arrays, like: in_array "$var" "${arr[@]}"
in_array() {
local needle=${1:?Missing needle}; shift
for arg; do
[[ $arg = $needle ]] && return
done; return 1
}; export -f in_array
# test for glob existance in current dir (use with literals, like: exists '*')
glob_exists() (
shopt -s nullglob dotglob; shopt -u failglob
a=($1); [[ ${a##*/} != . && ${a##*/} != .. && -e $a ]]
); export -f glob_exists
# reverse find, looks for <target> in specified dir or current then on parents up until /
# outputs location on stdin, returns 1 when not found
# usage: rfind <target> [<start>]
rfind() {
local target=${1:?Missing target} cwd=${2:-$PWD}
while true; do
if [[ -e "$cwd"/"$target" ]]; then
echo "$cwd"/"$target"
return 0
fi
if [[ "$cwd" ]]; then
cwd="${cwd%/*}"
else
break
fi
done
return 1
}; export -f rfind
# given a separator, a string and a variable name, creates an array with the parts of the string
# example:
# $ str_split / a/b/c parts
# equivalent to: parts=(a b c)
str_split() {
local OLDIFS=$IFS
IFS=${1:?"Missing separator"}
local srcval=${2:?"Missing value to split"}
local dstvar=${3:?"Missing destination array name"}
eval "$dstvar=(\$srcval)" # hail satan!
IFS=$OLDIFS
}; export -f str_split
# given a separator and a list of items echoes a string joined
# example:
# $ list_join , foo bar
# outputs on stdout: foo,bar
list_join() {
local OLDIFS=$IFS
IFS=${1:?"Missing separator"}
echo "${*:2}"
IFS=$OLDIFS
}; export -f list_join
# shortcuts (newline-join, colon-join)
nl_join() {
list_join $'\n' "$@"
}; export -f nl_join
cl_join() {
list_join : "$@"
}; export -f cl_join
# non-blocking unique filter
filter_uniq() {
awk '!a[$0]++' "$@"
}; export -f filter_uniq
## directory navigation helpers
# make and chdir
mcd() {
mkdir -p "$1" && cd "$1"
}; export -f mcd
# rm and chdir
rcd() {
rmdir "$PWD" && cd ..
}; export -f rcd
# in-place file redirection (usage: with <I/O-file> <cmd> <args>...)
with() {
local input=${1?:Missing input file} output; shift
if [[ -r $input ]]; then
echo "Cannot read from input file" >&2
return 1
fi
output=$(mktemp)
"$@" <"$input" >"$output" && mv "$output" "$input"; st=$?
if (($st)); then
echo "Command failed, partial output: $output" >&2
return $st
fi
}; export -f with
# run commands inside dir without moving (usage: within <dir> <cmd> <args>...)
within() (
local cwd=$PWD
cd "$1" && shift && "$@"; st=$?
cd "$cwd" && return "$st"
); export -f within
## interactive-mode helpers
# run command until exit status is zero (usage: retry [<delay>] <cmd> <args>...)
retry() {
local delay=1 n
if ! [[ $1 = *[^0-9]* ]]; then
#TODO allow delay=0 (prevents Ctrl-C)
if (($1 > 0)); then
delay=$1
fi
shift
fi
# run command
while ! "$@"; do
echo "retrying in ${delay}s" >&2
for ((n=delay; n>0; n--)); do
sleep 1 || return
done
done
}; export -f retry
# run lines of commands from stdin prepending prefix from args while they return true
# example:
# $ chain git <<EOF
# > checkout master
# > pull
# > checkout -
# > merge master
# > EOF
# runs: git checkout master && git pull && git checkout - && git merge master
chain() {
local prefix=("$@")
local cmd raw_cmd raw_cmds=()
# you're not allowed to do this... but I do.
while read -r raw_cmd; do
raw_cmds+=("$raw_cmd")
done && for raw_cmd in "${raw_cmds[@]}"; do
eval "cmd=($raw_cmd)" && "${prefix[@]}" "${cmd[@]}" || break # cookies...
done
}; export -f chain
## misc
# creates small test environments for scripting
mklab() {
local d=$(mktemp -d) && cd "$d" || return 1
clear; bash --login --noprofile --norc
cd - && rm -r "$d"
}; export -f mklab
# helper to show background tasks on prompt
jobs_prompt() {
local a running_jobs stopped_jobs
a=($(jobs -pr)); [[ $a ]] && running_jobs=${#a[@]}
a=($(jobs -ps)); [[ $a ]] && stopped_jobs=${#a[@]}
if [[ $running_jobs || $stopped_jobs ]]; then
if [[ $running_jobs && $stopped_jobs ]]; then a=+; else a=; fi
echo "(${running_jobs:+${running_jobs}r}$a${stopped_jobs:+${stopped_jobs}s})& "
fi
}; export -f jobs_prompt
## command helpers
# open vim's help directly
hvim() {
local h=$1; shift
vim -c ":h $h | bd1 | normal zt" "$@"
}; export -f hvim
# creates a vim session based on git's branch
svim() {
local parent session branch a target
if [[ $1 = git ]]; then
shift
branch=$(git rev-parse --abbrev-ref HEAD)
fi
parent=${PWD%/*}
session=${PWD##*/}_${parent##*/}${branch:+:$branch}
if [[ $1 = ++ ]]; then
shift
#TODO allow to run in a different dir other than .
target=.
a=(); while IFS= read -r -d '' file; do
a+=("$file")
done < <(find "$target" -type f \( -empty -o -exec fgrep -qI '' {} \; \) -print0 | sort -z)
vim -c "chdir $target | $(printf 'SessionSaveAs %q' "$session")" "$@" -- "${a[@]}"
elif [[ $1 = + ]]; then
shift
vim -c "$(printf 'SessionSaveAs %q' "$session")" "$@"
else
vim -c "$(printf 'SessionOpen %q' "$session")" "$@"
fi
}; export -f svim
# stupid wrapper for stupid debian-like systems
# adds `apt` command that connects to the appropiate apt-* variant
if hash apt-get >/dev/null 2>&1; then
apt() {
local sed_expr='/Commands:/,/^$/{//d;s/^ *\([^ ]\+\).*/\1/p;}'
local get_cmds=($(apt-get | sed -n "$sed_expr")) cache_cmds=($(apt-cache | sed -n "$sed_expr"))
local cmd=$1; shift
for get_cmd in "${get_cmds[@]}"; do
if [[ $cmd = $get_cmd ]]; then
sudo apt-get "$cmd" "$@"; return
fi
done
for cache_cmd in "${cache_cmds[@]}"; do
if [[ $cmd = $cache_cmd ]]; then
apt-cache "$cmd" "$@"; return
fi
done
echo "Unknown command: $cmd"
echo
echo "Commands (apt-get):"
printf " %s\n" "${get_cmds[@]}"
echo
echo "Commands (apt-cache):"
printf " %s\n" "${cache_cmds[@]}"
return 1
}; export -f apt
fi
# wget-download entire page and all necessary assets
wget_fullpage() {
wget --page-requisites --span-hosts --convert-links "$@"
}; export -f wget_fullpage
wget_fullpage_no_robots() {
wget_fullpage -e robots=off "$@"
}; export -f wget_fullpage_no_robots
# wget-download and backup all navigable links from and below target
wget_mirror() {
wget --timestamping --recursive --level=inf --no-parent --page-requisites --convert-links --backup-converted "$@"
}; export -f wget_mirror
wget_mirror_no_robots() {
wget_mirror -e robots=off "$@"
}; export -f wget_mirror_no_robots
# similar to `chain git` but adds interactive repl
git_repl() {
while IFS= read -rep "git$(__git_ps1)> " line; do
if [[ $line = \!* ]]; then
line=${line#\!}
elif [[ $line != git\ * ]]; then
line="git $line"
fi
eval "$line"; history -s "$line";
done
}; export -f git_repl
# intelligent version of `git stash` that converts numbers to "stash@{<number>}" strings
stash() {
local i args=()
for arg do
if [[ $arg != *[!0-9]* ]]; then
args+=("stash@{$arg}")
else
args+=("$arg")
fi
done
git stash "${args[@]}"
}; export -f stash
# lookup branch based on prefix (uses / as hierarchy)
# outputs matching branch on stdin
# returns 1 and output original when not found
lookup_branch_prefix() {
local target=${1:?Missing target prefix} ref refparts n
if command git rev-parse -q --verify "$target" 2>/dev/null; then
echo "$target"
return
fi
while read ref; do
ref=${ref#\* } ref=${ref%% *} ref=${ref#remotes/}
str_split / "$ref" refparts
for ((n=${#refparts[@]}; --n>=0;)); do
if [[ $(list_join / "${refparts[@]:$n}") == $target* ]]; then
echo "$(list_join / "${refparts[@]}")"
return
fi
done
done < <(command git branch -a)
echo "$target"
return 1
}; export -f lookup_branch_prefix
# make `git` recognize b:<prefix> branch notations
git() {
local args=("$@") n
for ((n=0; n<$#; n++)); do
#TODO support all notations specified by `man gitrevisions`
if [[ ${args[$n]} == b:* ]]; then
if branch=$(lookup_branch_prefix "${args[$n]:2}"); then
args[$n]=$branch
else
echo "WARN: Unknown branch: ${args[$n]}" >&2
fi
fi
done
command git "${args[@]}"
}; export -f git
# improved version of `gi` from gitignore.io (see: <https://github.com/joeblau/gitignore.io/issues/20>)
stdout_gi() (
gi_args=(); for arg; do
if [[ $arg = -- ]]; then
wget_args=("${gi_args[@]}")
gi_args=()
else
gi_args+=("$arg")
fi
done
IFS=,; wget -O- "${wget_args[@]}" http://gitignore.io/api/"${gi_args[*]}"
); export -f stdout_gi
# have the default command automatically update current .gitignore
gi() {
stdout_gi "$@" > .gitignore
}; export -f gi
[[ -e ~/.functionrc_local ]] && . ~/.functionrc_local