- 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

399
lib/composure.bash 100644 → 100755
View File

@ -1,72 +1,163 @@
#!/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
_load_composed_functions
_determine_printf_cmd
}
_get_composure_dir ()
{
if [ -n "$XDG_DATA_HOME" ]; then
echo "$XDG_DATA_HOME/composure"
else
echo "$HOME/.local/composure"
fi
}
_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" echo "about author example group param version"
} }
letterpress () _letterpress ()
{ {
typeset rightcol="$1" leftcol="${2:- }" typeset rightcol="$1" leftcol="${2:- }" leftwidth="${3:-20}"
if [ -z "$rightcol" ]; then if [ -z "$rightcol" ]; then
return return
fi fi
printf "%-20s%s\n" "$leftcol" "$rightcol" $_printf_cmd "%-*s%s\n" "$leftwidth" "$leftcol" "$rightcol"
} }
transcribe () _determine_printf_cmd() {
{ if [ -z "$_printf_cmd" ]; then
typeset func=$1 _printf_cmd=printf
typeset file=$2 # prefer GNU gprintf if available
typeset operation="$3" [ -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 git --version >/dev/null 2>&1; then
if [ -d ~/.composure ]; then
( (
cd ~/.composure 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 git rev-parse 2>/dev/null; then
if [ ! -f $file ]; then if [ ! -f "$file" ]; then
printf "%s\n" "Oops! Couldn't find $file to version it for you..." printf "%s\n" "Oops! Couldn't find $file to version it for you..."
return return
fi fi
cp $file ~/.composure/$func.inc cp "$file" "$composure_dir/$func.inc"
git add --all . git add --all .
git commit -m "$operation $func" if [ -z "$comment" ]; then
comment="$(_prompt 'Git Comment: ')"
fi
git commit -m "$operation $func: $comment"
fi fi
) )
}
_transcribe ()
{
typeset func="$1"
typeset file="$2"
typeset operation="$3"
typeset comment="${4:-}"
typeset composure_dir=$(_get_composure_dir)
if git --version >/dev/null 2>&1; then
if [ -d "$composure_dir" ]; then
_add_composure_file "$func" "$file" "$operation" "$comment"
else else
if [ "$USE_COMPOSURE_REPO" = "0" ]; then if [ "$USE_COMPOSURE_REPO" = "0" ]; then
return # if you say so... return # if you say so...
fi fi
printf "%s\n" "I see you don't have a ~/.composure repo..." printf "%s\n" "I see you don't have a $composure_dir repo..."
typeset input typeset input=''
typeset valid=0 typeset valid=0
while [ $valid != 1 ]; do while [ $valid != 1 ]; do
printf "\n%s" 'would you like to create one? y/n: ' printf "\n%s" 'would you like to create one? y/n: '
read input read -r input
case $input in case $input in
y|yes|Y|Yes|YES) y|yes|Y|Yes|YES)
( (
echo 'creating git repository for your functions...' echo 'creating git repository for your functions...'
mkdir ~/.composure mkdir -p "$composure_dir" || return 1
cd ~/.composure cd "$composure_dir" || return 1
git init git init
echo "composure stores your function definitions here" > README.txt echo "composure stores your function definitions here" > README.txt
git add README.txt git add README.txt
git commit -m 'initial commit' git commit -m 'initial commit'
) )
# if at first you don't succeed... # if at first you don't succeed...
transcribe "$func" "$file" "$operation" _transcribe "$func" "$file" "$operation" "$comment"
valid=1 valid=1
;; ;;
n|no|N|No|NO) n|no|N|No|NO)
@ -82,54 +173,86 @@ transcribe ()
fi fi
} }
typeset_functions () _typeset_functions ()
{ {
# unfortunately, there does not seem to be a easy, portable way to list just the # unfortunately, there does not seem to be a easy, portable way to list just the
# names of the defined shell functions... # names of the defined shell functions...
# first, determine our shell: case "$(_shell)" in
typeset shell sh|bash)
if [ -n "$SHELL" ]; then
shell=$(basename $SHELL) # we assume this is set correctly!
else
# we'll have to try harder
# here's a hack I modified from a StackOverflow post:
# we loop over the ps listing for the current process ($$), and print the last column (CMD)
# stripping any leading hyphens bash sometimes throws in there
typeset x ans
typeset this=$(for x in $(ps -p $$); do ans=$x; done; printf "%s\n" $ans | sed 's/^-*//')
typeset shell=$(basename $this) # e.g. /bin/bash => bash
fi
case "$shell" in
bash)
typeset -F | awk '{print $3}' typeset -F | awk '{print $3}'
;; ;;
*) *)
# trim everything following '()' in ksh # trim everything following '()' in ksh/zsh
typeset +f | sed 's/().*$//' typeset +f | sed 's/().*$//'
;; ;;
esac 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
}
# bootstrap metadata keywords for porcelain functions _shell () {
for f in $(composure_keywords) # 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 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
@ -151,24 +274,23 @@ cite ()
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)'
@ -177,59 +299,73 @@ draft ()
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
typeset cmd
if [ -z "$num" ]; then if [ -z "$num" ]; then
# parse last command from fc output
# 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
fi
cmd=$(fc -ln -$lines | head -1 | sed 's/^[[:blank:]]*//') cmd=$(fc -ln -$lines | head -1 | sed 's/^[[:blank:]]*//')
else else
# parse command from history line number # parse command from history line number
cmd=$(eval "history | grep '^[[:blank:]]*$num' | head -1" | sed 's/^[[:blank:][:digit:]]*//') cmd=$(eval "history | grep '^[[:blank:]]*$num' | head -1" | sed 's/^[[:blank:][:digit:]]*//')
fi fi
eval "$func() { $cmd; }" eval "function $func {
typeset file=$(mktemp /tmp/draft.XXXX) author '$(_get_author_name)'
typeset -f $func > $file about ''
transcribe $func $file draft param ''
rm $file 2>/dev/null example ''
group ''
$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
typeset group="$(typeset -f -- $func | metafor group)"
if [ "$group" != "$targetgroup" ]; then if [ "$group" != "$targetgroup" ]; then
continue # skip non-matching groups, if specified continue # skip non-matching groups, if specified
fi fi
fi fi
typeset about="$(typeset -f $func | metafor about)" typeset about="$(typeset -f -- $func | metafor about)"
letterpress "$about" $func 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
@ -243,15 +379,15 @@ metafor ()
# '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
@ -262,59 +398,68 @@ reference ()
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'
param '1: name of function'
example '$ revise myfunction' example '$ revise myfunction'
group composure example '$ revise -e myfunction'
example 'save a zero-length file to abort revision'
group 'composure'
typeset source='git'
if [ "$1" = '-e' ]; then
source='env'
shift
fi
typeset func=$1 typeset func=$1
typeset temp=$(mktemp /tmp/revise.XXXX)
if [ -z "$func" ]; then if [ -z "$func" ]; then
printf '%s\n' 'missing parameter(s)' printf '%s\n' 'missing parameter(s)'
reference revise reference revise
return return
fi fi
typeset composure_dir=$(_get_composure_dir)
typeset temp=$(_temp_filename_for revise)
# populate tempfile... # populate tempfile...
if [ -f ~/.composure/$func.inc ]; then if [ "$source" = 'env' ] || [ ! -f "$composure_dir/$func.inc" ]; then
# ...with contents of latest git revision... # ...with ENV if specified or not previously versioned
cat ~/.composure/$func.inc >> $temp typeset -f $func > $temp
else else
# ...or from ENV if not previously versioned # ...or with contents of latest git revision
typeset -f $func >> $temp cat "$composure_dir/$func.inc" > "$temp"
fi fi
if [ -z "$EDITOR" ] if [ -z "$EDITOR" ]
@ -322,20 +467,42 @@ revise ()
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)'
@ -343,23 +510,37 @@ 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