1169 lines
37 KiB
Plaintext
1169 lines
37 KiB
Plaintext
|
#! /usr/bin/env bash
|
||
|
# ___________________________________________________________________________ #
|
||
|
# #
|
||
|
# BashLIB -- A library for Bash scripting convenience. #
|
||
|
# #
|
||
|
# #
|
||
|
# Licensed under the Apache License, Version 2.0 (the "License"); #
|
||
|
# you may not use this file except in compliance with the License. #
|
||
|
# You may obtain a copy of the License at #
|
||
|
# #
|
||
|
# http://www.apache.org/licenses/LICENSE-2.0 #
|
||
|
# #
|
||
|
# Unless required by applicable law or agreed to in writing, software #
|
||
|
# distributed under the License is distributed on an "AS IS" BASIS, #
|
||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
|
||
|
# See the License for the specific language governing permissions and #
|
||
|
# limitations under the License. #
|
||
|
# ___________________________________________________________________________ #
|
||
|
# #
|
||
|
# #
|
||
|
# Copyright 2007-2010, lhunath #
|
||
|
# * http://www.lhunath.com #
|
||
|
# * Maarten Billemont #
|
||
|
# #
|
||
|
|
||
|
|
||
|
|
||
|
# ______________________________________________________________________
|
||
|
# | |
|
||
|
# | .:: GLOBAL CONFIGURATION ::. |
|
||
|
# |______________________________________________________________________|
|
||
|
|
||
|
{
|
||
|
shopt -s extglob
|
||
|
shopt -s globstar
|
||
|
} 2>/dev/null ||:
|
||
|
|
||
|
# Unset all exported functions. Exported functions are evil.
|
||
|
while read _ _ func; do
|
||
|
unset -f "$func"
|
||
|
done < <(declare -Fx)
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
# ______________________________________________________________________
|
||
|
# | |
|
||
|
# | .:: GLOBAL DECLARATIONS ::. |
|
||
|
# |______________________________________________________________________|
|
||
|
|
||
|
# Variables for global internal operation.
|
||
|
bobber=( '.' 'o' 'O' 'o' )
|
||
|
spinner=( '-' \\ '|' '/' )
|
||
|
crosser=( '+' 'x' '+' 'x' )
|
||
|
runner=( '> >' \
|
||
|
'>> ' \
|
||
|
'>>>' \
|
||
|
' >>' )
|
||
|
|
||
|
# Variables for terminal requests.
|
||
|
[[ -t 1 ]] && {
|
||
|
hide=$( tput civis || tput vi )
|
||
|
show=$( tput cvvis || tput vs )
|
||
|
save=$( tput sc )
|
||
|
load=$( tput rc )
|
||
|
bold=$( tput bold || tput md )
|
||
|
reset=$( tput sgr0 || tput me )
|
||
|
#blink=$( tput blink || tput mb )
|
||
|
italic=$( tput sitm || tput ZH )
|
||
|
[[ $TERM != *-m ]] && {
|
||
|
red=$( tput setaf 1|| tput AF 1 )
|
||
|
green=$( tput setaf 2|| tput AF 2 )
|
||
|
yellow=$( tput setaf 3|| tput AF 3 )
|
||
|
blue=$( tput setaf 4|| tput AF 4 )
|
||
|
magenta=$( tput setaf 5|| tput AF 5 )
|
||
|
cyan=$( tput setaf 6|| tput AF 6 )
|
||
|
}
|
||
|
white=$( tput setaf 7|| tput AF 7 )
|
||
|
default=$( tput op )
|
||
|
eed=$( tput ed || tput cd ) # Erase to end of display
|
||
|
eel=$( tput el || tput ce ) # Erase to end of line
|
||
|
ebl=$( tput el1 || tput cb ) # Erase to beginning of line
|
||
|
ewl=$eel$ebl # Erase whole line
|
||
|
draw=$( tput -S <<< ' enacs
|
||
|
smacs
|
||
|
acsc
|
||
|
rmacs' || { \
|
||
|
tput eA; tput as;
|
||
|
tput ac; tput ae; } ) # Drawing characters
|
||
|
back=$'\b'
|
||
|
} 2>/dev/null ||:
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
# ______________________________________________________________________
|
||
|
# | |
|
||
|
# | .:: FUNCTION DECLARATIONS ::. |
|
||
|
# |______________________________________________________________________|
|
||
|
|
||
|
|
||
|
|
||
|
# ______________________________________________________________________
|
||
|
# |__ Chr _______________________________________________________________|
|
||
|
#
|
||
|
# chr decimal
|
||
|
#
|
||
|
# Outputs the character that has the given decimal ASCII value.
|
||
|
#
|
||
|
chr() {
|
||
|
printf \\"$(printf '%03o' "$1")"
|
||
|
} # _____________________________________________________________________
|
||
|
|
||
|
|
||
|
|
||
|
# ______________________________________________________________________
|
||
|
# |__ Ord _______________________________________________________________|
|
||
|
#
|
||
|
# ord character
|
||
|
#
|
||
|
# Outputs the decimal ASCII value of the given character.
|
||
|
#
|
||
|
ord() {
|
||
|
printf %d "'$1"
|
||
|
} # _____________________________________________________________________
|
||
|
|
||
|
|
||
|
|
||
|
# ______________________________________________________________________
|
||
|
# |__ Hex _______________________________________________________________|
|
||
|
#
|
||
|
# hex character
|
||
|
#
|
||
|
# Outputs the hexadecimal ASCII value of the given character.
|
||
|
#
|
||
|
hex() {
|
||
|
printf '%x' "'$1"
|
||
|
} # _____________________________________________________________________
|
||
|
|
||
|
|
||
|
|
||
|
# ______________________________________________________________________
|
||
|
# |__ Unhex _______________________________________________________________|
|
||
|
#
|
||
|
# unhex character
|
||
|
#
|
||
|
# Outputs the character that has the given decimal ASCII value.
|
||
|
#
|
||
|
unhex() {
|
||
|
printf \\x"$1"
|
||
|
} # _____________________________________________________________________
|
||
|
|
||
|
|
||
|
|
||
|
# ______________________________________________________________________
|
||
|
# |__ Exists ____________________________________________________________|
|
||
|
#
|
||
|
# exists application
|
||
|
#
|
||
|
# Returns successfully if the application is in PATH and is executable
|
||
|
# by the current user.
|
||
|
#
|
||
|
exists() {
|
||
|
[[ -x $(type -P "$1" 2>/dev/null) ]]
|
||
|
} # _____________________________________________________________________
|
||
|
|
||
|
|
||
|
|
||
|
# ______________________________________________________________________
|
||
|
# |__ Eol _______________________________________________________________|
|
||
|
#
|
||
|
# eol message
|
||
|
#
|
||
|
# Return termination punctuation for a message, if necessary.
|
||
|
#
|
||
|
eol() {
|
||
|
: #[[ $1 && $1 != *[\!\?.,:\;\|] ]] && printf .. ||:
|
||
|
} # _____________________________________________________________________
|
||
|
|
||
|
|
||
|
|
||
|
# ______________________________________________________________________
|
||
|
# |__ CLoc ______________________________________________________________|
|
||
|
#
|
||
|
# cloc
|
||
|
#
|
||
|
# Outputs the current cursor location as two space-separated numbers: row column
|
||
|
#
|
||
|
cloc() {
|
||
|
local old=$(stty -g)
|
||
|
stty raw
|
||
|
|
||
|
# If the tty has input waiting then we can't read back its response. We'd only break and pollute the tty input buffer.
|
||
|
read -t 0 < /dev/tty 2>/dev/null && {
|
||
|
stty "$old"
|
||
|
return 1
|
||
|
}
|
||
|
|
||
|
printf '\e[6n' > /dev/tty
|
||
|
IFS='[;' read -dR _ row col < /dev/tty
|
||
|
printf '%d %d' "$row" "$col"
|
||
|
stty "$old"
|
||
|
} # _____________________________________________________________________
|
||
|
|
||
|
|
||
|
|
||
|
# ______________________________________________________________________
|
||
|
# |__ Latest ____________________________________________________________|
|
||
|
#
|
||
|
# latest [file...]
|
||
|
#
|
||
|
# Output the argument that represents the file with the latest modification time.
|
||
|
#
|
||
|
latest() (
|
||
|
shopt -s nullglob
|
||
|
local file latest=$1
|
||
|
for file; do
|
||
|
[[ $file -nt $latest ]] && latest=$file
|
||
|
done
|
||
|
printf '%s\n' "$latest"
|
||
|
)
|
||
|
|
||
|
|
||
|
# ______________________________________________________________________
|
||
|
# |__ Emit ______________________________________________________________|
|
||
|
#
|
||
|
# emit [options] message... [-- [command args...]]
|
||
|
#
|
||
|
# Display a message with contextual coloring.
|
||
|
#
|
||
|
# When a command is provided, a spinner will be activated in front of the
|
||
|
# message for as long as the command runs. When the command ends, its
|
||
|
# exit status will result in a message 'done' or 'failed' to be displayed.
|
||
|
#
|
||
|
# It is possible to only specify -- as final argument. This will prepare
|
||
|
# a spinner for you with the given message but leave it up to you to
|
||
|
# notify the spinner that it needs to stop. See the documentation for
|
||
|
# 'spinner' to learn how to do this.
|
||
|
#
|
||
|
# -n Do not end the line with a newline.
|
||
|
# -b Activate bright (bold) mode.
|
||
|
# -d Activate half-bright (dim) mode.
|
||
|
# -g Display in green.
|
||
|
# -y Display in yellow.
|
||
|
# -r Display in red.
|
||
|
# -w Display in the default color.
|
||
|
#
|
||
|
# -[code] A proxy-call to 'spinner -[code]'.
|
||
|
#
|
||
|
# Non-captialized versions of these options affect the * or the spinner
|
||
|
# in front of the message. Capitalized options affect the message text
|
||
|
# displayed.
|
||
|
#
|
||
|
emit() {
|
||
|
|
||
|
# Proxy call to spinner.
|
||
|
[[ $# -eq 1 && $1 = -+([0-9]) ]] \
|
||
|
&& { spinner $1; return; }
|
||
|
|
||
|
# Initialize the vars.
|
||
|
local arg
|
||
|
local style=
|
||
|
local color=
|
||
|
local textstyle=
|
||
|
local textcolor=
|
||
|
local noeol=0
|
||
|
local cmd=0
|
||
|
|
||
|
# Parse the options.
|
||
|
spinArgs=()
|
||
|
for arg in $(getArgs odbwgyrDBWGYRn "$@"); do
|
||
|
case ${arg%% } in
|
||
|
d) style=$dim ;;
|
||
|
b) style=$bold ;;
|
||
|
w) color=$white ;;
|
||
|
g) color=$green ;;
|
||
|
y) color=$yellow ;;
|
||
|
r) color=$red ;;
|
||
|
D) textstyle=$dim ;;
|
||
|
B) textstyle=$bold ;;
|
||
|
W) textcolor=$white ;;
|
||
|
G) textcolor=$green ;;
|
||
|
Y) textcolor=$yellow ;;
|
||
|
R) textcolor=$red ;;
|
||
|
n) noeol=1
|
||
|
spinArgs+=(-n) ;;
|
||
|
o) spinArgs+=("-$arg") ;;
|
||
|
esac
|
||
|
done
|
||
|
shift $(getArgs -c odbwgyrDBWGYRn "$@")
|
||
|
while [[ $1 = +* ]]; do
|
||
|
spinArgs+=("-${1#+}")
|
||
|
shift
|
||
|
done
|
||
|
|
||
|
# Defaults.
|
||
|
color=${color:-$textcolor}
|
||
|
color=${color:-$green}
|
||
|
[[ $color = $textcolor && -z $style ]] && style=$bold
|
||
|
|
||
|
# Get the text message.
|
||
|
local text= origtext=
|
||
|
for arg; do [[ $arg = -- ]] && break; origtext+="$arg "; done
|
||
|
origtext=${origtext%% }
|
||
|
(( noeol )) && text=$origtext || text=$origtext$reset$(eol "$origtext")$'\n'
|
||
|
|
||
|
|
||
|
# Trim off everything up to --
|
||
|
while [[ $# -gt 1 && $1 != -- ]]; do shift; done
|
||
|
[[ $1 = -- ]] && { shift; cmd=1; }
|
||
|
|
||
|
# Figure out what FD to use for our messages.
|
||
|
[[ -t 1 ]]; local fd=$(( $? + 1 ))
|
||
|
|
||
|
# Display the message or spinner.
|
||
|
if (( cmd )); then
|
||
|
# Don't let this Bash handle SIGINT.
|
||
|
#trap : INT
|
||
|
|
||
|
# Create the spinner in the background.
|
||
|
spinPipe=${TMPDIR:-/tmp}/bashlib.$$
|
||
|
{ touch "$spinPipe" && rm -f "$spinPipe" && mkfifo "$spinPipe"; } 2>/dev/null \
|
||
|
|| unset spinPipe
|
||
|
{ spinner "${spinArgs[@]}" "$origtext" -- "$style" "$color" "$textstyle" "$textcolor" < "${spinPipe:-/dev/null}" & } 2>/dev/null
|
||
|
[[ $spinPipe ]] && echo > "$spinPipe"
|
||
|
spinPid=$!
|
||
|
|
||
|
# Execute the command for the spinner if one is given.
|
||
|
#sleep 1 # Let the spinner initialize itself properly first. # Can probably remove this now that we echo > spinPipe?
|
||
|
if (( $# == 1 )); then command=$1
|
||
|
elif (( $# > 1 )); then command=$(printf '%q ' "$@")
|
||
|
else return 0; fi
|
||
|
|
||
|
eval "$command" >/dev/null \
|
||
|
&& spinner -0 \
|
||
|
|| spinner -1
|
||
|
else
|
||
|
# Make reset codes restore the initial font.
|
||
|
local font=$reset$textstyle$textcolor
|
||
|
text=$font${text//$reset/$font}
|
||
|
|
||
|
printf "\r$reset $style$color* %s$reset" "$text" >&$fd
|
||
|
fi
|
||
|
} # _____________________________________________________________________
|
||
|
|
||
|
|
||
|
|
||
|
# ______________________________________________________________________
|
||
|
# |__ Spinner ___________________________________________________________|
|
||
|
#
|
||
|
# spinner message... [-- style color textstyle textcolor]
|
||
|
# or
|
||
|
# spinner -[code]
|
||
|
#
|
||
|
# Displays a spinner on the screen that waits until a certain time.
|
||
|
# Best used through its interface provided by 'emit'.
|
||
|
#
|
||
|
# style A terminal control string that defines the style of the spinner.
|
||
|
# color A terminal control string that defines the color of the spinner.
|
||
|
# textstyle A terminal control string that defines the style of the message.
|
||
|
# textcolor A terminal control string that defines the color of the message.
|
||
|
#
|
||
|
# -[code] Shut down a previously activated spinner with the given exit
|
||
|
# code. If the exit code is 0, a green message 'done' will be
|
||
|
# displayed. Otherwise a red message 'failed' will appear.
|
||
|
# The function will return with this exit code as result.
|
||
|
#
|
||
|
# You can manually specify a previously started spinner by putting its PID in
|
||
|
# the 'spinPid' variable. If this variable is not defined, the PID of the most
|
||
|
# recently backgrounded process is used. The 'spinPid' variable is unset upon
|
||
|
# each call to 'spinner' and reset to the PID of the spinner if one is created.
|
||
|
#
|
||
|
spinner() {
|
||
|
|
||
|
# Check usage.
|
||
|
(( ! $# )) || getArgs -q :h "$@" && {
|
||
|
emit -y 'Please specify a message as argument or a status option.'
|
||
|
return 1
|
||
|
}
|
||
|
|
||
|
# Initialize spinner vars.
|
||
|
# Make sure monitor mode is off or we won't be able to trap INT properly.
|
||
|
local monitor=0; [[ $- = *m* ]] && monitor=1
|
||
|
local done=
|
||
|
|
||
|
# Place the trap for interrupt signals.
|
||
|
trap 'done="${red}failed"' USR2
|
||
|
trap 'done="${green}done"' USR1
|
||
|
|
||
|
# Initialize the vars.
|
||
|
local pid=${spinPid:-$!}
|
||
|
local graphics=( "${bobber[@]}" )
|
||
|
local style=$bold
|
||
|
local color=$green
|
||
|
local textstyle=
|
||
|
local textcolor=
|
||
|
local output=
|
||
|
local noeol=
|
||
|
unset spinPid
|
||
|
|
||
|
# Any remaining options are the exit status of an existing spinner or spinner type.
|
||
|
while [[ $1 = -* ]]; do
|
||
|
arg=${1#-}
|
||
|
shift
|
||
|
|
||
|
# Stop parsing when arg is --
|
||
|
[[ $arg = - ]] && break
|
||
|
|
||
|
# Process arg: Either a spinner type or result code.
|
||
|
if [[ $arg = *[^0-9]* ]]; then
|
||
|
case $arg in
|
||
|
b) graphics=( "${bobber[@]}" ) ;;
|
||
|
c) graphics=( "${crosser[@]}" ) ;;
|
||
|
r) graphics=( "${runner[@]}" ) ;;
|
||
|
s) graphics=( "${spinner[@]}" ) ;;
|
||
|
o) output=1 ;;
|
||
|
n) noeol=1 ;;
|
||
|
esac
|
||
|
elif [[ $pid ]]; then
|
||
|
[[ $arg = 0 ]] \
|
||
|
&& kill -USR1 $pid 2>/dev/null \
|
||
|
|| kill -USR2 $pid 2>/dev/null
|
||
|
|
||
|
trap - INT
|
||
|
wait $pid 2>/dev/null
|
||
|
|
||
|
return $arg
|
||
|
fi
|
||
|
done
|
||
|
|
||
|
# Read arguments.
|
||
|
local text= origtext=
|
||
|
for arg; do [[ $arg = -- ]] && break; origtext+="$arg "; done
|
||
|
origtext=${origtext% }
|
||
|
local styles=$*; [[ $styles = *' -- '* ]] || styles=
|
||
|
read -a styles <<< "${styles##* -- }"
|
||
|
[[ ${styles[0]} ]] && style=${styles[0]}
|
||
|
[[ ${styles[1]} ]] && color=${styles[1]}
|
||
|
[[ ${styles[2]} ]] && textstyle=${styles[2]}
|
||
|
[[ ${styles[3]} ]] && textcolor=${styles[3]}
|
||
|
|
||
|
# Figure out what FD to use for our messages.
|
||
|
[[ -t 1 ]]; local fd=$(( $? + 1 ))
|
||
|
|
||
|
# Make reset codes restore the initial font.
|
||
|
local font=$reset$textstyle$textcolor
|
||
|
origtext=$font${origtext//$reset/$font}
|
||
|
(( noeol )) && text=$origtext || text=$origtext$reset$(eol "$origtext")
|
||
|
|
||
|
# Spinner initial status.
|
||
|
printf "\r$save$eel$reset $style$color* %s$reset" "$text" >&$fd
|
||
|
(( output )) && printf "\n" >&$fd
|
||
|
|
||
|
# Render the spinner.
|
||
|
set +m
|
||
|
local i=0
|
||
|
while [[ ! $done ]]; do
|
||
|
IFS= read -r -d '' newtext || true
|
||
|
newtext=${newtext%%$'\n'}; newtext=${newtext##*$'\n'}
|
||
|
if [[ $newtext = +* ]]; then
|
||
|
newtext="$origtext [${newtext#+}]"
|
||
|
fi
|
||
|
if [[ $newtext ]]; then
|
||
|
newtext="$font${newtext//$reset/$font}"
|
||
|
(( noeol )) && text=$newtext || text=$newtext$reset$(eol "$newtext")
|
||
|
fi
|
||
|
|
||
|
if (( output ))
|
||
|
then printf "\r" >&$fd
|
||
|
else printf "$load$eel" >&$fd
|
||
|
fi
|
||
|
|
||
|
if (( output ))
|
||
|
then printf "$reset $style$color$blue%s %s$reset" \
|
||
|
"${graphics[i++ % 4]}" "$text" >&$fd
|
||
|
else printf "$reset $style$color%s %s$reset" \
|
||
|
"${graphics[i++ % 4]}" "$text" >&$fd
|
||
|
fi
|
||
|
|
||
|
sleep .25 # Four iterations make one second.
|
||
|
|
||
|
# Cancel when calling script disappears.
|
||
|
kill -0 $$ >/dev/null || done="${red}aborted"
|
||
|
done
|
||
|
|
||
|
# Get rid of the spinner traps.
|
||
|
trap - USR1 USR2; (( monitor )) && set -m
|
||
|
|
||
|
# Spinner final status.
|
||
|
if (( output ))
|
||
|
then text=; printf "\r" >&$fd
|
||
|
else printf "$load" >&$fd
|
||
|
fi
|
||
|
|
||
|
printf "$eel$reset $style$color* %s${text:+ }$bold%s$font.$reset\n" \
|
||
|
"$text" "$done" >&$fd
|
||
|
} # _____________________________________________________________________
|
||
|
|
||
|
|
||
|
|
||
|
# ______________________________________________________________________
|
||
|
# |__ report ___________________________________________________________|
|
||
|
#
|
||
|
# report [-code] [-e] failure-message [success-message]
|
||
|
#
|
||
|
# This is a convenience function for replacement of spinner -code.
|
||
|
#
|
||
|
# It checks either the exit code of the previously completed command or
|
||
|
# the code provided as option to determine whether to display the success
|
||
|
# or failure message. It calls spinner -code to complete an actively
|
||
|
# emitted message if there is one. The success message is optional.
|
||
|
#
|
||
|
# -[code] The exit code to use.
|
||
|
# -e Exit the script on failure.
|
||
|
#
|
||
|
report() {
|
||
|
|
||
|
# Exit Status of previous command.
|
||
|
local code=$?
|
||
|
|
||
|
# Parse the options.
|
||
|
while [[ $1 = -* && $2 ]]; do
|
||
|
arg=${1#-}
|
||
|
shift
|
||
|
|
||
|
# Stop parsing when arg is --
|
||
|
[[ $arg = - ]] && break
|
||
|
|
||
|
# Process arg: Either a spinner type or result code.
|
||
|
if [[ $arg = *[^0-9]* ]]; then
|
||
|
case $arg in
|
||
|
esac
|
||
|
else code=$arg
|
||
|
fi
|
||
|
done
|
||
|
|
||
|
# Initialize the vars.
|
||
|
local failure=$1
|
||
|
local success=$2
|
||
|
|
||
|
# Check usage.
|
||
|
(( ! $# )) || getArgs -q :h "$@" && {
|
||
|
emit -y 'Please specify at least a failure message as argument.'
|
||
|
return 1
|
||
|
}
|
||
|
|
||
|
# Proxy call to spinner.
|
||
|
(( spinPid )) \
|
||
|
&& { spinner -$code; }
|
||
|
|
||
|
# Success or failure message.
|
||
|
if (( ! code ))
|
||
|
then [[ $success ]] && emit " $success"
|
||
|
else [[ $failure ]] && emit -R " $failure"
|
||
|
fi
|
||
|
|
||
|
# Pass on exit code.
|
||
|
return $code
|
||
|
} # _____________________________________________________________________
|
||
|
|
||
|
|
||
|
|
||
|
# ______________________________________________________________________
|
||
|
# |__ Ask _______________________________________________________________|
|
||
|
#
|
||
|
# ask [-optionchars|+default] message...
|
||
|
#
|
||
|
# Ask a question and read the user's reply to it.
|
||
|
#
|
||
|
# By default, a reply is terminated by a newline.
|
||
|
#
|
||
|
# You may use the options to switch into key mode. In key mode, only a
|
||
|
# single character is read. The valid characters are specified in the
|
||
|
# optionchars. A capital option character makes that option the default.
|
||
|
#
|
||
|
# If the reply character in key mode was not amoungst the provided options
|
||
|
# the default is assumed instead. If no default was given, an exit code
|
||
|
# of 2 is returned.
|
||
|
#
|
||
|
# You may mark an optionchar as 'valid' by appending a '!' to it. As a
|
||
|
# result, an exit code of 0 will only be returned if this valid option
|
||
|
# is replied. If not, an exit code of 1 will be returned.
|
||
|
#
|
||
|
# If no option is marked as valid, the given reply is echoed and an exit
|
||
|
# code of 0 is returned.
|
||
|
#
|
||
|
# You can specify the -# option to make ask hide the user's input.
|
||
|
#
|
||
|
# If you prefix the first argument with a + instead of a -, the remaining
|
||
|
# argument is taken as the default string value and returned when no input
|
||
|
# was received. In this case, the exit code is 0 either way.
|
||
|
#
|
||
|
ask() {
|
||
|
|
||
|
# Check usage.
|
||
|
(( ! $# )) || getArgs -q :h "$@" && {
|
||
|
emit -y 'Please specify a question as argument.'
|
||
|
return 1
|
||
|
}
|
||
|
|
||
|
# Initialize the vars.
|
||
|
local arg
|
||
|
local option=
|
||
|
local options=
|
||
|
local default=
|
||
|
local silent=
|
||
|
local valid=
|
||
|
local muteChar=
|
||
|
|
||
|
# Parse the options.
|
||
|
if [[ $1 = +* ]]; then
|
||
|
option=${1#+}
|
||
|
default=$option
|
||
|
|
||
|
shift
|
||
|
else
|
||
|
for arg in $(getArgs "$(printf "%s" {a..z} {A..Z})!#%" "$@"); do
|
||
|
[[ $arg = [[:upper:]] ]] \
|
||
|
&& default=$arg
|
||
|
[[ $arg = ! ]] \
|
||
|
&& { valid=${options: -1}; continue; }
|
||
|
[[ $arg = '#' ]] \
|
||
|
&& { silent=1 arg=; }
|
||
|
[[ $arg = '%' ]] \
|
||
|
&& { silent=1 muteChar='*' arg=; }
|
||
|
|
||
|
options+=$arg
|
||
|
done
|
||
|
fi
|
||
|
|
||
|
# Trim off the options.
|
||
|
while [[ $1 = -* ]]; do shift; done
|
||
|
|
||
|
# Figure out what FD to use for our messages.
|
||
|
[[ -t 1 ]]; local fd=$(( $? + 1 ))
|
||
|
|
||
|
# Ask the question.
|
||
|
emit -yn "$*${option:+ [$option]}${options:+ [$options]} "
|
||
|
|
||
|
# Read the reply.
|
||
|
if [[ $muteChar ]]; then
|
||
|
local reply
|
||
|
while read -s -n1 && [[ $REPLY ]]; do
|
||
|
reply+=$REPLY
|
||
|
printf "%s" "$muteChar" >&$fd
|
||
|
done
|
||
|
REPLY=$reply
|
||
|
else
|
||
|
read ${options:+-n1} ${silent:+-s}
|
||
|
fi
|
||
|
[[ $options && $REPLY ]] || (( silent )) && printf "\n" >&$fd
|
||
|
|
||
|
# Evaluate the reply.
|
||
|
while true; do
|
||
|
if [[ $REPLY && ( ! $options || $options = *$REPLY* ) ]]; then
|
||
|
if [[ $valid ]]
|
||
|
then [[ $REPLY = $valid ]]
|
||
|
else printf "%s" "$REPLY"
|
||
|
fi
|
||
|
|
||
|
return
|
||
|
fi
|
||
|
|
||
|
[[ -z $default || $REPLY = $default ]] \
|
||
|
&& return 2
|
||
|
|
||
|
REPLY=$default
|
||
|
done
|
||
|
} # _____________________________________________________________________
|
||
|
|
||
|
|
||
|
|
||
|
# ______________________________________________________________________
|
||
|
# |__ Trim ______________________________________________________________|
|
||
|
#
|
||
|
# trim lines ...
|
||
|
#
|
||
|
# Trim the whitespace off of the beginning and end of the given lines.
|
||
|
# Each argument is considdered one line; is treated and printed out.
|
||
|
#
|
||
|
# When no arguments are given, lines will be read from standard input.
|
||
|
#
|
||
|
trim() {
|
||
|
|
||
|
# Initialize the vars.
|
||
|
local lines
|
||
|
local line
|
||
|
local oIFS
|
||
|
|
||
|
# Get the lines.
|
||
|
lines=( "$@" )
|
||
|
if (( ! ${#lines[@]} )); then
|
||
|
oIFS=$IFS; IFS=$'\n'
|
||
|
lines=( $(cat) )
|
||
|
IFS=$oIFS
|
||
|
fi
|
||
|
|
||
|
# Trim the lines
|
||
|
for line in "${lines[@]}"; do
|
||
|
line=${line##*([[:space:]])}; line=${line%%*([[:space:]])}
|
||
|
printf "%s" "$line"
|
||
|
done
|
||
|
} # _____________________________________________________________________
|
||
|
|
||
|
|
||
|
|
||
|
# ______________________________________________________________________
|
||
|
# |__ Reverse ___________________________________________________________|
|
||
|
#
|
||
|
# reverse [-0] [elements ...] [<<< elements]
|
||
|
#
|
||
|
# Reverse the order of the given elements. Elements are read from command
|
||
|
# arguments or standard input if no element arguments are given.
|
||
|
# They are reversed and output on standard output.
|
||
|
#
|
||
|
# If the -0 option is given, input and output are delimited by NUL bytes.
|
||
|
# Otherwise, they are delimited by newlines.
|
||
|
#
|
||
|
reverse() {
|
||
|
|
||
|
# Initialize the vars.
|
||
|
local elements delimitor=$'\n'
|
||
|
|
||
|
# Parse the options.
|
||
|
while [[ $1 = -* ]]; do
|
||
|
case $1 in
|
||
|
-0) delimitor=$'\0' ;;
|
||
|
--) shift; break ;;
|
||
|
esac
|
||
|
shift
|
||
|
done
|
||
|
|
||
|
# Get the elements.
|
||
|
elements=( "$@" )
|
||
|
if (( ! ${#elements[@]} )); then
|
||
|
while IFS= read -r -d "$delimitor"; do
|
||
|
elements+=("$REPLY")
|
||
|
done
|
||
|
fi
|
||
|
|
||
|
# Iterate in reverse order.
|
||
|
for (( i=${#elements[@]} - 1; i >=0; --i )); do
|
||
|
printf '%s%s' "${elements[i]}" "$delimitor"
|
||
|
done
|
||
|
} # _____________________________________________________________________
|
||
|
|
||
|
|
||
|
|
||
|
# ______________________________________________________________________
|
||
|
# |__ SWait _____________________________________________________________|
|
||
|
#
|
||
|
# swait pid...
|
||
|
#
|
||
|
# Wait for the given PID(s). The PID does not need to be a child of the
|
||
|
# running shell. Note that relying on PIDs always introduces race conditions
|
||
|
# which may be potentially harmful and sometimes even a security issue.
|
||
|
#
|
||
|
# This implementation requires the necessary permissions to send signals
|
||
|
# to the PID(s) provided.
|
||
|
#
|
||
|
swait() {
|
||
|
|
||
|
# Check usage.
|
||
|
(( ! $# )) || getArgs -q :h "$@" && {
|
||
|
emit -y 'Please provide one or more PIDs to wait for as argument.'
|
||
|
return 1
|
||
|
}
|
||
|
|
||
|
# Time to wait.
|
||
|
local pid
|
||
|
for pid; do
|
||
|
while kill -0 $pid 2>/dev/null
|
||
|
do sleep .1; done
|
||
|
done
|
||
|
} # _____________________________________________________________________
|
||
|
|
||
|
|
||
|
|
||
|
# ______________________________________________________________________
|
||
|
# |__ GetArgs ___________________________________________________________|
|
||
|
#
|
||
|
# getArgs [options] optstring [args...]
|
||
|
#
|
||
|
# Retrieve all options present in the given arguments.
|
||
|
#
|
||
|
# This is a wrapper for getopts(P) which will safely work inside functions.
|
||
|
# It manages OPTIND for you and returns a list of options found in the
|
||
|
# provided arguments.
|
||
|
#
|
||
|
# optstring This is a string of characters in which each character
|
||
|
# represents an option to look for in the arguments.
|
||
|
# See getopts(P) for a description of the optstring syntax.
|
||
|
#
|
||
|
# args This is a list of arguments in which to look for options.
|
||
|
# Most commonly, you will use "$@" to supply these arguments.
|
||
|
#
|
||
|
# -c Instead of output the arguments, output OPTARGS.
|
||
|
# -q Be quiet. No arguments are displayed. Only the exit code is set.
|
||
|
# -n Use newlines as a separator between the options that were found.
|
||
|
# -0 Use NULL-bytes as a separator between the options that were found.
|
||
|
#
|
||
|
# If any given arguments are found, an exit code of 0 is returned. If none
|
||
|
# are found, an exit code of 1 is returned.
|
||
|
#
|
||
|
# After the operation, OPTARGS is set the the index of the last argument
|
||
|
# that has been parsed by getArgs. Ready for you to use shift $OPTARGS.
|
||
|
#
|
||
|
getArgs() {
|
||
|
|
||
|
# Check usage.
|
||
|
(( ! $# )) && {
|
||
|
emit -y 'Please provide the arguments to search for in' \
|
||
|
'getopts(P) format followed by the positional parameters.'
|
||
|
return 1
|
||
|
}
|
||
|
|
||
|
# Initialize the defaults.
|
||
|
local arg
|
||
|
local found=0
|
||
|
local quiet=0
|
||
|
local count=0
|
||
|
local delimitor=' '
|
||
|
|
||
|
# Parse the options.
|
||
|
while [[ $1 = -* ]]; do
|
||
|
case $1 in
|
||
|
-q) quiet=1 ;;
|
||
|
-c) count=1 ;;
|
||
|
-n) delimitor=$'\n' ;;
|
||
|
-0) delimitor=$'\0' ;;
|
||
|
esac
|
||
|
shift
|
||
|
done
|
||
|
|
||
|
# Get the optstring.
|
||
|
local optstring=$1; shift
|
||
|
local oOPTIND=$OPTIND OPTIND=1
|
||
|
|
||
|
# Enumerate the arguments.
|
||
|
while getopts "$optstring" arg; do
|
||
|
[[ $arg != '?' ]] && found=1
|
||
|
|
||
|
(( quiet + count )) || \
|
||
|
printf "%s${OPTARG:+ }%s%s" "$arg" "$OPTARG" "$delimitor"
|
||
|
done
|
||
|
OPTARGS=$(( OPTIND - 1 ))
|
||
|
OPTIND=$oOPTIND
|
||
|
|
||
|
# Any arguments found?
|
||
|
(( count )) && printf "%s" "$OPTARGS"
|
||
|
return $(( ! found ))
|
||
|
} # _____________________________________________________________________
|
||
|
|
||
|
|
||
|
|
||
|
# |__ ShowHelp __________________________________________________________|
|
||
|
#
|
||
|
# showHelp name description author [option description]...
|
||
|
#
|
||
|
# Generate a prettily formatted usage description of the application.
|
||
|
#
|
||
|
# name Provide the name of the application.
|
||
|
#
|
||
|
# description Provide a detailed description of the application's
|
||
|
# purpose and usage.
|
||
|
#
|
||
|
# option An option the application can take as argument.
|
||
|
#
|
||
|
# description A description of the effect of the preceding option.
|
||
|
#
|
||
|
showHelp() {
|
||
|
|
||
|
# Check usage.
|
||
|
(( $# < 3 )) || getArgs -q :h "$@" && {
|
||
|
emit -y 'Please provide the name, description, author and options' \
|
||
|
'of the application.'
|
||
|
return 1
|
||
|
}
|
||
|
|
||
|
# Parse the options.
|
||
|
local appName=$1; shift
|
||
|
local appDesc=${1//+([[:space:]])/ }; shift
|
||
|
local appAuthor=$1; shift
|
||
|
local cols=$(tput cols)
|
||
|
(( cols = ${cols:-80} - 10 ))
|
||
|
|
||
|
# Figure out what FD to use for our messages.
|
||
|
[[ -t 1 ]]; local fd=$(( $? + 1 ))
|
||
|
|
||
|
# Print out the help header.
|
||
|
printf "$reset$bold\n" >&$fd
|
||
|
printf "\t\t%s\n" "$appName" >&$fd
|
||
|
printf "$reset\n" >&$fd
|
||
|
printf "%s\n" "$appDesc" | fmt -w "$cols" | sed $'s/^/\t/' >&$fd
|
||
|
printf "\t $reset$bold~ $reset$bold%s\n" "$appAuthor" >&$fd
|
||
|
printf "$reset\n" >&$fd
|
||
|
|
||
|
# Print out the application options and columnize them.
|
||
|
while (( $# )); do
|
||
|
local optName=$1; shift
|
||
|
local optDesc=$1; shift
|
||
|
printf " %s\t" "$optName"
|
||
|
printf "%s\n" "${optDesc//+( )/ }" | fmt -w "$cols" | sed $'1!s/^/ \t/'
|
||
|
printf "\n"
|
||
|
done | column -t -s $'\t' \
|
||
|
| sed "s/^\( [^ ]*\)/$bold$green\1$reset/" >&$fd
|
||
|
printf "\n" >&$fd
|
||
|
} # _____________________________________________________________________
|
||
|
|
||
|
|
||
|
|
||
|
# ______________________________________________________________________
|
||
|
# |__ Quote _____________________________________________________________|
|
||
|
#
|
||
|
# quote [-e] [argument...]
|
||
|
#
|
||
|
# Output a single string where all arguments are quoted
|
||
|
# such that the string is safe to be passed as shell
|
||
|
# command arguments as though given arguments had been
|
||
|
# passed.
|
||
|
#
|
||
|
# When no arguments are passed; no output is generated.
|
||
|
#
|
||
|
# -e Use backslashes rather than single quotes.
|
||
|
#
|
||
|
quote() {
|
||
|
|
||
|
# Initialize the defaults.
|
||
|
local arg escape=0 quotedArgs=()
|
||
|
|
||
|
# Parse the options.
|
||
|
while [[ $1 = -* ]]; do
|
||
|
case $1 in
|
||
|
-e) escape=1 ;;
|
||
|
--) shift; break ;;
|
||
|
esac
|
||
|
shift
|
||
|
done
|
||
|
|
||
|
# Print out each argument, quoting it properly.
|
||
|
for arg; do
|
||
|
if (( escape )); then
|
||
|
quotedArgs+=("$(printf "%q" "$arg")")
|
||
|
else
|
||
|
quotedArgs+=("$(printf "'%s'" "${arg//"'"/"\\'"}")")
|
||
|
fi
|
||
|
done
|
||
|
|
||
|
printf '%s\n' "$(IFS=' '; echo "${quotedArgs[*]}")"
|
||
|
} # _____________________________________________________________________
|
||
|
|
||
|
|
||
|
|
||
|
# ______________________________________________________________________
|
||
|
# |__ Shorten ___________________________________________________________|
|
||
|
#
|
||
|
# shorten [-p pwd] path [suffix]...
|
||
|
#
|
||
|
# Shorten an absolute path for pretty printing by cutting
|
||
|
# off PWD and replacing HOME by ~.
|
||
|
#
|
||
|
# -p Use the given pathname as the base for relative filenames instead of PWD.
|
||
|
# path The path string to shorten.
|
||
|
# suffix Suffix strings that must be cut off from the end.
|
||
|
# Only the first suffix string matched will be cut off.
|
||
|
#
|
||
|
shorten() {
|
||
|
|
||
|
# Check usage.
|
||
|
(( $# < 1 )) || getArgs -q :h "$@" && {
|
||
|
emit -y 'Please provide the path to shorten.'
|
||
|
return 1
|
||
|
}
|
||
|
|
||
|
# Parse the options.
|
||
|
local suffix path pwd=$PWD
|
||
|
[[ $1 = -p ]] && { pwd=$2; shift 2; }
|
||
|
path=$1; shift
|
||
|
|
||
|
# Make path absolute.
|
||
|
[[ $path = /* ]] || path=$PWD/$path
|
||
|
|
||
|
# If the path denotes something that exists; it's easy.
|
||
|
if [[ -d $path ]]
|
||
|
then path=$(cd "$path"; printf "%s" "$PWD")
|
||
|
elif [[ -d ${path%/*} ]]
|
||
|
then path=$(cd "${path%/*}"; printf "%s" "$PWD/${path##*/}")
|
||
|
|
||
|
# If not, we'll try readlink -m.
|
||
|
elif readlink -m / >/dev/null 2>&1; then
|
||
|
path=$(readlink -m "$path")
|
||
|
|
||
|
# If we don't have that - unleash the sed(1) madness.
|
||
|
else
|
||
|
local oldpath=/
|
||
|
while [[ $oldpath != $path ]]; do
|
||
|
oldpath=$path
|
||
|
path=$(sed -e 's,///*,/,g' -e 's,\(^\|/\)\./,\1,g' -e 's,\(^\|/\)[^/]*/\.\.\($\|/\),\1,g' <<< "$path")
|
||
|
done
|
||
|
fi
|
||
|
|
||
|
# Replace special paths.
|
||
|
path=${path#$pwd/}
|
||
|
path=${path/#$HOME/'~'}
|
||
|
|
||
|
# Cut off suffix.
|
||
|
for suffix; do
|
||
|
[[ $path = *$suffix ]] && {
|
||
|
path=${path%$suffix}
|
||
|
break
|
||
|
}
|
||
|
done
|
||
|
|
||
|
printf "%s" "$path"
|
||
|
} # _____________________________________________________________________
|
||
|
|
||
|
|
||
|
|
||
|
# ______________________________________________________________________
|
||
|
# |__ InArray ___________________________________________________________|
|
||
|
#
|
||
|
# inArray element array
|
||
|
#
|
||
|
# Checks whether a certain element is in the given array.
|
||
|
#
|
||
|
# element The element to search the array for.
|
||
|
# array This is a list of elements to search through.
|
||
|
#
|
||
|
inArray() {
|
||
|
|
||
|
# Check usage.
|
||
|
(( $# < 1 )) || getArgs -q :h "$@" && {
|
||
|
emit -y 'Please provide the element to search for and the array' \
|
||
|
'to search through.'
|
||
|
return 1
|
||
|
}
|
||
|
|
||
|
# Parse the options.
|
||
|
local element
|
||
|
local search=$1; shift
|
||
|
|
||
|
# Perform the search.
|
||
|
for element
|
||
|
do [[ $element = $search ]] && return 0; done
|
||
|
return 1
|
||
|
} # _____________________________________________________________________
|
||
|
|
||
|
|
||
|
|
||
|
# ______________________________________________________________________
|
||
|
# |__ xpathNodes ________________________________________________________|
|
||
|
#
|
||
|
# xpathNodes query [filename]
|
||
|
#
|
||
|
# Outputs every xpath node that matches the query on a separate line.
|
||
|
# Leading and trailing whitespace is always stripped.
|
||
|
#
|
||
|
# filename The path to the file that contains the document to run the query on.
|
||
|
# query The XPath query to run on the document.
|
||
|
#
|
||
|
xpathNodes() {
|
||
|
local query=$1 filename=$2
|
||
|
[[ $filename ]] || filename=<(cat)
|
||
|
|
||
|
{
|
||
|
if xpath -e / <(echo '<a></a>') >/dev/null 2>&1; then
|
||
|
xpath -e "$query" "$filename" 2>&1
|
||
|
else
|
||
|
xpath "$filename" "$query" 2>&1
|
||
|
fi
|
||
|
} | {
|
||
|
read
|
||
|
sed -ne $'s/-- NODE --/\\\n/g' -e 's/^[[:space:]]*\(.*[^[:space:]]\)[[:space:]]*$/\1/p'
|
||
|
}
|
||
|
|
||
|
return "${PIPESTATUS[0]}"
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
# ______________________________________________________________________
|
||
|
# |__ HideDebug _________________________________________________________|
|
||
|
#
|
||
|
# hideDebug [ on | off ]
|
||
|
#
|
||
|
# Toggle Bash's debugging mode off temporarily. To hide Bash's debugging
|
||
|
# output for a function, you should have a hideDebug on as its first line
|
||
|
# and hideDebug off as its last.
|
||
|
#
|
||
|
hideDebug() {
|
||
|
|
||
|
if [[ $1 = on ]]; then
|
||
|
: -- HIDING DEBUG OUTPUT ..
|
||
|
[[ $- != *x* ]]; bashlib_debugWasOn=$?
|
||
|
set +x
|
||
|
elif [[ $1 = off ]]; then
|
||
|
: -- SHOWING DEBUG OUTPUT ..
|
||
|
(( bashlib_debugWasOn )) && \
|
||
|
set -x
|
||
|
fi
|
||
|
}
|
||
|
|
||
|
# ______________________________________________________________________
|
||
|
# |__ StackTrace ________________________________________________________|
|
||
|
#
|
||
|
# stackTrace
|
||
|
#
|
||
|
# Retrieve a mapping of a key from the given map or modify the given map by
|
||
|
# assigning a new value for the given key if stdin is not the terminal.
|
||
|
#
|
||
|
stackTrace() {
|
||
|
|
||
|
# Some general debug information.
|
||
|
printf "\t$bold%s$reset v$bold%s$reset" "$BASH" "$BASH_VERSION\n"
|
||
|
printf " Was running: $bold%s %s$reset" "$BASH_COMMAND" "$*\n"
|
||
|
printf "\n"
|
||
|
printf " [Shell : $bold%15s$reset] [Subshells : $bold%5s$reset]\n" "$SHLVL" "$BASH_SUBSHELL"
|
||
|
printf " [Locale : $bold%15s$reset] [Runtime : $bold%5s$reset]\n" "$LC_ALL" "${SECONDS}s"
|
||
|
printf "\n"
|
||
|
|
||
|
# Search through the map.
|
||
|
local arg=0
|
||
|
for i in ${!FUNCNAME[@]}; do
|
||
|
#if (( i )); then
|
||
|
|
||
|
# Print this execution stack's location.
|
||
|
printf "$reset $bold-$reset $green"
|
||
|
[[ ${BASH_SOURCE[i+1]} ]] \
|
||
|
&& printf "%s$reset:$green$bold%s" "${BASH_SOURCE[i+1]}" "${BASH_LINENO[i]}" \
|
||
|
|| printf "${bold}Prompt"
|
||
|
|
||
|
# Print this execution stack's function and positional parameters.
|
||
|
printf "$reset :\t$bold%s(" "${FUNCNAME[i]}"
|
||
|
[[ ${BASH_ARGC[i]} ]] && \
|
||
|
for (( j = 0; j < ${BASH_ARGC[i]}; j++ )); do
|
||
|
(( j )) && printf ', '
|
||
|
printf "%s" "${BASH_ARGV[arg]}"
|
||
|
let arg++
|
||
|
done
|
||
|
|
||
|
# Print the end of this execution stack's line.
|
||
|
printf ")$reset\n"
|
||
|
#fi
|
||
|
done
|
||
|
printf "\n"
|
||
|
|
||
|
} # _____________________________________________________________________
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
# ______________________________________________________________________
|
||
|
# | |
|
||
|
# | .:: ENTRY POINT ::. |
|
||
|
# |______________________________________________________________________|
|
||
|
|
||
|
# Make sure this file is sourced and not executed.
|
||
|
(( ! BASH_LINENO )) && {
|
||
|
emit -R "You should source this file, not execute it."
|
||
|
exit 1
|
||
|
}
|
||
|
|
||
|
:
|
||
|
: .:: END SOURCING ::.
|
||
|
: ______________________________________________________________________
|
||
|
:
|