blob: beaf13a2c573f37701a3e6cf8831b9db3a1915b4 [file] [log] [blame]
Travis Swicegood3e7c0fb2012-05-04 16:30:22 -05001# composure - by erichs
Erich Smith635c3802012-05-08 23:48:01 -04002# light-hearted functions for intuitive shell programming
Erich Smitha8fcf9f2012-04-27 15:07:04 -04003
Travis Swicegood3e7c0fb2012-05-04 16:30:22 -05004# install: source this script in your ~/.profile or ~/.${SHELL}rc script
Erich Smitha8fcf9f2012-04-27 15:07:04 -04005
6# latest source available at http://git.io/composure
Travis Swicegood3e7c0fb2012-05-04 16:30:22 -05007# known to work on bash, zsh, and ksh93
Erich Smitha8fcf9f2012-04-27 15:07:04 -04008
Erich Smith635c3802012-05-08 23:48:01 -04009# 'plumbing' functions
Travis Swicegood3e7c0fb2012-05-04 16:30:22 -050010
Erich Smith635c3802012-05-08 23:48:01 -040011composure_keywords ()
Erich Smitha8fcf9f2012-04-27 15:07:04 -040012{
Erich Smith635c3802012-05-08 23:48:01 -040013 echo "about author example group param version"
Travis Swicegood3e7c0fb2012-05-04 16:30:22 -050014}
15
16letterpress ()
17{
Erich Smith635c3802012-05-08 23:48:01 -040018 typeset rightcol="$1" leftcol="${2:- }"
Travis Swicegood3e7c0fb2012-05-04 16:30:22 -050019
Erich Smith635c3802012-05-08 23:48:01 -040020 if [ -z "$rightcol" ]; then
Travis Swicegood3e7c0fb2012-05-04 16:30:22 -050021 return
22 fi
23
Erich Smith635c3802012-05-08 23:48:01 -040024 printf "%-20s%s\n" "$leftcol" "$rightcol"
Erich Smitha8fcf9f2012-04-27 15:07:04 -040025}
26
Travis Swicegood3e7c0fb2012-05-04 16:30:22 -050027transcribe ()
Erich Smitha8fcf9f2012-04-27 15:07:04 -040028{
Travis Swicegood3e7c0fb2012-05-04 16:30:22 -050029 typeset func=$1
30 typeset file=$2
31 typeset operation="$3"
Erich Smitha8fcf9f2012-04-27 15:07:04 -040032
Travis Swicegood3e7c0fb2012-05-04 16:30:22 -050033 if git --version >/dev/null 2>&1; then
34 if [ -d ~/.composure ]; then
35 (
36 cd ~/.composure
37 if git rev-parse 2>/dev/null; then
38 if [ ! -f $file ]; then
39 printf "%s\n" "Oops! Couldn't find $file to version it for you..."
40 return
41 fi
42 cp $file ~/.composure/$func.inc
43 git add --all .
44 git commit -m "$operation $func"
45 fi
46 )
47 else
48 if [ "$USE_COMPOSURE_REPO" = "0" ]; then
49 return # if you say so...
50 fi
51 printf "%s\n" "I see you don't have a ~/.composure repo..."
52 typeset input
53 typeset valid=0
54 while [ $valid != 1 ]; do
55 printf "\n%s" 'would you like to create one? y/n: '
56 read input
57 case $input in
58 y|yes|Y|Yes|YES)
59 (
60 echo 'creating git repository for your functions...'
61 mkdir ~/.composure
62 cd ~/.composure
63 git init
64 echo "composure stores your function definitions here" > README.txt
65 git add README.txt
66 git commit -m 'initial commit'
67 )
68 # if at first you don't succeed...
69 transcribe "$func" "$file" "$operation"
70 valid=1
71 ;;
72 n|no|N|No|NO)
73 printf "%s\n" "ok. add 'export USE_COMPOSURE_REPO=0' to your startup script to disable this message."
74 valid=1
75 ;;
76 *)
77 printf "%s\n" "sorry, didn't get that..."
78 ;;
79 esac
80 done
81 fi
Erich Smitha8fcf9f2012-04-27 15:07:04 -040082 fi
Erich Smitha8fcf9f2012-04-27 15:07:04 -040083}
84
Erich Smith635c3802012-05-08 23:48:01 -040085transcribe ()
86{
87 typeset func=$1
88 typeset file=$2
89 typeset operation="$3"
90
91 if git --version >/dev/null 2>&1; then
92 if [ -d ~/.composure ]; then
93 (
94 cd ~/.composure
95 if git rev-parse 2>/dev/null; then
96 if [ ! -f $file ]; then
97 printf "%s\n" "Oops! Couldn't find $file to version it for you..."
98 return
99 fi
100 cp $file ~/.composure/$func.inc
101 git add --all .
102 git commit -m "$operation $func"
103 fi
104 )
105 else
106 if [ "$USE_COMPOSURE_REPO" = "0" ]; then
107 return # if you say so...
108 fi
109 printf "%s\n" "I see you don't have a ~/.composure repo..."
110 typeset input
111 typeset valid=0
112 while [ $valid != 1 ]; do
113 printf "\n%s" 'would you like to create one? y/n: '
114 read input
115 case $input in
116 y|yes|Y|Yes|YES)
117 (
118 echo 'creating git repository for your functions...'
119 mkdir ~/.composure
120 cd ~/.composure
121 git init
122 echo "composure stores your function definitions here" > README.txt
123 git add README.txt
124 git commit -m 'initial commit'
125 )
126 # if at first you don't succeed...
127 transcribe "$func" "$file" "$operation"
128 valid=1
129 ;;
130 n|no|N|No|NO)
131 printf "%s\n" "ok. add 'export USE_COMPOSURE_REPO=0' to your startup script to disable this message."
132 valid=1
133 ;;
134 *)
135 printf "%s\n" "sorry, didn't get that..."
136 ;;
137 esac
138 done
139 fi
140 fi
141}
142
143typeset_functions ()
144{
145 # unfortunately, there does not seem to be a easy, portable way to list just the
146 # names of the defined shell functions...
147
Travis Swicegoode14543a2012-05-15 10:26:03 -0500148 # first, determine our shell:
149 typeset shell
150 if [ -n "$SHELL" ]; then
151 shell=$(basename $SHELL) # we assume this is set correctly!
152 else
153 # we'll have to try harder
154 # here's a hack I modified from a StackOverflow post:
155 # we loop over the ps listing for the current process ($$), and print the last column (CMD)
156 # stripping any leading hyphens bash sometimes throws in there
157 typeset x ans
158 typeset this=$(for x in $(ps -p $$); do ans=$x; done; printf "%s\n" $ans | sed 's/^-*//')
159 typeset shell=$(basename $this) # e.g. /bin/bash => bash
160 fi
Erich Smith635c3802012-05-08 23:48:01 -0400161 case "$shell" in
162 bash)
163 typeset -F | awk '{print $3}'
164 ;;
165 *)
166 # trim everything following '()' in ksh
167 typeset +f | sed 's/().*$//'
168 ;;
169 esac
170}
171
172
173# bootstrap metadata keywords for porcelain functions
174for f in $(composure_keywords)
175do
176 eval "$f() { :; }"
177done
178unset f
179
180
181# 'porcelain' functions
182
183cite ()
184{
185 about creates one or more meta keywords for use in your functions
186 param one or more keywords
187 example '$ cite url username'
188 example '$ url http://somewhere.com'
189 example '$ username alice'
190 group composure
191
192 # this is the storage half of the 'metadata' system:
193 # we create dynamic metadata keywords with function wrappers around
194 # the NOP command, ':'
195
196 # anything following a keyword will get parsed as a positional
197 # parameter, but stay resident in the ENV. As opposed to shell
198 # comments, '#', which do not get parsed and are not available
199 # at runtime.
200
201 # a BIG caveat--your metadata must be roughly parsable: do not use
202 # contractions, and consider single or double quoting if it contains
203 # non-alphanumeric characters
204
205 if [ -z "$1" ]; then
206 printf '%s\n' 'missing parameter(s)'
207 reference cite
208 return
209 fi
210
211 typeset keyword
212 for keyword in $*; do
213 eval "$keyword() { :; }"
214 done
215}
216
217draft ()
218{
219 about wraps command from history into a new function, default is last command
220 param 1: name to give function
221 param 2: optional history line number
222 example '$ ls'
223 example '$ draft list'
224 example '$ draft newfunc 1120 # wraps command at history line 1120 in newfunc()'
225 group composure
226
227 typeset func=$1
228 typeset num=$2
229 typeset cmd
230
231 if [ -z "$func" ]; then
232 printf '%s\n' 'missing parameter(s)'
233 reference draft
234 return
235 fi
236
Erich Smitha385e0f2012-05-11 14:10:39 -0400237 # aliases bind tighter than function names, disallow them
Travis Swicegoode14543a2012-05-15 10:26:03 -0500238 if [ -n "$(type -a $func 2>/dev/null | grep 'is.*alias')" ]; then
Erich Smitha385e0f2012-05-11 14:10:39 -0400239 printf '%s\n' "sorry, $(type -a $func). please choose another name."
240 return
241 fi
242
Erich Smith635c3802012-05-08 23:48:01 -0400243 if [ -z "$num" ]; then
Erich Smitha385e0f2012-05-11 14:10:39 -0400244 # parse last command from fc output
245 # some versions of 'fix command, fc' need corrective lenses...
246 typeset myopic=$(fc -ln -1 | grep draft)
247 typeset lines=1
248 if [ -n "$myopic" ]; then
249 lines=2
250 fi
251 cmd=$(fc -ln -$lines | head -1 | sed 's/^[[:blank:]]*//')
Erich Smith635c3802012-05-08 23:48:01 -0400252 else
253 # parse command from history line number
254 cmd=$(eval "history | grep '^[[:blank:]]*$num' | head -1" | sed 's/^[[:blank:][:digit:]]*//')
255 fi
256 eval "$func() { $cmd; }"
257 typeset file=$(mktemp /tmp/draft.XXXX)
258 typeset -f $func > $file
259 transcribe $func $file draft
260 rm $file 2>/dev/null
261}
262
263glossary ()
264{
265 about displays help summary for all functions, or summary for a group of functions
266 param 1: optional, group name
267 example '$ glossary'
268 example '$ glossary misc'
269 group composure
270
271 typeset targetgroup=${1:-}
272
273 for func in $(typeset_functions); do
Erich Smith635c3802012-05-08 23:48:01 -0400274 if [ -n "$targetgroup" ]; then
275 typeset group="$(typeset -f $func | metafor group)"
276 if [ "$group" != "$targetgroup" ]; then
277 continue # skip non-matching groups, if specified
278 fi
279 fi
Erich Smitha385e0f2012-05-11 14:10:39 -0400280 typeset about="$(typeset -f $func | metafor about)"
Erich Smith635c3802012-05-08 23:48:01 -0400281 letterpress "$about" $func
282 done
283}
284
285metafor ()
286{
287 about prints function metadata associated with keyword
288 param 1: meta keyword
289 example '$ typeset -f glossary | metafor example'
290 group composure
291
292 typeset keyword=$1
293
294 if [ -z "$keyword" ]; then
295 printf '%s\n' 'missing parameter(s)'
296 reference metafor
297 return
298 fi
299
300 # this sed-fu is the retrieval half of the 'metadata' system:
301 # 'grep' for the metadata keyword, and then parse/filter the matching line
302
Erich Smitha385e0f2012-05-11 14:10:39 -0400303 # grep keyword # strip trailing '|"|; # ignore thru keyword and leading '|"
304 sed -n "/$keyword / s/['\";]*$//;s/^[ ]*$keyword ['\"]*\([^([].*\)*$/\1/p"
Erich Smith635c3802012-05-08 23:48:01 -0400305}
306
307reference ()
308{
309 about displays apidoc help for a specific function
310 param 1: function name
311 example '$ reference revise'
312 group composure
313
314 typeset func=$1
315 if [ -z "$func" ]; then
316 printf '%s\n' 'missing parameter(s)'
317 reference reference
318 return
319 fi
320
321 typeset line
322
323 typeset about="$(typeset -f $func | metafor about)"
324 letterpress "$about" $func
325
326 typeset author="$(typeset -f $func | metafor author)"
327 if [ -n "$author" ]; then
328 letterpress "$author" 'author:'
329 fi
330
331 typeset version="$(typeset -f $func | metafor version)"
332 if [ -n "$version" ]; then
333 letterpress "$version" 'version:'
334 fi
335
336 if [ -n "$(typeset -f $func | metafor param)" ]; then
337 printf "parameters:\n"
338 typeset -f $func | metafor param | while read line
339 do
340 letterpress "$line"
341 done
342 fi
343
344 if [ -n "$(typeset -f $func | metafor example)" ]; then
345 printf "examples:\n"
346 typeset -f $func | metafor example | while read line
347 do
348 letterpress "$line"
349 done
350 fi
351}
352
353revise ()
354{
355 about loads function into editor for revision
356 param 1: name of function
357 example '$ revise myfunction'
358 group composure
359
360 typeset func=$1
361 typeset temp=$(mktemp /tmp/revise.XXXX)
362
363 if [ -z "$func" ]; then
364 printf '%s\n' 'missing parameter(s)'
365 reference revise
366 return
367 fi
368
369 # populate tempfile...
370 if [ -f ~/.composure/$func.inc ]; then
371 # ...with contents of latest git revision...
372 cat ~/.composure/$func.inc >> $temp
373 else
374 # ...or from ENV if not previously versioned
375 typeset -f $func >> $temp
376 fi
377
378 if [ -z "$EDITOR" ]
379 then
380 typeset EDITOR=vi
381 fi
382
383 $EDITOR $temp
384 . $temp # source edited file
385
386 transcribe $func $temp revise
387 rm $temp
388}
389
390write ()
391{
392 about writes one or more composed function definitions to stdout
393 param one or more function names
394 example '$ write finddown foo'
395 example '$ write finddown'
396 group composure
397
398 if [ -z "$1" ]; then
399 printf '%s\n' 'missing parameter(s)'
400 reference write
401 return
402 fi
403
404# bootstrap metadata
405cat <<END
406for f in $(composure_keywords)
407do
408 eval "\$f() { :; }"
409done
410unset f
411END
412
413 # include cite() to enable custom keywords
414 typeset -f cite $*
415}
416
Erich Smitha8fcf9f2012-04-27 15:07:04 -0400417: <<EOF
418License: The MIT License
419
420Copyright © 2012 Erich Smith
421
422Permission is hereby granted, free of charge, to any person obtaining a copy of this
423software and associated documentation files (the "Software"), to deal in the Software
424without restriction, including without limitation the rights to use, copy, modify,
425merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
426permit persons to whom the Software is furnished to do so, subject to the following
427conditions:
428
429The above copyright notice and this permission notice shall be included in all copies
430or substantial portions of the Software.
431
432THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
433INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
434PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
435HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
436CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
437OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
438EOF