baf8a1eab4f48d178ac86fd1f024968a9cbee592
[feisty_meow.git] / scripts / core / functions.sh
1 #!/bin/bash
2
3 # This defines some general, useful functions.
4
5 #hmmm: starting to get a bit beefy in here.  perhaps there is a good way to refactor the functions into more specific folders, if they aren't really totally general purpose?
6
7 ##############
8
9 # test whether we've been here before or not.
10 skip_all=
11 type function_sentinel &>/dev/null
12 if [ $? -eq 0 ]; then
13   # there was no error, so we can skip the inits.
14   if [ ! -z "$DEBUG_FEISTY_MEOW" ]; then
15     echo "skipping function definitions, because already defined."
16   fi
17   skip_all=yes
18 else
19   skip_all=
20 fi
21
22 if [ -z "$skip_all" ]; then
23
24   if [ ! -z "$DEBUG_FEISTY_MEOW" ]; then
25     echo "feisty meow function definitions beginning now..."
26   fi
27
28   # a handy little method that can be used for date strings.  it was getting
29   # really tiresome how many different ways the script did the date formatting.
30   function date_stringer() {
31     local sep="$1"; shift
32     if [ -z "$sep" ]; then sep='_'; fi
33     date +"%Y$sep%m$sep%d$sep%H%M$sep%S" | tr -d '/\n/'
34   }
35   
36   # makes a directory of the name specified and then tries to change the
37   # current directory to that directory.
38   function mcd() {
39     if [ ! -d "$1" ]; then mkdir -p "$1"; fi
40     cd "$1"
41   }
42
43   # returns true if the variable is an array.
44   function is_array() {
45     [[ "$(declare -p $1)" =~ "declare -a" ]]
46   }
47
48   # returns true if the name provided is a defined alias.
49   function is_alias() {
50     alias $1 &>/dev/null
51     return $?
52   }
53
54   # makes the status of pipe number N (passed as first parameter) into the
55   # main return value (i.e., the value for $?).  this is super handy to avoid
56   # repeating the awkward looking code below in multiple places.
57   function promote_pipe_return()
58   {
59     ( exit ${PIPESTATUS[$1]} )
60   }
61
62   ##############
63
64   function fm_username()
65   {
66     # see if we can get the user name from the login name.  oddly this sometimes doesn't work.
67     local custom_user="$(logname 2>/dev/null)"
68     if [ -z "$custom_user" ]; then
69       # try the normal unix user variable.
70       custom_user="$USER"
71     fi
72     if [ -z "$custom_user" ]; then
73       # try the windows user variable.
74       custom_user="$USERNAME"
75     fi
76     echo "$custom_user"
77   }
78
79   ##############
80
81   # displays the value of a variable in bash friendly format.
82   function var() {
83     HOLDIFS="$IFS"
84     IFS=""
85     while true; do
86       local varname="$1"; shift
87       if [ -z "$varname" ]; then
88         break
89       fi
90
91       if is_alias "$varname"; then
92 #echo found $varname is alias
93         local tmpfile="$(mktemp $TMP/aliasout.XXXXXX)"
94         alias $varname | sed -e 's/.*=//' >$tmpfile
95         echo "alias $varname=$(cat $tmpfile)"
96         \rm $tmpfile
97       elif [ -z "${!varname}" ]; then
98         echo "$varname undefined"
99       else
100         if is_array "$varname"; then
101 #echo found $varname is array var 
102           local temparray
103           eval temparray="(\${$varname[@]})"
104           echo "$varname=(${temparray[@]})"
105 #hmmm: would be nice to print above with elements enclosed in quotes, so that we can properly
106 # see ones that have spaces in them.
107         else
108 #echo found $varname is simple
109           echo "$varname=${!varname}"
110         fi
111       fi
112     done | sort
113     IFS="$HOLDIFS"
114   }
115
116   ##############
117
118   # when passed a list of things, this will return the unique items from that list as an echo.
119   function uniquify()
120   {
121     # do the uniquification: split the space separated items into separate lines, then
122     # sort the list, then run the uniq tool on the list.  results will be packed back onto
123     # one line when invoked like: local fredlist="$(uniquify a b c e d a e f a e d b)"
124     echo $* | tr ' ' '\n' | sort | uniq
125   }
126
127   # sets the variable in parameter 1 to the value in parameter 2, but only if
128   # that variable was undefined.
129   function set_var_if_undefined()
130   {
131     local var_name="$1"; shift
132     local var_value="$1"; shift
133     if [ -z "${!var_name}" ]; then
134       eval export $var_name="$var_value"
135     fi
136   }
137
138   ##############
139
140   function success_sound()
141   {
142     if [ ! -z "$CLAM_FINISH_SOUND" ]; then
143       bash $FEISTY_MEOW_SCRIPTS/multimedia/sound_play.sh "$CLAM_FINISH_SOUND"
144     fi
145   }
146
147   function error_sound()
148   {
149     if [ ! -z "$CLAM_ERROR_SOUND" ]; then
150       bash $FEISTY_MEOW_SCRIPTS/multimedia/sound_play.sh "$CLAM_ERROR_SOUND"
151     fi
152   }
153
154   ##############
155
156   # echoes the maximum number of columns that the terminal supports.  usually
157   # anything you print to the terminal with length less than (but not equal to)
158   # maxcols will never wrap.
159   function get_maxcols()
160   {
161     # calculate the number of columsn in the terminal.
162     local cols=$(stty size | awk '{print $2}')
163     echo $cols
164   }
165
166   ##############
167
168   # checks the result of the last command that was run, and if that failed,
169   # then this complains and exits from bash.  the function parameters are
170   # used as the message to print as a complaint.
171   function exit_on_error()
172   {
173     if [ $? -ne 0 ]; then
174       echo -e "\n\nan important action failed and this script will stop:\n\n$*\n\n*** Exiting script..."
175       error_sound
176       exit 1
177     fi
178   }
179
180   # like exit_on_error, but will keep going after complaining.
181   function continue_on_error()
182   {
183     if [ $? -ne 0 ]; then
184       echo -e "\n\na problem occurred, but we can continue:\n\n$*\n\n=> Continuing script..."
185       error_sound
186     fi
187   }
188
189   ##############
190
191   # accepts any number of arguments and outputs them to the feisty meow event log.
192   function log_feisty_meow_event()
193   {
194     echo -e "$(date_stringer) -- ${USER}@$(hostname): $*" >> "$FEISTY_MEOW_EVENT_LOG"
195   }
196
197   ##############
198
199   # wraps secure shell with some parameters we like, most importantly to enable X forwarding.
200   function ssh()
201   {
202     local args=($@)
203     # we remember the old terminal title, then force the TERM variable to a more generic
204     # version for the other side (just 'linux'); we don't want the remote side still
205     # thinking it's running xterm.
206     save_terminal_title
207
208 #hmmm: why were we doing this?  it scorches the user's logged in session, leaving it without proper terminal handling.
209 #    # we save the value of TERM; we don't want to leave the user's terminal
210 #    # brain dead once we come back from this function.
211 #    local oldterm="$TERM"
212 #    export TERM=linux
213
214     /usr/bin/ssh -Y -C "${args[@]}"
215
216 #    # restore the terminal variable also.
217 #    TERM="$oldterm"
218
219     restore_terminal_title
220     if [ ! -z "$DEBUG_FEISTY_MEOW" ]; then
221       echo TERM title restored to prior value
222     fi
223   }
224
225   ##############
226
227   # locates a process given a search pattern to match in the process list.
228   # supports a single command line flag style parameter of "-u USERNAME";
229   # if the -u flag is found, a username is expected afterwards, and only the
230   # processes of that user are considered.
231   function psfind() {
232     local -a patterns=("${@}")
233 #echo ====
234 #echo patterns list is: "${patterns[@]}"
235 #echo ====
236
237     local user_flag
238     if [ "${patterns[0]}" == "-u" ]; then
239       user_flag="-u ${patterns[1]}" 
240 #echo "found a -u parm and user=${patterns[1]}" 
241       # void the two elements with that user flag so we don't use them as patterns.
242       unset patterns[0] patterns[1]=
243     else
244       # select all users.
245       user_flag="-e"
246     fi
247
248     local PID_DUMP="$(mktemp "$TMP/zz_pidlist.XXXXXX")"
249     local -a PIDS_SOUGHT
250
251     if [ "$OS" == "Windows_NT" ]; then
252       # gets cygwin's (god awful) ps to show windoze processes also.
253       local EXTRA_DOZER_FLAGS="-W"
254       # pattern to use for peeling off the process numbers.
255       local pid_finder_pattern='s/ *\([0-9][0-9]*\) *.*$/\1/p'
256
257     else
258       # flags which clean up the process listing output on unixes.
259       # apparently cygwin doesn't count as a type of unix, because their
260       # crummy specialized ps command doesn't support normal ps flags.
261       local EXTRA_UNIX_FLAGS="-o pid,args"
262       # pattern to use for peeling off the process numbers.
263       local pid_finder_pattern='s/^[[:space:]]*\([0-9][0-9]*\).*$/\1/p'
264     fi
265
266     /bin/ps $EXTRA_DOZER_FLAGS $EXTRA_UNIX_FLAGS $user_flag | tail -n +2 >$PID_DUMP
267 #echo ====
268 #echo got all this stuff in the pid dump file:
269 #cat $PID_DUMP
270 #echo ====
271
272     # search for the pattern the user wants to find, and just pluck the process
273     # ids out of the results.
274     local i
275     for i in "${patterns[@]}"; do
276       PIDS_SOUGHT+=($(cat $PID_DUMP \
277         | grep -i "$i" \
278         | sed -n -e "$pid_finder_pattern"))
279     done
280 #echo ====
281 #echo pids sought list became:
282 #echo "${PIDS_SOUGHT[@]}"
283 #echo ====
284
285     if [ ${#PIDS_SOUGHT[*]} -ne 0 ]; then
286       local PIDS_SOUGHT2=$(printf -- '%s\n' ${PIDS_SOUGHT[@]} | sort | uniq)
287       PIDS_SOUGHT=()
288       PIDS_SOUGHT=${PIDS_SOUGHT2[*]}
289       echo ${PIDS_SOUGHT[*]}
290     fi
291     /bin/rm $PID_DUMP
292   }
293   
294   # finds all processes matching the pattern specified and shows their full
295   # process listing (whereas psfind just lists process ids).
296   function psa() {
297     if [ -z "$1" ]; then
298       echo "psa finds processes by pattern, but there was no pattern on the command line."
299       return 1
300     fi
301     local -a patterns=("${@}")
302     p=$(psfind "${patterns[@]}")
303     if [ -z "$p" ]; then
304       # no matches.
305       return 0
306     fi
307
308     if [ "${patterns[0]}" == "-u" ]; then
309       # void the two elements with that user flag so we don't use them as patterns.
310       unset patterns[0] patterns[1]=
311     fi
312
313     echo ""
314     echo "Processes matching ${patterns[@]}..."
315     echo ""
316     if [ -n "$IS_DARWIN" ]; then
317       unset fuzil_sentinel
318       for i in $p; do
319         # only print the header the first time.
320         if [ -z "$fuzil_sentinel" ]; then
321           ps $i -w -u
322         else
323           ps $i -w -u | sed -e '1d'
324         fi
325         fuzil_sentinel=true
326       done
327     else 
328       # cases besides mac os x's darwin.
329       if [ "$OS" == "Windows_NT" ]; then
330         # special case for windows.
331         ps | head -1
332         for curr in $p; do
333           ps -W -p $curr | tail -n +2
334         done
335       else
336         # normal OSes can handle a nice simple query.
337         ps wu $p
338       fi
339     fi
340   }
341   
342   ##############
343
344 #hmmm: holy crowbars, this is an old one.  do we ever still have any need of it?
345   # an unfortunately similarly named function to the above 'ps' as in process
346   # methods, but this 'ps' stands for postscript.  this takes a postscript file
347   # and converts it into pcl3 printer language and then ships it to the printer.
348   # this mostly makes sense for an environment where one's default printer is
349   # pcl.  if the input postscript causes ghostscript to bomb out, there has been
350   # some good success running ps2ps on the input file and using the cleaned
351   # postscript file for printing.
352   function ps2pcl2lpr() {
353     for $i in $*; do
354       gs -sDEVICE=pcl3 -sOutputFile=- -sPAPERSIZE=letter "$i" | lpr -l 
355     done
356   }
357   
358   function screen() {
359     save_terminal_title
360 #hmmm: ugly absolute path here.
361     /usr/bin/screen $*
362     restore_terminal_title
363   }
364   
365   # switches from a /X/path form to an X:/ form.  this also processes cygwin paths.
366   function unix_to_dos_path() {
367     # we usually remove dos slashes in favor of forward slashes.
368     local DOSSYHOME
369     if [[ ! "$OS" =~ ^[Ww][iI][nN] ]]; then
370       # fake this value for non-windows (non-cygwin) platforms.
371       DOSSYHOME="$HOME"
372     else
373       # for cygwin, we must replace the /home/X path with an absolute one, since cygwin
374       # insists on the /home form instead of /c/cygwin/home being possible.  this is
375       # super frustrating and nightmarish.
376       DOSSYHOME="$(cygpath -am "$HOME")"
377     fi
378
379     if [ ! -z "$SERIOUS_SLASH_TREATMENT" ]; then
380       # unless this flag is set, in which case we force dos slashes.
381       echo "$1" | sed -e "s?^$HOME?$DOSSYHOME?g" | sed -e 's/\\/\//g' | sed -e 's/\/cygdrive//' | sed -e 's/\/\([a-zA-Z]\)\/\(.*\)/\1:\/\2/' | sed -e 's/\//\\/g'
382     else
383       echo "$1" | sed -e "s?^$HOME?$DOSSYHOME?g" | sed -e 's/\\/\//g' | sed -e 's/\/cygdrive//' | sed -e 's/\/\([a-zA-Z]\)\/\(.*\)/\1:\/\2/'
384     fi
385   }
386   
387 #  # switches from an X:/ form to a /cygdrive/X/path form.  this is only useful
388 #  # for the cygwin environment currently.
389 #  function dos_to_unix_path() {
390 #    # we always remove dos slashes in favor of forward slashes.
391 ##old:    echo "$1" | sed -e 's/\\/\//g' | sed -e 's/\([a-zA-Z]\):\/\(.*\)/\/\1\/\2/'
392 #         echo "$1" | sed -e 's/\\/\//g' | sed -e 's/\([a-zA-Z]\):\/\(.*\)/\/cygdrive\/\1\/\2/'
393 #  }
394
395   # returns a successful value (0) if this system is debian or ubuntu.
396   function debian_like() {
397     # decide if we think this is debian or ubuntu or a variant.
398     DEBIAN_LIKE=$(if [ ! -z "$(grep -i debian /etc/issue)" \
399         -o ! -z "$(grep -i ubuntu /etc/issue)" ]; then echo 1; else echo 0; fi)
400     if [ $DEBIAN_LIKE -eq 1 ]; then
401       # success; this is debianish.
402       return 0
403     else
404       # this seems like some other OS.
405       return 1
406     fi
407   }
408   
409   # this function wraps the normal sudo by ensuring we replace the terminal
410   # label before we launch what they're passing to sudo.  we also ensure that
411   # the feisty meow environment is recreated; normal subshells don't need
412   # this, but when switching identity with sudo, it seems important.  yet,
413   # we also don't want to hose up their normal sudo actions, such as passing
414   # along the current environment, should the user choose.
415   function sudo() {
416     save_terminal_title
417     # hoist our X authorization info in case environment is passed along;
418     # this can allow root to use our display to show Xorg windows.
419     if [ ! -z "$DISPLAY" ]; then
420       export IMPORTED_XAUTH="$(xauth list $DISPLAY | head -n 1 | awk '{print $3}')"
421     fi
422     # prep a simple command string here, rather than messing with arguments
423     # in the already complicated command below.  i was seeing some really
424     # screwy behavior trying to expand $@ when embedded for the bash -c flag,
425     # but making the variable ahead of time gets rid of that.
426     cmd="/usr/bin/sudo ""$@"
427
428     # omit any variables that are either wrong for a different user or used
429     # to shield the feisty meow scripts from reconfiguring.  when we do the
430     # sudo, we want a fresh start for feisty meow at least.
431     # our approach to launching sudo is further complicated by our sentinel
432     # alias, which normally is passed to any subshells (to prevent recreating
433     # aliases).  we turn off the expand_aliases shell option to avoid passing
434     # the sentinel, which ensures aliases do get recreated for the new user.
435     BUILD_VARS_LOADED= \
436       CORE_VARIABLES_LOADED= \
437       FEISTY_MEOW_SCRIPTS_LOADED= \
438       function_sentinel= \
439       MAIL= \
440       HOME= \
441     bash +O expand_aliases -c "$cmd"
442     retval=$?
443     restore_terminal_title
444     return $retval
445   }
446   
447   # trashes the .#blah files that cvs and subversion leave behind when finding conflicts.
448   # this kind of assumes you've already checked them for any salient facts.
449   function clean_cvs_junk() {
450     for i in $*; do
451       find $i -follow -type f -iname ".#*" -exec perl $FEISTY_MEOW_SCRIPTS/files/safedel.pl {} ";" 
452     done
453   }
454
455   # overlay for nechung binary so that we can complain less grossly about it when it's missing.
456   function nechung() {
457     local wheres_nechung=$(which nechung 2>/dev/null)
458     if [ -z "$wheres_nechung" ]; then
459       echo "The nechung oracle program cannot be found.  You may want to consider"
460       echo "rebuilding the feisty meow applications with this command:"
461       echo "bash $FEISTY_MEOW_SCRIPTS/generator/produce_feisty_meow.sh"
462       echo
463     else
464       $wheres_nechung
465     fi
466   }
467   
468   # recreates all the generated files that the feisty meow scripts use.
469   function regenerate() {
470     # do the bootstrapping process again.
471     save_terminal_title
472     echo "regenerating feisty meow script environment."
473     bash $FEISTY_MEOW_SCRIPTS/core/reconfigure_feisty_meow.sh
474     echo
475     # force a full reload by turning off sentinel variables and methods.
476     unset -v CORE_VARIABLES_LOADED FEISTY_MEOW_LOADING_DOCK USER_CUSTOMIZATIONS_LOADED
477     unalias CORE_ALIASES_LOADED &>/dev/null
478     unset -f function_sentinel 
479     # reload feisty meow environment in current shell.
480     log_feisty_meow_event "reloading the feisty meow scripts for $USER in current shell."
481     source "$FEISTY_MEOW_SCRIPTS/core/launch_feisty_meow.sh"
482     # run nechung oracle to give user a new fortune.
483     nechung
484     restore_terminal_title
485   }
486
487   # merges a set of custom scripts into the feisty meow environment.  can be
488   # passed a name to use as the custom scripts source folder (found on path
489   # $FEISTY_MEOW_SCRIPTS/customize/{name}), or it will try to guess the name
490   # by using the login name.
491   function recustomize()
492   {
493     local custom_user="$1"; shift
494     if [ -z "$custom_user" ]; then
495       # default to login name if there was no name provided.
496       custom_user="$(fm_username)"
497         # we do intend to use the login name here to get the login name and to ignore
498         # if the user has sudo root access; we don't want to provide a custom
499         # profile for root.
500     fi
501     # chop off any email address style formatting to leave just the name.
502     custom_user="$(echo "$custom_user" | cut -f1 -d'@')"
503
504     save_terminal_title
505
506     if [ ! -d "$FEISTY_MEOW_SCRIPTS/customize/$custom_user" ]; then
507       echo -e "the customization folder for '$custom_user' is missing:
508
509     $FEISTY_MEOW_SCRIPTS/customize/$custom_user
510
511 we will skip recustomization, but these other customizations are available:
512 "
513       # a little tr and sed magic to fix the carriage returns into commas.
514       local line="$(find $FEISTY_MEOW_SCRIPTS/customize -mindepth 1 -maxdepth 1 -type d -exec basename {} ';' | tr '\n' '&' | sed 's/&/, /g' | sed -e 's/, $//')"
515         # make the line feeds and carriage returns manageable with tr.
516         # convert the ampersand, our weird replacement for EOL, with a comma + space in sed.
517         # last touch with sed removes the last comma.
518       echo "    $line"
519       return 1
520     fi
521
522     # recreate the feisty meow loading dock.
523     regenerate >/dev/null
524
525     # jump into the loading dock and make our custom link.
526     pushd "$FEISTY_MEOW_LOADING_DOCK" &>/dev/null
527     if [ -h custom ]; then
528       # there's an existing link, so remove it.
529       \rm custom
530     fi
531     # make sure we cleaned up the area before we re-link.
532     if [ -h custom -o -d custom -o -f custom ]; then
533       echo "
534 Due to an over-abundance of caution, we are not going to remove an unexpected
535 'custom' object found in the file system.  This object is located in the
536 feisty meow loading dock here: $(pwd)
537 And here is a description of the rogue 'custom' object:
538 "
539       ls -al custom
540       echo "
541 If you are pretty sure that this is just a remnant of an older approach in
542 feisty meow, where we copied the custom directory rather than linking it
543 (and it most likely is just such a bit of cruft of that nature), then please
544 remove that old remnant 'custom' item, for example by saying:
545   /bin/rm -rf \"custom\" ; popd
546 Sorry for the interruption, but we want to make sure this removal wasn't
547 automatic if there is even a small amount of doubt about the issue."
548       return 1
549     fi
550
551     # create the custom folder as a link to the customizations.
552     ln -s "$FEISTY_MEOW_SCRIPTS/customize/$custom_user" custom
553
554     popd &>/dev/null
555
556     # now take into account all the customizations by regenerating the feisty meow environment.
557     regenerate
558
559     restore_terminal_title
560   }
561
562   # generates a random password where the first parameter is the number of characters
563   # in the password (default 20) and the second parameter specifies whether to use
564   # special characters (1) or not (0).
565   # found function at http://legroom.net/2010/05/06/bash-random-password-generator
566   function random_password()
567   {
568     [ "$2" == "0" ] && CHAR="[:alnum:]" || CHAR="[:graph:]"
569     cat /dev/urandom | tr -cd "$CHAR" | head -c ${1:-32}
570     echo
571   }
572
573   # a wrapper for the which command that finds items on the path.  some OSes
574   # do not provide which, so we want to not be spewing errors when that
575   # happens.
576   function whichable()
577   {
578     to_find="$1"; shift
579     which which &>/dev/null
580     if [ $? -ne 0 ]; then
581       # there is no which command here.  we produce nothing due to this.
582       echo
583     fi
584     echo $(which $to_find)
585   }
586
587   function add_cygwin_drive_mounts() {
588     for i in c d e f g h q z ; do
589 #hmmm: improve this by not adding the link if already there, or if the drive is not valid.
590       ln -s /cygdrive/$i $i
591     done
592   }
593
594   ############################
595
596   # takes a file to modify, and then it will replace any occurrences of the
597   # pattern provided as the second parameter with the text in the third
598   # parameter.
599   function replace_pattern_in_file()
600   {
601     local file="$1"; shift
602     local pattern="$1"; shift
603     local replacement="$1"; shift
604     if [ -z "$file" -o -z "$pattern" -o -z "$replacement" ]; then
605       echo "replace_pattern_in_file: needs a filename, a pattern to replace, and the"
606       echo "text to replace that pattern with."
607       return 1
608     fi
609     sed -i -e "s%$pattern%$replacement%g" "$file"
610   }
611
612   # similar to replace_pattern_in_file, but also will add the new value
613   # when the old one did not already exist in the file.
614   function replace_if_exists_or_add()
615   {
616     local file="$1"; shift
617     local phrase="$1"; shift
618     local replacement="$1"; shift
619     if [ -z "$file" -o ! -f "$file" -o -z "$phrase" -o -z "$replacement" ]; then
620       echo "replace_if_exists_or_add: needs a filename, a phrase to replace, and the"
621       echo "text to replace that phrase with."
622       return 1
623     fi
624     grep "$phrase" "$file" >/dev/null
625     # replace if the phrase is there, otherwise add it.
626     if [ $? -eq 0 ]; then
627       replace_pattern_in_file "$file" "$phrase" "$replacement"
628     else
629       # this had better be the complete line.
630       echo "$replacement" >>"$file"
631     fi
632   }
633
634   ############################
635
636   # finds a variable (first parameter) in a particular property file
637   # (second parameter).  the expected format for the file is:
638   # varX=valueX
639   function seek_variable()
640   {
641     local find_var="$1"; shift
642     local file="$1"; shift
643     if [ -z "$find_var" -o -z "$file" -o ! -f "$file" ]; then
644       echo -e "seek_variable: needs two parameters, firstly a variable name, and\nsecondly a file where the variable's value will be sought." 1>&2
645       return 1
646     fi
647   
648     while read line; do
649       if [ ${#line} -eq 0 ]; then continue; fi
650       # split the line into the variable name and value.
651       IFS='=' read -a assignment <<< "$line"
652       local var="${assignment[0]}"
653       local value="${assignment[1]}"
654       if [ "${value:0:1}" == '"' ]; then
655         # assume the entry was in quotes and remove them.
656         value="${value:1:$((${#value} - 2))}"
657       fi
658       if [ "$find_var" == "$var" ]; then
659         echo "$value"
660       fi
661     done < "$file"
662   }
663   
664   # finds a variable (first parameter) in a particular XML format file
665   # (second parameter).  the expected format for the file is:
666   # ... name="varX" value="valueX" ...
667   function seek_variable_in_xml()
668   {
669     local find_var="$1"; shift
670     local file="$1"; shift
671     if [ -z "$find_var" -o -z "$file" -o ! -f "$file" ]; then
672       echo "seek_variable_in_xml: needs two parameters, firstly a variable name, and"
673       echo "secondly an XML file where the variable's value will be sought."
674       return 1
675     fi
676   
677     while read line; do
678       if [ ${#line} -eq 0 ]; then continue; fi
679       # process the line to make it more conventional looking.
680       line="$(echo "$line" | sed -e 's/.*name="\([^"]*\)" value="\([^"]*\)"/\1=\2/')"
681       # split the line into the variable name and value.
682       IFS='=' read -a assignment <<< "$line"
683       local var="${assignment[0]}"
684       local value="${assignment[1]}"
685       if [ "${value:0:1}" == '"' ]; then
686         # assume the entry was in quotes and remove them.
687         value="${value:1:$((${#value} - 2))}"
688       fi
689       if [ "$find_var" == "$var" ]; then
690         echo "$value"
691       fi
692     done < "$file"
693   }
694   
695   ############################
696
697   # goes to a particular directory passed as parameter 1, and then removes all
698   # the parameters after that from that directory.
699   function push_whack_pop()
700   {
701     local dir="$1"; shift
702     pushd "$dir" &>/dev/null
703     if [ $? -ne 0 ]; then echo failed to enter dir--quitting.; fi
704     rm -rf $* &>/dev/null
705     if [ $? -ne 0 ]; then echo received a failure code when removing.; fi
706     popd &>/dev/null
707   }
708
709   ##############
710
711 # new breed of definer functions goes here.  still in progress.
712
713   # defines an alias and remembers that this is a new or modified definition.
714   # if the feisty meow codebase is unloaded, then so are all the aliases that
715   # were defined.
716   function define_yeti_alias()
717   {
718 # if alias exists already, save old value for restore,
719 # otherwise save null value for restore,
720 # have to handle unaliasing if there was no prior value of one
721 # we newly defined.
722 # add alias name to a list of feisty defined aliases.
723
724 #hmmm: first implem, just do the alias and get that working...
725 alias "${@}"
726
727
728 return 0
729   }
730
731   ##############
732
733 #hmmm: this points to an extended functions file being needed; not all of these are core.
734
735   # displays documentation in "md" formatted files.
736   function show_md()
737   {
738     local file="$1"; shift
739     pandoc "$file" | lynx -stdin
740   }
741
742   ##############
743
744   # just shows a separator line for an 80 column console, or uses the first
745   # parameter as the number of columns to expect.
746   function separator()
747   {
748     count=$1; shift
749     if [ -z "$count" ]; then
750       count=79
751     fi
752     echo
753     local i
754     for ((i=0; i < $count - 1; i++)); do
755       echo -n "="
756     done
757     echo
758     echo
759   }
760   # alias for separator.
761   function sep()
762   {
763     separator $*
764   }
765
766   ##############
767
768   # count the number of sub-directories in a directory and echo the result.
769   function count_directories()
770   {
771     local subbydir="$1"; shift
772     numdirs="$(find "$subbydir" -mindepth 1 -maxdepth 1 -type d | wc -l)"
773     echo $numdirs
774   }
775
776   # takes a string and capitalizes just the first character.  any capital letters in the remainder of
777   # the string are made lower case.  the processed string is returned by an echo.
778   function capitalize_first_char()
779   {
780     local to_dromedary="$1"; shift
781     to_dromedary="$(tr '[:lower:]' '[:upper:]' <<< ${to_dromedary:0:1})$(tr '[:upper:]' '[:lower:]' <<< ${to_dromedary:1})"
782     echo "$to_dromedary"
783   }
784
785   # given a source path and a target path, this will make a symbolic link from
786   # the source to the destination, but only if the source actually exists.
787   function make_safe_link()
788   {
789     local src="$1"; shift
790     local target="$1"; shift
791   
792     if [ -d "$src" ]; then
793       ln -s "$src" "$target"
794       exit_on_error "Creating symlink from '$src' to '$target'"
795     fi
796     echo "Created symlink from '$src' to '$target'."
797   }
798
799   # pretty prints the json files provided as parameters.
800   function clean_json()
801   {
802     if [ -z "$*" ]; then return; fi
803     local show_list=()
804     while true; do
805       local file="$1"; shift
806       if [ -z "$file" ]; then break; fi
807       if [ ! -f "$file" ]; then "echo File '$file' does not exist."; continue; fi
808       temp_out="$TMP/$file.view"
809       cat "$file" | python -m json.tool > "$temp_out"
810       show_list+=($temp_out)
811       continue_on_error "pretty printing '$file'"
812     done
813     filedump "${show_list[@]}"
814     rm "${show_list[@]}"
815   }
816
817   function json_text()
818   {
819     # only print our special headers or text fields.
820     local CR=$'\r'
821     local LF=$'\n'
822     clean_json $* |
823         grep -i "\"text\":\|^=.*" | 
824         sed -e "s/\\\\r/$CR/g" -e "s/\\\\n/\\$LF/g"
825   }
826
827   ##############
828
829   # echoes the machine's hostname.  can be used like so:
830   #   local my_host=$(get_hostname)
831   function get_hostname()
832   {
833     # there used to be more variation in how to do this, but adopting mingw
834     # and cygwin tools really helped out.
835     local this_host=unknown
836     if [ "$OS" == "Windows_NT" ]; then
837       this_host=$(hostname)
838     elif [ ! -z "$(echo $MACHTYPE | grep apple)" ]; then
839       this_host=$(hostname)
840     elif [ ! -z "$(echo $MACHTYPE | grep suse)" ]; then
841       this_host=$(hostname --long)
842     elif [ -x "$(which hostname 2>/dev/null)" ]; then
843       this_host=$(hostname)
844     fi
845     echo "$this_host"
846   }
847
848   # makes sure that the provided "folder" is a directory and is writable.
849   function test_writeable()
850   {
851     local folder="$1"; shift
852     if [ ! -d "$folder" -o ! -w "$folder" ]; then return 1; fi
853     return 0
854   }
855
856   ##############
857
858   # given a filename and a string to seek and a number of lines, then this
859   # function will remove the first occurrence of a line in the file that
860   # matches the string, and it will also axe the next N lines as specified.
861   function create_chomped_copy_of_file()
862   {
863     local filename="$1"; shift
864     local seeker="$1"; shift
865     local numlines=$1; shift
866
867 #echo into create_chomped_copy...
868 #var filename seeker numlines 
869
870     # make a backup first, oy.
871     \cp -f "$filename" "/tmp/$(basename ${filename}).bkup-${RANDOM}" 
872     exit_on_error "backing up file: $filename"
873
874     # make a temp file to write to before we move file into place in bind.
875     local new_version="/tmp/$(basename ${filename}).bkup-${RANDOM}" 
876     \rm -f "$new_version"
877     exit_on_error "cleaning out new version of file from: $new_version"
878
879     local line
880     local skip_count=0
881     local found_any=
882     while read line; do
883       # don't bother looking at the lines if we're already in skip mode.
884       if [[ $skip_count == 0 ]]; then
885         # find the string they're seeking.
886         if [[ ! "$line" =~ .*${seeker}.* ]]; then
887           # no match.
888           echo "$line" >> "$new_version"
889         else
890           # a match!  start skipping.  we will delete this line and the next N lines.
891           ((skip_count++))
892 #echo first skip count is now $skip_count
893           found_any=yes
894         fi
895       else
896         # we're already skipping.  let's keep going until we hit the limit.
897         ((skip_count++))
898 #echo ongoing skip count is now $skip_count
899         if (( $skip_count > $numlines )); then
900           echo "Done skipping, and back to writing output file."
901           skip_count=0
902         fi
903       fi
904     done < "$filename"
905
906 #echo file we created looks like this:
907 #cat "$new_version"
908
909     if [ ! -z "$found_any" ]; then
910       # put the file back into place under the original name.
911       \mv "$new_version" "$filename"
912       exit_on_error "moving the new version into place in: $filename"
913     else
914       # cannot always be considered an error, but we can at least gripe.
915       echo "Did not find any matches for seeker '$seeker' in file: $filename"
916     fi
917   }
918
919   ##############
920
921   # space 'em all: fixes naming for all of the files of the appropriate types
922   # in the directories specified.
923   function spacemall() {
924     local -a dirs=("${@}")
925     if [ ${#dirs[@]} -eq 0 ]; then
926       dirs=(.)
927     fi
928
929     local charnfile="$(mktemp $TMP/zz_charn.XXXXXX)"
930     find "${dirs[@]}" -follow -maxdepth 1 -mindepth 1 -type f | \
931         grep -i \
932 "doc\|docx\|eml\|html\|jpeg\|jpg\|m4a\|mov\|mp3\|ods\|odt\|pdf\|png\|ppt\|pptx\|txt\|vsd\|vsdx\|xls\|xlsx\|zip" | \
933         sed -e 's/^/"/' | sed -e 's/$/"/' | \
934         xargs bash "$FEISTY_MEOW_SCRIPTS/files/spacem.sh"
935     # drop the temp file now that we're done.
936     rm "$charnfile"
937   }
938
939   ##############
940
941   # site avenger aliases
942   function switchto()
943   {
944     THISDIR="$FEISTY_MEOW_SCRIPTS/site_avenger"
945     source "$FEISTY_MEOW_SCRIPTS/site_avenger/shared_site_mgr.sh"
946     switch_to "$1"
947   }
948
949   ##############
950
951   # you have hit the borderline functional zone...
952
953 #hmmm: not really doing anything yet; ubuntu seems to have changed from pulseaudio in 17.04?
954   # restarts the sound driver.
955   function fix_sound_driver() {
956     # stop bash complaining about blank function body.
957     local nothing=
958 #if alsa something
959 #    sudo service alsasound restart
960 #elif pulse something
961 #    sudo pulseaudio -k
962 #    sudo pulseaudio -D
963 #else
964 #    something else...?
965 #fi
966
967   }
968
969   # ...and here's the end of the borderline functional zone.
970
971   ##############
972
973   # NOTE: no more function definitions are allowed after this point.
974
975   function function_sentinel()
976   {
977     return 0; 
978   }
979   
980   if [ ! -z "$DEBUG_FEISTY_MEOW" ]; then echo "feisty meow function definitions done."; fi
981
982   ##############
983
984   # test code for set_var_if_undefined.
985   run_test=0
986   if [ $run_test != 0 ]; then
987     echo running tests on set_var_if_undefined.
988     flagrant=petunia
989     set_var_if_undefined flagrant forknordle
990     exit_on_error "testing if defined variable would be whacked"
991     if [ $flagrant != petunia ]; then
992       echo set_var_if_undefined failed to leave the test variable alone
993       exit 1
994     fi
995     unset bobblehead_stomper
996     set_var_if_undefined bobblehead_stomper endurance
997     if [ $bobblehead_stomper != endurance ]; then
998       echo set_var_if_undefined failed to set a variable that was not defined yet
999       exit 1
1000     fi
1001   fi
1002
1003 fi
1004