-#!/bin/bash
-# Composure - don't fear the UNIX chainsaw...
-# by erichs, 2012
+# composure - by erichs
+# light-hearted shell functions for intuitive shell programming
-# these are a set of light-hearted shell functions that aim to make
-# programming the shell easier and more intuitive
+# install: source this script in your ~/.profile or ~/.${SHELL}rc script
# latest source available at http://git.io/composure
+# known to work on bash, zsh, and ksh93
-source_composure ()
-{
- if [ -z "$EDITOR" ]
- then
- export EDITOR=vi
- fi
-
- if $(tty -s) # is this a TTY?
- then
- bind '"\C-j": edit-and-execute-command'
- fi
-
- cite ()
- {
- about () { :; }
- about creates a new meta keyword for use in your functions
- local keyword=$1
- for keyword in $*; do
- eval "function $keyword { :; }"
- done
- }
-
- cite about param example
-
- draft ()
- {
- about wraps last command into a new function
- param 1: name to give function
- example $ ls
- example $ draft list
- example $ list
- local name=$1
- eval 'function ' $name ' { ' $(fc -ln -1) '; }'
- }
-
- write ()
- {
- about prints function declaration to stdout
- param name of function or functions, separated by spaces
- example $ write myfunction
- example $ write func1 func2 func3 > ~/funcs.sh
- local func
- for func in $*
- do
- # trim trailing semicolons generated by declare -f
- declare -f $func | sed "s/^\(.*\);$/\1/"
- echo
- done
- }
-
- revise ()
- {
- about loads function into editor for revision
- param name of function or functions, separated by spaces
- example $ revise myfunction
- example $ revise func1 func2 func3
- local temp=$(mktemp /tmp/revise.XXXX)
- write $* > $temp
- $EDITOR $temp
- eval "$(cat $temp)"
- rm $temp
- }
-
- metafor ()
- {
- about prints function metadata associated with keyword
- param 1: function name
- param 2: meta keyword
- example $ metafor reference example
- local func=$1 keyword=$2
- write $func | sed -n "s/^ *$keyword \([^([].*\)$/\1/p"
- }
-
- reference ()
- {
- about displays help summary for all functions, or help for specific function
- param 1: optional, function name
- example $ reference
- example $ reference metafor
-
- printline ()
- {
- local metadata=$1 lhs=${2:- }
-
- if [[ -z "$metadata" ]]
- then
- return
- fi
+# define default metadata keywords:
+about () { :; }
+group () { :; }
+param () { :; }
+author () { :; }
+example () { :; }
- OLD=$IFS; IFS=$'\n'
- local line
- for line in $metadata
- do
- printf "%-20s%s\n" $lhs $line
- done
- IFS=$OLD
- }
-
- help ()
- {
- local func=$1
+cite ()
+{
+ about creates a new meta keyword for use in your functions
+ param 1: keyword
+ example $ cite url
+ example $ url http://somewhere.com
+ group composure
+
+ # this is the storage half of the 'metadata' system:
+ # we create dynamic metadata keywords with function wrappers around
+ # the NOP command, ':'
+
+ # anything following a keyword will get parsed as a positional
+ # parameter, but stay resident in the ENV. As opposed to shell
+ # comments, '#', which do not get parsed, thus are not available
+ # at runtime.
+
+ # a BIG caveat--your metadata must be roughly parsable: do not use
+ # contractions, and consider single or double quoting if it contains
+ # non-alphanumeric characters
+
+ typeset keyword
+ for keyword in $*; do
+ eval "function $keyword { :; }"
+ done
+}
- local about="$(metafor $func about)"
- printline "$about" $func
+draft ()
+{
+ about wraps last command into a new function
+ param 1: name to give function
+ example $ ls
+ example $ draft list
+ example $ list
+ group composure
+
+ typeset func=$1
+ eval 'function ' $func ' { ' $(fc -ln -1) '; }'
+ typeset file=$(mktemp /tmp/draft.XXXX)
+ typeset -f $func > $file
+ transcribe $func $file draft
+ rm $file 2>/dev/null
+}
- local params="$(metafor $func param)"
- if [[ -n "$params" ]]
- then
- echo "parameters:"
- printline "$params"
+glossary ()
+{
+ about displays help summary for all functions, or summary for a group of functions
+ param 1: optional, group name
+ example $ glossary
+ example $ glossary misc
+ group composure
+
+ typeset targetgroup=${1:-}
+
+ for func in $(listfunctions); do
+ typeset about="$(metafor $func about)"
+ if [ -n "$targetgroup" ]; then
+ typeset group="$(metafor $func group)"
+ if [ "$group" != "$targetgroup" ]; then
+ continue # skip non-matching groups, if specified
fi
+ fi
+ letterpress "$about" $func
+ done
+}
- local examples="$(metafor $func example)"
- if [[ -n "$examples" ]]
- then
- echo "examples:"
- printline "$examples"
- fi
+letterpress ()
+{
+ typeset metadata=$1 leftcol=${2:- } rightcol
- unset printline
- }
+ if [ -z "$metadata" ]; then
+ return
+ fi
- if [[ -n "$1" ]]
- then
- help $1
- else
- for func in $(compgen -A function); do
- local about="$(metafor $func about)"
- printline "$about" $func
- done
- fi
+ OLD=$IFS; IFS=$'\n'
+ for rightcol in $metadata; do
+ printf "%-20s%s\n" $leftcol $rightcol
+ done
+ IFS=$OLD
+}
- unset help printline
- }
+listfunctions ()
+{
+ # unfortunately, there does not seem to be a easy, portable way to list just the
+ # names of the defined shell functions...
+
+ # 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/^-*//')
+ case "$this" in
+ bash)
+ typeset -F | awk '{print $3}'
+ ;;
+ *)
+ # trim everything following '()' in ksh
+ typeset +f | sed 's/().*$//'
+ ;;
+ esac
+}
+metafor ()
+{
+ about prints function metadata associated with keyword
+ param 1: function name
+ param 2: meta keyword
+ example $ metafor glossary example
+ group composure
+ typeset func=$1 keyword=$2
+
+ # this sed-fu is the retrieval half of the 'metadata' system:
+ # first 'cat' the function definition,
+ # then 'grep' for the metadata keyword, and
+ # then parse and massage the matching line
+ typeset -f $func | sed -n "s/;$//;s/^[ ]*$keyword \([^([].*\)*$/\1/p"
}
-install_composure ()
+reference ()
{
- echo 'stay calm. installing composure elements...'
+ about displays apidoc help for a specific function
+ param 1: function name
+ example $ reference revise
+ group composure
- # find our absolute PATH
- SOURCE="${BASH_SOURCE[0]}"
- while [ -h "$SOURCE" ]
- do
- SOURCE="$(readlink "$SOURCE")"
- done
- DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
+ typeset func=$1
- # vim: automatically chmod +x scripts with #! lines
- done_previously () { [ ! -z "$(grep BufWritePost | grep bin | grep chmod)" ]; }
+ typeset about="$(metafor $func about)"
+ letterpress "$about" $func
- if [ -f ~/.vimrc ] && ! $(<~/.vimrc done_previously)
- then
- echo 'vimrc: adding automatic chmod+x for files with shebang (#!) lines...'
- echo 'au BufWritePost * if getline(1) =~ "^#!" | if getline(1) =~ "/bin/" | silent execute "!chmod a+x <afile>" | endif | endif' >> ~/.vimrc
+ typeset params="$(metafor $func param)"
+ if [ -n "$params" ]; then
+ printf "parameters:\n"
+ letterpress "$params"
fi
- # source this file in your startup: .bashrc, or .bash_profile
- local done=0
- done_previously () { [ ! -z "$(grep source | grep $DIR | grep composure)" ]; }
+ typeset examples="$(metafor $func example)"
+ if [ -n "$examples" ]; then
+ printf "examples:\n"
+ letterpress "$examples"
+ fi
+}
- [ -f ~/.bashrc ] && $(<~/.bashrc done_previously) && done=1
- ! (($done)) && [ -f ~/.bash_profile ] && $(<~/.bash_profile done_previously) && done=1
+revise ()
+{
+ about loads function into editor for revision
+ param 1: name of function
+ example $ revise myfunction
+ group composure
+
+ typeset func=$1
+ typeset temp=$(mktemp /tmp/revise.XXXX)
+
+ # populate tempfile...
+ if [ -f ~/.composure/$func.inc ]; then
+ # ...with contents of latest git revision...
+ cat ~/.composure/$func.inc >> $temp
+ else
+ # ...or from ENV if not previously versioned
+ typeset -f $func >> $temp
+ fi
- if ! (($done))
+ if [ -z "$EDITOR" ]
then
- echo 'sourcing composure from .bashrc...'
- echo "source $DIR/$(basename $0)" >> ~/.bashrc
+ typeset EDITOR=vi
fi
- echo 'composure installed.'
+ $EDITOR $temp
+ source $temp
+
+ transcribe $func $temp revise
+ rm $temp
}
-if [[ "$BASH_SOURCE" == "$0" ]]
-then
- install_composure
-else
- source_composure
- unset install_composure source_composure
-fi
+transcribe ()
+{
+ typeset func=$1
+ typeset file=$2
+ typeset operation="$3"
+
+ if git --version >/dev/null 2>&1; then
+ if [ -d ~/.composure ]; then
+ (
+ 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
+}
: <<EOF
License: The MIT License