#+INCLUDE: ./orgheader.org #+TITLE: A Pretty Shell Function * What is it? A pretty shell function is a special case of a *canonical function*, which is produced by using the *declare* shell built-in: : $ declare -f function ... In this exercise, I've dressed it up to handle long lines from piped commands. This paper is part of my [[../index.org][Commonplace Book]]; the [[./aprettyshellfunction.txt][text of this paper]], in [[../10_red/index.org][this chapter]]. ** The Challenge :prettyprint: Long pipe sequences in functions can quickly run over the conventional page width, about a 72-character line length. I'd thought of trying to detect the extra-long lines and reformat on the fly. Rather than focus on automated discovery, I thought it better if the developer decides where to break the long lines. This feature provides the means. What would a resulting re-format look like? + it would have to retain it's execution semantics + it shouldn't be more intrusive than a badly-wrapped line, + it should be much more readable, and to foster that, + it might allow a tab formatting. On this last point, I recall reading shell scripts from my days at Bell Labs, which could have lines of long pipe sequences successively indented by two spaces. Since I've been committed to a shell function format given by the un-adorned: : declare -f ... by itself, one might say it gratuitously reformats, some pain-staking pretty printing laid out by the developer. The idea is to preserve the simple functional interface, and supply underlying functionality to break lines at programmer discretion to produce a pleasing format. Preserving the interface, and sacrificing a small bit of performance fosters the ease of understanding by breaking long pipes into phrases. ** How to Proceed :command: The bash shell has a *command* builtin. This has allowed script writers the means of intercepting a call to the fixed commands and dressing up the results. The *help* builtin facility uses an example with the *ls* command. An ~ls~ function would look like this: #+BEGIN_SRC sh function ls () { ... command ls ... # whatever default arguments you wanted to supply ... } #+END_SRC The inner ~ls~ when prefaced by the *command* builtin says ignore any defined alias and function names, and proceed directly to the command on the users PATH. Thus avoiding recursive calls. In our case, we want to capture the full features of the ~declare -f~ command, and dress up the resulting output. Our function will look something like: #+BEGIN_SRC sh function declare () { : capture the declare -f command, pretty-print : case $1 in -f ) command declare $@ ... # something with the remaining arguments # to achieve the desired pretty-print ;; *) command declare $@ # all other cases behave as before ;; esac } #+END_SRC ** The insight :dots: Look at all the ~...~ lying around. The literary equivalent of "do whatever". Can we turn a command line like this: : a command with lots of stuff before | and after a pipe with maybe | more pipes following Into this: : a command with lots of stuff before | : ... and after a pipe with maybe | : ... ... more pipes following Yes indeed, literally, "dot, dot, dot". What does that function look like? #+BEGIN_EXAMPLE function ... () { : a place-holder, do what follows; : eval $@ } #+END_EXAMPLE So, in the example, : ... after executes ~after~ with its arguments, and : ... ... pipes executes : ... pipes, which ... ** In practice :fold:pipes: I spent little time thinking about writing the code to automatically detect pipes in the command line. It would present these challenges: + What is the shortest command sequence allowed? + the longest? + how to handle multiple pipes on a line and their length? + ... The simple answer, "Let the programmer decide". Should the programmer want a different sized offset, or a single level (un-tabbed) indenting, the above example might be rendered: #+BEGIN_EXAMPLE a command with lots of stuff before | .. and after a pipe with maybe | .. more pipes following #+END_EXAMPLE All that's required is a different dot-dot function, and this is the important part, she must edit the original source to look like this: : a command with lots of stuff before | ... and after a pipe with maybe | ... ... more pipes following or, for the second result: : a command with lots of stuff before | .. and after a pipe with maybe | .. more pipes following ** Now to declare :declare: #+BEGIN_SRC sh $ declare -f declare declare () { : insert ... syntactic sugar into shell functions; : date: 2021-01-03; case $1 in -f) command declare -f ${@:2} | sed ' s/| *\([.][. ]*\) /|\ \1 /g' ;; *) command declare $@ ;; esac } #+END_SRC In the event the function is not called with the -f flag, proceed as if the built-in were called directly, otherwise, handle the dotted commands. + the ~-f ${@:2}~ syntax repeats the -f flag, and passes the subsequent function name arguments ~${@:2}~ to the declare builtin. This returns the canonical (including long lines) functions to the stream editor, which + recognizes the pipe symbol + followed by zero or more spaces + then a single period, + then an optional string containing only periods or spaces, + and a single space The stream editor remembers: \( ... \), which is the string from the first period to just before the last space, replacing the whole thing with + the pipe symbol, + a newline and four spaces, + the remembered string + and finally, a space, this is repeated 'globally', i.e. for each instance of the pattern on the line. ** A test case :declare_test: Note the functions, in particular ~declare_test~: This exercise was necessary, these lines were needed ahead of the command for the ~-f~ argument: #+BEGIN_SRC : test for function names; set -- $(func_args ${@:2}); read_devtty $#, $@; [[ $# -lt 1 ]] && return 1; :; : there are some, proceed; #+END_SRC The need arose when I realized that some uses of ~declare -f~, particularly in the test for function arguments, would fail, since here we have to simulate the behavior before reformatting the function text. *** Utilities #+BEGIN_SRC sh func_args () { : use TYPE to simulate declare -f; : limitation: QUOTED args may not be REFORMATTED; type -a ${@:-/dev/null} 2> /dev/null | awk '$4 ~ /function/ { print $1 }' } ... () { : synctactic sugar for long pipe sequences; : date: 2021-01-03; read_devtty "$@"; eval "$@" } .. () { : another synctactic sugar for long pipe sequences; : date: 2021-01-03; eval "$@" } declare () { : insert ... syntactic sugar into shell functions; : date: 2021-01-03; case $1 in -f) : test for function names; set -- $(func_args ${@:2}); read_devtty $#, $@; [[ $# -lt 1 ]] && return 1; :; : there are some, proceed; command declare -f $@ | fold_pipedots ;; *) command declare $@ ;; esac } declare_test () { : date: 2021-01-03; ignore pushd $HOME/Dropbox/git/applemcg.github.io/reading; local format="\n%s\n==============================================\n"; :; printf $format UTILITIES; declare -f func_args ... .. declare{,_test}; :; printf $format "BEFORE - declare builtin"; command declare -f latestbook readhistory; :; printf $format "AFTER - declare Function"; declare -f latestbook readhistory } #+END_SRC *** Before and After These are best looked at together. Before shows the results of the builtin ~declare~. The programer has inserted the ~...~ where she wants to fold the lines The After view shows the results of installing the ~declare~ *function* and the ~...~ functions. #+BEGIN_SRC sh BEFORE - declare builtin ============================================== latestbook () { : date: 2020-12-05; table_history book_page.rdb | i_timeDate latest | ... jointable -1 book -2 book - author_book.rdb | ... ... column latest page author book | tee $(myname).rdb } readhistory () { : reading history with currentbooks on top; : date: 2020-12-30; : date: 2021-01-03; zcat .hry/book_page.rdb.Z | sorttable -r | select_first book | .. ncolumn delete_time | rdb_iDate | rename i_date latest | .. column latest page book | tiddlylink book | threeColumnTiddly } AFTER - declare Function ============================================== latestbook () { : date: 2020-12-05; table_history book_page.rdb | i_timeDate latest | ... jointable -1 book -2 book - author_book.rdb | ... ... column latest page author book | tee $(myname).rdb } readhistory () { : reading history with currentbooks on top; : date: 2020-12-30; : date: 2021-01-03; zcat .hry/book_page.rdb.Z | sorttable -r | select_first book | .. ncolumn delete_time | rdb_iDate | rename i_date latest | .. column latest page book | tiddlylink book | threeColumnTiddly } #+END_SRC These two functions report on my reading history, where I usr /rdb to keep track of the books I'm reading. Here's a current copy the table /book_page.rdb/ followed by a report generated by this command: #+BEGIN_EXAMPLE page book ---- ---- 360 Life and Fate 150 How to Be an Antiracist 20 It Can't Happen Here 266 Say Nothing: A True Story of Murder and Memory ... 120 The Lake Wobegon Virus 3640 Last Seen Wearing #+END_EXAMPLE > latestbook | sorttable | justify #+BEGIN_EXAMPLE latest page author book ------ ---- ------ ---- 210102 20 Sinclair Lewis It Can't Happen Here 210102 120 Garrison Keillor The Lake Wobegon Virus 210102 150 Ibram X Kendi How to Be an Antiracist 210102 266 Patrick Radden Keefe Say Nothing: A True Story of Murder and Memory ... 210102 360 Vasily Grossman Life and Fate 210102 3640 Colin Dexter Last Seen Wearing #+END_EXAMPLE ** A reflection The whole purpose of this exercise is shown in the difference in the readable layout offered by the ~declare~ *function*. The developer need not ever look at long lines again from pipes too numerous to fit in the printable page. And, from a maintenance standpoint, the developer is free to add or remove any ~...~ functions in the folded layout. At the risk of over emphasis, the developer need only work on the folded copy of the function. I place so much emphasis on this because the canonical copy of the funtion is at the centerpiece of my library maintenance practice. Here's a typical sequence: #+BEGIN_SRC sh $ set -- someFunctionLib $ $EDITOR $1 $ declare -f $(functions $1) | tee $1.new $ diff $1 $1.new # satisfy yourself all is well $ mv $1.new $1 $ backup $1 # see the reference below #+END_SRC This is the most recent chapter in my Commonplace Book. As such it sets the future direction of that work. Today's challenge is the work to place it in that framework. The tasks: + load it to the Web: mcgowans.org/pubs/marty3/commonplace + place it in it's appropriate chapter + set links to the chapter and back to this article ** reference :version: 1. http://mcgowans.org/pubs/marty3/commonplace/software/aprettyshellfunction.html -- this paper, online 1. My [[../book.org][Commonplace Book]] 1. [[./backupfunction.org][The Only Backup You'll Ever Need]] 1. My [[https://applemcg.github.io/shelf/#On_RDB][/rdb reference]] ** COMMENT Commands ln -f aprettyshellfunction.{org,txt}