- Created submodule lib/composure (links to github.com/erichs/composure)

- Removed "normal" file lib/composure.bash
- Symlinked lib/composure/composure.sh to lib/composure.bash
	- This allows ease of updating composure directly from git.
pull/872/head
Brian Recchia 2016-12-15 17:50:16 -05:00
parent 1fa0f82f61
commit 614b71ecf8
3 changed files with 450 additions and 265 deletions

3
.gitmodules vendored 100644
View File

@ -0,0 +1,3 @@
[submodule "lib/composure"]
path = lib/composure
url = git@github.com:erichs/composure.git

1
lib/composure 160000

@ -0,0 +1 @@
Subproject commit f784e3a59c6f5d57fb6a8a93adb2c88b0799c77b

711
lib/composure.bash 100644 → 100755
View File

@ -1,365 +1,546 @@
#!/bin/bash
# composure - by erichs # composure - by erichs
# light-hearted functions for intuitive shell programming # light-hearted functions for intuitive shell programming
# install: source this script in your ~/.profile or ~/.${SHELL}rc script # version: 1.3.1
# latest source available at http://git.io/composure # latest source available at http://git.io/composure
# install: source this script in your ~/.profile or ~/.${SHELL}rc script
# known to work on bash, zsh, and ksh93 # known to work on bash, zsh, and ksh93
# 'plumbing' functions # 'plumbing' functions
composure_keywords () _bootstrap_composure() {
{ _generate_metadata_functions
echo "about author example group param version" _load_composed_functions
_determine_printf_cmd
} }
letterpress () _get_composure_dir ()
{ {
typeset rightcol="$1" leftcol="${2:- }" if [ -n "$XDG_DATA_HOME" ]; then
echo "$XDG_DATA_HOME/composure"
else
echo "$HOME/.local/composure"
fi
}
if [ -z "$rightcol" ]; then _get_author_name ()
{
typeset name localname
localname="$(git --git-dir "$(_get_composure_dir)/.git" config --get user.name)"
for name in "$GIT_AUTHOR_NAME" "$localname"; do
if [ -n "$name" ]; then
echo "$name"
break
fi
done
}
_composure_keywords ()
{
echo "about author example group param version"
}
_letterpress ()
{
typeset rightcol="$1" leftcol="${2:- }" leftwidth="${3:-20}"
if [ -z "$rightcol" ]; then
return
fi
$_printf_cmd "%-*s%s\n" "$leftwidth" "$leftcol" "$rightcol"
}
_determine_printf_cmd() {
if [ -z "$_printf_cmd" ]; then
_printf_cmd=printf
# prefer GNU gprintf if available
[ -x "$(which gprintf 2>/dev/null)" ] && _printf_cmd=gprintf
export _printf_cmd
fi
}
_longest_function_name_length ()
{
echo "$1" | awk 'BEGIN{ maxlength=0 }
{
for(i=1;i<=NF;i++)
if (length($i)>maxlength)
{
maxlength=length($i)
}
}
END{ print maxlength}'
}
_temp_filename_for ()
{
typeset file=$(mktemp "/tmp/$1.XXXX")
command rm "$file" 2>/dev/null # ensure file is unlinked prior to use
echo "$file"
}
_prompt ()
{
typeset prompt="$1"
typeset result
case "$(_shell)" in
bash)
read -r -e -p "$prompt" result;;
*)
echo -n "$prompt" >&2; read -r result;;
esac
echo "$result"
}
_add_composure_file ()
{
typeset func="$1"
typeset file="$2"
typeset operation="$3"
typeset comment="${4:-}"
typeset composure_dir=$(_get_composure_dir)
(
if ! cd "$composure_dir"; then
printf "%s\n" "Oops! Can't find $composure_dir!"
return
fi
if git rev-parse 2>/dev/null; then
if [ ! -f "$file" ]; then
printf "%s\n" "Oops! Couldn't find $file to version it for you..."
return return
fi
cp "$file" "$composure_dir/$func.inc"
git add --all .
if [ -z "$comment" ]; then
comment="$(_prompt 'Git Comment: ')"
fi
git commit -m "$operation $func: $comment"
fi fi
)
printf "%-20s%s\n" "$leftcol" "$rightcol"
} }
transcribe () _transcribe ()
{ {
typeset func=$1 typeset func="$1"
typeset file=$2 typeset file="$2"
typeset operation="$3" typeset operation="$3"
typeset comment="${4:-}"
typeset composure_dir=$(_get_composure_dir)
if git --version >/dev/null 2>&1; then if git --version >/dev/null 2>&1; then
if [ -d ~/.composure ]; then if [ -d "$composure_dir" ]; then
( _add_composure_file "$func" "$file" "$operation" "$comment"
cd ~/.composure
if git rev-parse 2>/dev/null; then
if [ ! -f $file ]; then
printf "%s\n" "Oops! Couldn't find $file to version it for you..."
return
fi
cp $file ~/.composure/$func.inc
git add --all .
git commit -m "$operation $func"
fi
)
else
if [ "$USE_COMPOSURE_REPO" = "0" ]; then
return # if you say so...
fi
printf "%s\n" "I see you don't have a ~/.composure repo..."
typeset input
typeset valid=0
while [ $valid != 1 ]; do
printf "\n%s" 'would you like to create one? y/n: '
read input
case $input in
y|yes|Y|Yes|YES)
(
echo 'creating git repository for your functions...'
mkdir ~/.composure
cd ~/.composure
git init
echo "composure stores your function definitions here" > README.txt
git add README.txt
git commit -m 'initial commit'
)
# if at first you don't succeed...
transcribe "$func" "$file" "$operation"
valid=1
;;
n|no|N|No|NO)
printf "%s\n" "ok. add 'export USE_COMPOSURE_REPO=0' to your startup script to disable this message."
valid=1
;;
*)
printf "%s\n" "sorry, didn't get that..."
;;
esac
done
fi
fi
}
typeset_functions ()
{
# unfortunately, there does not seem to be a easy, portable way to list just the
# names of the defined shell functions...
# first, determine our shell:
typeset shell
if [ -n "$SHELL" ]; then
shell=$(basename $SHELL) # we assume this is set correctly!
else else
# we'll have to try harder if [ "$USE_COMPOSURE_REPO" = "0" ]; then
# here's a hack I modified from a StackOverflow post: return # if you say so...
# we loop over the ps listing for the current process ($$), and print the last column (CMD) fi
# stripping any leading hyphens bash sometimes throws in there printf "%s\n" "I see you don't have a $composure_dir repo..."
typeset x ans typeset input=''
typeset this=$(for x in $(ps -p $$); do ans=$x; done; printf "%s\n" $ans | sed 's/^-*//') typeset valid=0
typeset shell=$(basename $this) # e.g. /bin/bash => bash while [ $valid != 1 ]; do
fi printf "\n%s" 'would you like to create one? y/n: '
case "$shell" in read -r input
bash) case $input in
typeset -F | awk '{print $3}' y|yes|Y|Yes|YES)
(
echo 'creating git repository for your functions...'
mkdir -p "$composure_dir" || return 1
cd "$composure_dir" || return 1
git init
echo "composure stores your function definitions here" > README.txt
git add README.txt
git commit -m 'initial commit'
)
# if at first you don't succeed...
_transcribe "$func" "$file" "$operation" "$comment"
valid=1
;; ;;
*) n|no|N|No|NO)
# trim everything following '()' in ksh printf "%s\n" "ok. add 'export USE_COMPOSURE_REPO=0' to your startup script to disable this message."
typeset +f | sed 's/().*$//' valid=1
;; ;;
esac *)
printf "%s\n" "sorry, didn't get that..."
;;
esac
done
fi
fi
} }
_typeset_functions ()
{
# unfortunately, there does not seem to be a easy, portable way to list just the
# names of the defined shell functions...
# bootstrap metadata keywords for porcelain functions case "$(_shell)" in
for f in $(composure_keywords) sh|bash)
do typeset -F | awk '{print $3}'
;;
*)
# trim everything following '()' in ksh/zsh
typeset +f | sed 's/().*$//'
;;
esac
}
_typeset_functions_about ()
{
typeset f
for f in $(_typeset_functions); do
typeset -f -- "$f" | grep -qE "^about[[:space:]]|[[:space:]]about[[:space:]]" && echo -- "$f"
done
}
_shell () {
# here's a hack I modified from a StackOverflow post:
# get the ps listing for the current process ($$), and print the last column (CMD)
# stripping any leading hyphens shells sometimes throw in there
typeset this=$(ps -p $$ | tail -1 | awk '{print $NF}' | sed 's/^-*//')
echo "${this##*/}" # e.g. /bin/bash => bash
}
_generate_metadata_functions() {
typeset f
for f in $(_composure_keywords)
do
eval "$f() { :; }" eval "$f() { :; }"
done done
unset f }
_list_composure_files () {
typeset composure_dir="$(_get_composure_dir)"
[ -d "$composure_dir" ] && find "$composure_dir" -maxdepth 1 -name '*.inc'
}
_load_composed_functions () {
# load previously composed functions into shell
# you may disable this by adding the following line to your shell startup:
# export LOAD_COMPOSED_FUNCTIONS=0
if [ "$LOAD_COMPOSED_FUNCTIONS" = "0" ]; then
return # if you say so...
fi
typeset inc
for inc in $(_list_composure_files); do
# shellcheck source=/dev/null
. "$inc"
done
}
_strip_trailing_whitespace () {
sed -e 's/ \+$//'
}
_strip_semicolons () {
sed -e 's/;$//'
}
# 'porcelain' functions # 'porcelain' functions
cite () cite ()
{ {
about creates one or more meta keywords for use in your functions about 'creates one or more meta keywords for use in your functions'
param one or more keywords param 'one or more keywords'
example '$ cite url username' example '$ cite url username'
example '$ url http://somewhere.com' example '$ url http://somewhere.com'
example '$ username alice' example '$ username alice'
group composure group 'composure'
# this is the storage half of the 'metadata' system: # this is the storage half of the 'metadata' system:
# we create dynamic metadata keywords with function wrappers around # we create dynamic metadata keywords with function wrappers around
# the NOP command, ':' # the NOP command, ':'
# anything following a keyword will get parsed as a positional # anything following a keyword will get parsed as a positional
# parameter, but stay resident in the ENV. As opposed to shell # parameter, but stay resident in the ENV. As opposed to shell
# comments, '#', which do not get parsed and are not available # comments, '#', which do not get parsed and are not available
# at runtime. # at runtime.
# a BIG caveat--your metadata must be roughly parsable: do not use # a BIG caveat--your metadata must be roughly parsable: do not use
# contractions, and consider single or double quoting if it contains # contractions, and consider single or double quoting if it contains
# non-alphanumeric characters # non-alphanumeric characters
if [ -z "$1" ]; then if [ -z "$1" ]; then
printf '%s\n' 'missing parameter(s)' printf '%s\n' 'missing parameter(s)'
reference cite reference cite
return return
fi fi
typeset keyword typeset keyword
for keyword in $*; do for keyword in "$@"; do
eval "$keyword() { :; }" eval "$keyword() { :; }"
done done
} }
draft () draft ()
{ {
about wraps command from history into a new function, default is last command about 'wraps command from history into a new function, default is last command'
param 1: name to give function param '1: name to give function'
param 2: optional history line number param '2: optional history line number'
example '$ ls' example '$ ls'
example '$ draft list' example '$ draft list'
example '$ draft newfunc 1120 # wraps command at history line 1120 in newfunc()' example '$ draft newfunc 1120 # wraps command at history line 1120 in newfunc()'
group composure group 'composure'
typeset func=$1 typeset func=$1
typeset num=$2 typeset num=$2
typeset cmd
if [ -z "$func" ]; then if [ -z "$func" ]; then
printf '%s\n' 'missing parameter(s)' printf '%s\n' 'missing parameter(s)'
reference draft reference draft
return return
fi fi
# aliases bind tighter than function names, disallow them # aliases bind tighter than function names, disallow them
if [ -n "$(LANG=C type -t $func 2>/dev/null | grep 'alias')" ]; then if type -a "$func" 2>/dev/null | grep -q 'is.*alias'; then
printf '%s\n' "sorry, $(type -a $func). please choose another name." printf '%s\n' "sorry, $(type -a "$func"). please choose another name."
return return
fi fi
if [ -z "$num" ]; then typeset cmd
# parse last command from fc output if [ -z "$num" ]; then
# some versions of 'fix command, fc' need corrective lenses... # some versions of 'fix command, fc' need corrective lenses...
typeset myopic=$(fc -ln -1 | grep draft) typeset lines=$(fc -ln -1 | grep -q draft && echo 2 || echo 1)
typeset lines=1 # parse last command from fc output
if [ -n "$myopic" ]; then # shellcheck disable=SC2086
lines=2 cmd=$(fc -ln -$lines | head -1 | sed 's/^[[:blank:]]*//')
fi else
cmd=$(fc -ln -$lines | head -1 | sed 's/^[[:blank:]]*//') # parse command from history line number
else cmd=$(eval "history | grep '^[[:blank:]]*$num' | head -1" | sed 's/^[[:blank:][:digit:]]*//')
# parse command from history line number fi
cmd=$(eval "history | grep '^[[:blank:]]*$num' | head -1" | sed 's/^[[:blank:][:digit:]]*//') eval "function $func {
fi author '$(_get_author_name)'
eval "$func() { $cmd; }" about ''
typeset file=$(mktemp /tmp/draft.XXXX) param ''
typeset -f $func > $file example ''
transcribe $func $file draft group ''
rm $file 2>/dev/null
$cmd;
}"
typeset file=$(_temp_filename_for draft)
typeset -f "$func" | _strip_trailing_whitespace | _strip_semicolons > "$file"
_transcribe "$func" "$file" Draft "Initial draft"
command rm "$file" 2>/dev/null
revise "$func"
} }
glossary () glossary ()
{ {
about displays help summary for all functions, or summary for a group of functions about 'displays help summary for all functions, or summary for a group of functions'
param 1: optional, group name param '1: optional, group name'
example '$ glossary' example '$ glossary'
example '$ glossary misc' example '$ glossary misc'
group composure group 'composure'
typeset targetgroup=${1:-} typeset targetgroup=${1:-}
typeset functionlist="$(_typeset_functions_about)"
typeset maxwidth=$(_longest_function_name_length "$functionlist" | awk '{print $1 + 5}')
for func in $(typeset_functions); do for func in $(echo $functionlist); do
if [ -n "$targetgroup" ]; then
typeset group="$(typeset -f $func | metafor group)" if [ "X${targetgroup}X" != "XX" ]; then
if [ "$group" != "$targetgroup" ]; then typeset group="$(typeset -f -- $func | metafor group)"
continue # skip non-matching groups, if specified if [ "$group" != "$targetgroup" ]; then
fi continue # skip non-matching groups, if specified
fi fi
typeset about="$(typeset -f $func | metafor about)" fi
letterpress "$about" $func typeset about="$(typeset -f -- $func | metafor about)"
typeset aboutline=
echo "$about" | fmt | while read -r aboutline; do
_letterpress "$aboutline" "$func" "$maxwidth"
func=" " # only display function name once
done done
done
} }
metafor () metafor ()
{ {
about prints function metadata associated with keyword about 'prints function metadata associated with keyword'
param 1: meta keyword param '1: meta keyword'
example '$ typeset -f glossary | metafor example' example '$ typeset -f glossary | metafor example'
group composure group 'composure'
typeset keyword=$1 typeset keyword=$1
if [ -z "$keyword" ]; then if [ -z "$keyword" ]; then
printf '%s\n' 'missing parameter(s)' printf '%s\n' 'missing parameter(s)'
reference metafor reference metafor
return return
fi fi
# this sed-fu is the retrieval half of the 'metadata' system: # this sed-fu is the retrieval half of the 'metadata' system:
# 'grep' for the metadata keyword, and then parse/filter the matching line # 'grep' for the metadata keyword, and then parse/filter the matching line
# grep keyword # strip trailing '|"|; # ignore thru keyword and leading '|" # grep keyword # strip trailing '|"|; # ignore thru keyword and leading '|"
sed -n "/$keyword / s/['\";]*$//;s/^[ ]*$keyword ['\"]*\([^([].*\)*$/\1/p" sed -n "/$keyword / s/['\";]*\$//;s/^[ ]*$keyword ['\"]*\([^([].*\)*\$/\1/p"
} }
reference () reference ()
{ {
about displays apidoc help for a specific function about 'displays apidoc help for a specific function'
param 1: function name param '1: function name'
example '$ reference revise' example '$ reference revise'
group composure group 'composure'
typeset func=$1 typeset func=$1
if [ -z "$func" ]; then if [ -z "$func" ]; then
printf '%s\n' 'missing parameter(s)' printf '%s\n' 'missing parameter(s)'
reference reference reference reference
return return
fi fi
typeset line typeset line
typeset about="$(typeset -f $func | metafor about)" typeset about="$(typeset -f "$func" | metafor about)"
letterpress "$about" $func _letterpress "$about" "$func"
typeset author="$(typeset -f $func | metafor author)" typeset author="$(typeset -f $func | metafor author)"
if [ -n "$author" ]; then if [ -n "$author" ]; then
letterpress "$author" 'author:' _letterpress "$author" 'author:'
fi fi
typeset version="$(typeset -f $func | metafor version)" typeset version="$(typeset -f $func | metafor version)"
if [ -n "$version" ]; then if [ -n "$version" ]; then
letterpress "$version" 'version:' _letterpress "$version" 'version:'
fi fi
if [ -n "$(typeset -f $func | metafor param)" ]; then if [ -n "$(typeset -f $func | metafor param)" ]; then
printf "parameters:\n" printf "parameters:\n"
typeset -f $func | metafor param | while read line typeset -f $func | metafor param | while read -r line
do do
letterpress "$line" _letterpress "$line"
done done
fi fi
if [ -n "$(typeset -f $func | metafor example)" ]; then if [ -n "$(typeset -f $func | metafor example)" ]; then
printf "examples:\n" printf "examples:\n"
typeset -f $func | metafor example | while read line typeset -f $func | metafor example | while read -r line
do do
letterpress "$line" _letterpress "$line"
done done
fi fi
} }
revise () revise ()
{ {
about loads function into editor for revision about 'loads function into editor for revision'
param 1: name of function param '<optional> -e: revise version stored in ENV'
example '$ revise myfunction' param '1: name of function'
group composure example '$ revise myfunction'
example '$ revise -e myfunction'
example 'save a zero-length file to abort revision'
group 'composure'
typeset func=$1 typeset source='git'
typeset temp=$(mktemp /tmp/revise.XXXX) if [ "$1" = '-e' ]; then
source='env'
shift
fi
if [ -z "$func" ]; then typeset func=$1
printf '%s\n' 'missing parameter(s)' if [ -z "$func" ]; then
reference revise printf '%s\n' 'missing parameter(s)'
return reference revise
fi return
fi
# populate tempfile... typeset composure_dir=$(_get_composure_dir)
if [ -f ~/.composure/$func.inc ]; then typeset temp=$(_temp_filename_for revise)
# ...with contents of latest git revision... # populate tempfile...
cat ~/.composure/$func.inc >> $temp if [ "$source" = 'env' ] || [ ! -f "$composure_dir/$func.inc" ]; then
else # ...with ENV if specified or not previously versioned
# ...or from ENV if not previously versioned typeset -f $func > $temp
typeset -f $func >> $temp else
fi # ...or with contents of latest git revision
cat "$composure_dir/$func.inc" > "$temp"
fi
if [ -z "$EDITOR" ] if [ -z "$EDITOR" ]
then then
typeset EDITOR=vi typeset EDITOR=vi
fi fi
$EDITOR $temp $EDITOR "$temp"
. $temp # source edited file if [ -s "$temp" ]; then
typeset edit='N'
transcribe $func $temp revise # source edited file
rm $temp # shellcheck source=/dev/null
. "$temp" || edit='Y'
while [ $edit = 'Y' ]; do
echo -n "Re-edit? Y/N: "
read -r edit
case $edit in
y|yes|Y|Yes|YES)
edit='Y'
$EDITOR "$temp"
# shellcheck source=/dev/null
. "$temp" && edit='N';;
*)
edit='N';;
esac
done
_transcribe "$func" "$temp" Revise
else
# zero-length files abort revision
printf '%s\n' 'zero-length file, revision aborted!'
fi
command rm "$temp"
} }
write () write ()
{ {
about writes one or more composed function definitions to stdout about 'writes one or more composed function definitions to stdout'
param one or more function names param 'one or more function names'
example '$ write finddown foo' example '$ write finddown foo'
example '$ write finddown' example '$ write finddown'
group composure group 'composure'
if [ -z "$1" ]; then if [ -z "$1" ]; then
printf '%s\n' 'missing parameter(s)' printf '%s\n' 'missing parameter(s)'
reference write reference write
return return
fi fi
echo "#!/usr/bin/env ${SHELL##*/}"
# bootstrap metadata # bootstrap metadata
cat <<END cat <<END
for f in $(composure_keywords) for f in $(_composure_keywords)
do do
eval "\$f() { :; }" eval "\$f() { :; }"
done done
unset f unset f
END END
# include cite() to enable custom keywords # write out function definitons
typeset -f cite $* # shellcheck disable=SC2034
typeset -f cite "$@"
cat <<END
main() {
echo "edit me to do something useful!"
exit 0
} }
main \$*
END
}
_bootstrap_composure
: <<EOF : <<EOF
License: The MIT License License: The MIT License
Copyright © 2012 Erich Smith Copyright © 2012, 2013 Erich Smith
Permission is hereby granted, free of charge, to any person obtaining a copy of this Permission is hereby granted, free of charge, to any person obtaining a copy of this
software and associated documentation files (the "Software"), to deal in the Software software and associated documentation files (the "Software"), to deal in the Software