blob: 8ca66d669d4bc64b04c26bc6644049ab6c96c74d [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
148 # here's a hack I modified from a StackOverflow post:
149 # we loop over the ps listing for the current process ($$), and print the last column (CMD)
150 # stripping any leading hyphens bash sometimes throws in there
151
152 typeset x ans
153 typeset this=$(for x in $(ps -p $$); do ans=$x; done; printf "%s\n" $ans | sed 's/^-*//')
154 typeset shell=$(basename $this) # e.g. /bin/bash => bash
155 case "$shell" in
156 bash)
157 typeset -F | awk '{print $3}'
158 ;;
159 *)
160 # trim everything following '()' in ksh
161 typeset +f | sed 's/().*$//'
162 ;;
163 esac
164}
165
166
167# bootstrap metadata keywords for porcelain functions
168for f in $(composure_keywords)
169do
170 eval "$f() { :; }"
171done
172unset f
173
174
175# 'porcelain' functions
176
177cite ()
178{
179 about creates one or more meta keywords for use in your functions
180 param one or more keywords
181 example '$ cite url username'
182 example '$ url http://somewhere.com'
183 example '$ username alice'
184 group composure
185
186 # this is the storage half of the 'metadata' system:
187 # we create dynamic metadata keywords with function wrappers around
188 # the NOP command, ':'
189
190 # anything following a keyword will get parsed as a positional
191 # parameter, but stay resident in the ENV. As opposed to shell
192 # comments, '#', which do not get parsed and are not available
193 # at runtime.
194
195 # a BIG caveat--your metadata must be roughly parsable: do not use
196 # contractions, and consider single or double quoting if it contains
197 # non-alphanumeric characters
198
199 if [ -z "$1" ]; then
200 printf '%s\n' 'missing parameter(s)'
201 reference cite
202 return
203 fi
204
205 typeset keyword
206 for keyword in $*; do
207 eval "$keyword() { :; }"
208 done
209}
210
211draft ()
212{
213 about wraps command from history into a new function, default is last command
214 param 1: name to give function
215 param 2: optional history line number
216 example '$ ls'
217 example '$ draft list'
218 example '$ draft newfunc 1120 # wraps command at history line 1120 in newfunc()'
219 group composure
220
221 typeset func=$1
222 typeset num=$2
223 typeset cmd
224
225 if [ -z "$func" ]; then
226 printf '%s\n' 'missing parameter(s)'
227 reference draft
228 return
229 fi
230
Erich Smitha385e0f2012-05-11 14:10:39 -0400231 # aliases bind tighter than function names, disallow them
232 if [ -n "$(type -a $func | grep 'is.*alias')" ]; then
233 printf '%s\n' "sorry, $(type -a $func). please choose another name."
234 return
235 fi
236
Erich Smith635c3802012-05-08 23:48:01 -0400237 if [ -z "$num" ]; then
Erich Smitha385e0f2012-05-11 14:10:39 -0400238 # parse last command from fc output
239 # some versions of 'fix command, fc' need corrective lenses...
240 typeset myopic=$(fc -ln -1 | grep draft)
241 typeset lines=1
242 if [ -n "$myopic" ]; then
243 lines=2
244 fi
245 cmd=$(fc -ln -$lines | head -1 | sed 's/^[[:blank:]]*//')
Erich Smith635c3802012-05-08 23:48:01 -0400246 else
247 # parse command from history line number
248 cmd=$(eval "history | grep '^[[:blank:]]*$num' | head -1" | sed 's/^[[:blank:][:digit:]]*//')
249 fi
250 eval "$func() { $cmd; }"
251 typeset file=$(mktemp /tmp/draft.XXXX)
252 typeset -f $func > $file
253 transcribe $func $file draft
254 rm $file 2>/dev/null
255}
256
257glossary ()
258{
259 about displays help summary for all functions, or summary for a group of functions
260 param 1: optional, group name
261 example '$ glossary'
262 example '$ glossary misc'
263 group composure
264
265 typeset targetgroup=${1:-}
266
267 for func in $(typeset_functions); do
Erich Smith635c3802012-05-08 23:48:01 -0400268 if [ -n "$targetgroup" ]; then
269 typeset group="$(typeset -f $func | metafor group)"
270 if [ "$group" != "$targetgroup" ]; then
271 continue # skip non-matching groups, if specified
272 fi
273 fi
Erich Smitha385e0f2012-05-11 14:10:39 -0400274 typeset about="$(typeset -f $func | metafor about)"
Erich Smith635c3802012-05-08 23:48:01 -0400275 letterpress "$about" $func
276 done
277}
278
279metafor ()
280{
281 about prints function metadata associated with keyword
282 param 1: meta keyword
283 example '$ typeset -f glossary | metafor example'
284 group composure
285
286 typeset keyword=$1
287
288 if [ -z "$keyword" ]; then
289 printf '%s\n' 'missing parameter(s)'
290 reference metafor
291 return
292 fi
293
294 # this sed-fu is the retrieval half of the 'metadata' system:
295 # 'grep' for the metadata keyword, and then parse/filter the matching line
296
Erich Smitha385e0f2012-05-11 14:10:39 -0400297 # grep keyword # strip trailing '|"|; # ignore thru keyword and leading '|"
298 sed -n "/$keyword / s/['\";]*$//;s/^[ ]*$keyword ['\"]*\([^([].*\)*$/\1/p"
Erich Smith635c3802012-05-08 23:48:01 -0400299}
300
301reference ()
302{
303 about displays apidoc help for a specific function
304 param 1: function name
305 example '$ reference revise'
306 group composure
307
308 typeset func=$1
309 if [ -z "$func" ]; then
310 printf '%s\n' 'missing parameter(s)'
311 reference reference
312 return
313 fi
314
315 typeset line
316
317 typeset about="$(typeset -f $func | metafor about)"
318 letterpress "$about" $func
319
320 typeset author="$(typeset -f $func | metafor author)"
321 if [ -n "$author" ]; then
322 letterpress "$author" 'author:'
323 fi
324
325 typeset version="$(typeset -f $func | metafor version)"
326 if [ -n "$version" ]; then
327 letterpress "$version" 'version:'
328 fi
329
330 if [ -n "$(typeset -f $func | metafor param)" ]; then
331 printf "parameters:\n"
332 typeset -f $func | metafor param | while read line
333 do
334 letterpress "$line"
335 done
336 fi
337
338 if [ -n "$(typeset -f $func | metafor example)" ]; then
339 printf "examples:\n"
340 typeset -f $func | metafor example | while read line
341 do
342 letterpress "$line"
343 done
344 fi
345}
346
347revise ()
348{
349 about loads function into editor for revision
350 param 1: name of function
351 example '$ revise myfunction'
352 group composure
353
354 typeset func=$1
355 typeset temp=$(mktemp /tmp/revise.XXXX)
356
357 if [ -z "$func" ]; then
358 printf '%s\n' 'missing parameter(s)'
359 reference revise
360 return
361 fi
362
363 # populate tempfile...
364 if [ -f ~/.composure/$func.inc ]; then
365 # ...with contents of latest git revision...
366 cat ~/.composure/$func.inc >> $temp
367 else
368 # ...or from ENV if not previously versioned
369 typeset -f $func >> $temp
370 fi
371
372 if [ -z "$EDITOR" ]
373 then
374 typeset EDITOR=vi
375 fi
376
377 $EDITOR $temp
378 . $temp # source edited file
379
380 transcribe $func $temp revise
381 rm $temp
382}
383
384write ()
385{
386 about writes one or more composed function definitions to stdout
387 param one or more function names
388 example '$ write finddown foo'
389 example '$ write finddown'
390 group composure
391
392 if [ -z "$1" ]; then
393 printf '%s\n' 'missing parameter(s)'
394 reference write
395 return
396 fi
397
398# bootstrap metadata
399cat <<END
400for f in $(composure_keywords)
401do
402 eval "\$f() { :; }"
403done
404unset f
405END
406
407 # include cite() to enable custom keywords
408 typeset -f cite $*
409}
410
Erich Smitha8fcf9f2012-04-27 15:07:04 -0400411: <<EOF
412License: The MIT License
413
414Copyright © 2012 Erich Smith
415
416Permission is hereby granted, free of charge, to any person obtaining a copy of this
417software and associated documentation files (the "Software"), to deal in the Software
418without restriction, including without limitation the rights to use, copy, modify,
419merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
420permit persons to whom the Software is furnished to do so, subject to the following
421conditions:
422
423The above copyright notice and this permission notice shall be included in all copies
424or substantial portions of the Software.
425
426THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
427INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
428PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
429HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
430CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
431OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
432EOF