3 # Set a version string for this script.
4 scriptversion
=2014-01-07.03
; # UTC
6 # A portable, pluggable option parser for Bourne shell.
7 # Written by Gary V. Vaughan, 2010
9 # Copyright (C) 2010-2014, 2017 Free Software Foundation, Inc.
10 # This is free software; see the source for copying conditions. There is NO
11 # warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 # Please report bugs or propose patches to gary@gnu.org.
33 # This file is a library for parsing options in your shell scripts along
34 # with assorted other useful supporting features that you can make use
37 # For the simplest scripts you might need only:
40 # . relative/path/to/funclib.sh
41 # . relative/path/to/options-parser
43 # func_options ${1+"$@"}
44 # eval set dummy "$func_options_result"; shift
45 # ...rest of your script...
47 # In order for the '--version' option to work, you will need to have a
48 # suitably formatted comment like the one at the top of this file
49 # starting with '# Written by ' and ending with '# warranty; '.
51 # For '-h' and '--help' to work, you will also need a one line
52 # description of your script's purpose in a comment directly above the
53 # '# Written by ' line, like the one at the top of this file.
55 # The default options also support '--debug', which will turn on shell
56 # execution tracing (see the comment above debug_cmd below for another
57 # use), and '--verbose' and the func_verbose function to allow your script
58 # to display verbose messages only when your user has specified
61 # After sourcing this file, you can plug processing for additional
62 # options by amending the variables from the 'Configuration' section
63 # below, and following the instructions in the 'Option parsing'
64 # section further down.
70 # You should override these variables in your script after sourcing this
71 # file so that they reflect the customisations you have added to the
74 # The usage line for option parsing errors and the start of '-h' and
75 # '--help' output messages. You can embed shell variables for delayed
76 # expansion at the time the message is displayed, but you will need to
77 # quote other shell meta-characters carefully to prevent them being
78 # expanded when the contents are evaled.
79 usage
='$progpath [OPTION]...'
81 # Short help message in response to '-h' and '--help'. Add to this or
82 # override it after sourcing this library to reflect the full set of
83 # options your script accepts.
85 --debug enable verbose shell tracing
86 -W, --warnings=CATEGORY
87 report the warnings falling in CATEGORY [all]
88 -v, --verbose verbosely report processing
89 --version print version information and exit
90 -h, --help print short or long help message and exit
93 # Additional text appended to 'usage_message' in response to '--help'.
95 Warning categories include:
96 'all' show all warnings
97 'none' turn off all the warnings
98 'error' warnings are treated as fatal errors"
100 # Help message printed before fatal option parsing errors.
101 fatal_help
="Try '\$progname --help' for more information."
105 ## ------------------------- ##
106 ## Hook function management. ##
107 ## ------------------------- ##
109 # This section contains functions for adding, removing, and running hooks
110 # to the main code. A hook is just a named list of of function, that can
111 # be run in order later on.
113 # func_hookable FUNC_NAME
114 # -----------------------
115 # Declare that FUNC_NAME will run hooks added with
116 # 'func_add_hook FUNC_NAME ...'.
121 func_append hookable_fns
" $1"
125 # func_add_hook FUNC_NAME HOOK_FUNC
126 # ---------------------------------
127 # Request that FUNC_NAME call HOOK_FUNC before it returns. FUNC_NAME must
128 # first have been declared "hookable" by a call to 'func_hookable'.
133 case " $hookable_fns " in
135 *) func_fatal_error
"'$1' does not accept hook functions." ;;
138 eval func_append
${1}_hooks
'" $2"'
142 # func_remove_hook FUNC_NAME HOOK_FUNC
143 # ------------------------------------
144 # Remove HOOK_FUNC from the list of functions called by FUNC_NAME.
149 eval ${1}_hooks
='`$ECHO "\$'$1'_hooks" |$SED "s| '$2'||"`'
153 # func_run_hooks FUNC_NAME [ARG]...
154 # ---------------------------------
155 # Run all hook functions registered to FUNC_NAME.
156 # It is assumed that the list of hook functions contains nothing more
157 # than a whitespace-delimited list of legal shell function names, and
158 # no effort is wasted trying to catch shell meta-characters or preserve
164 case " $hookable_fns " in
166 *) func_fatal_error
"'$1' does not support hook funcions.n" ;;
169 eval _G_hook_fns
=\$
$1_hooks; shift
171 for _G_hook
in $_G_hook_fns; do
174 # store returned options list back into positional
175 # parameters for next 'cmd' execution.
176 eval _G_hook_result
=\$
${_G_hook}_result
177 eval set dummy
"$_G_hook_result"; shift
180 func_quote_for_eval
${1+"$@"}
181 func_run_hooks_result
=$func_quote_for_eval_result
186 ## --------------- ##
187 ## Option parsing. ##
188 ## --------------- ##
190 # In order to add your own option parsing hooks, you must accept the
191 # full positional parameter list in your hook function, remove any
192 # options that you action, and then pass back the remaining unprocessed
193 # options in '<hooked_function_name>_result', escaped suitably for
200 # # Extend the existing usage message.
201 # usage_message=$usage_message'
202 # -s, --silent don'\''t print informational messages
205 # func_quote_for_eval ${1+"$@"}
206 # my_options_prep_result=$func_quote_for_eval_result
208 # func_add_hook func_options_prep my_options_prep
211 # my_silent_option ()
215 # # Note that for efficiency, we parse as many options as we can
216 # # recognise in a loop before passing the remainder back to the
217 # # caller on the first unrecognised argument we encounter.
218 # while test $# -gt 0; do
221 # --silent|-s) opt_silent=: ;;
222 # # Separate non-argument short options:
223 # -s*) func_split_short_opt "$_G_opt"
224 # set dummy "$func_split_short_opt_name" \
225 # "-$func_split_short_opt_arg" ${1+"$@"}
228 # *) set dummy "$_G_opt" "$*"; shift; break ;;
232 # func_quote_for_eval ${1+"$@"}
233 # my_silent_option_result=$func_quote_for_eval_result
235 # func_add_hook func_parse_options my_silent_option
238 # my_option_validation ()
242 # $opt_silent && $opt_verbose && func_fatal_help "\
243 # '--silent' and '--verbose' options are mutually exclusive."
245 # func_quote_for_eval ${1+"$@"}
246 # my_option_validation_result=$func_quote_for_eval_result
248 # func_add_hook func_validate_options my_option_validation
250 # You'll alse need to manually amend $usage_message to reflect the extra
251 # options you parse. It's preferable to append if you can, so that
252 # multiple option parsing hooks can be added safely.
255 # func_options [ARG]...
256 # ---------------------
257 # All the functions called inside func_options are hookable. See the
258 # individual implementations for details.
259 func_hookable func_options
264 func_options_prep
${1+"$@"}
265 eval func_parse_options \
266 ${func_options_prep_result+"$func_options_prep_result"}
267 eval func_validate_options \
268 ${func_parse_options_result+"$func_parse_options_result"}
270 eval func_run_hooks func_options \
271 ${func_validate_options_result+"$func_validate_options_result"}
273 # save modified positional parameters for caller
274 func_options_result
=$func_run_hooks_result
278 # func_options_prep [ARG]...
279 # --------------------------
280 # All initialisations required before starting the option parse loop.
281 # Note that when calling hook functions, we pass through the list of
282 # positional parameters. If a hook function modifies that list, and
283 # needs to propogate that back to rest of this script, then the complete
284 # modified list must be put in 'func_run_hooks_result' before
286 func_hookable func_options_prep
295 func_run_hooks func_options_prep
${1+"$@"}
297 # save modified positional parameters for caller
298 func_options_prep_result
=$func_run_hooks_result
302 # func_parse_options [ARG]...
303 # ---------------------------
304 # The main option parsing loop.
305 func_hookable func_parse_options
306 func_parse_options
()
310 func_parse_options_result
=
312 # this just eases exit handling
313 while test $# -gt 0; do
314 # Defer to hook functions for initial option parsing, so they
315 # get priority in the event of reusing an option name.
316 func_run_hooks func_parse_options
${1+"$@"}
318 # Adjust func_parse_options positional parameters to match
319 eval set dummy
"$func_run_hooks_result"; shift
321 # Break out of the loop if we already parsed every option.
322 test $# -gt 0 ||
break
327 --debug|
-x) debug_cmd
='set -x'
328 func_echo
"enabling shell trace mode"
332 --no-warnings|
--no-warning|
--no-warn)
333 set dummy
--warnings none
${1+"$@"}
337 --warnings|
--warning|
-W)
338 test $# = 0 && func_missing_arg
$_G_opt && break
339 case " $warning_categories $1" in
341 # trailing space prevents matching last $1 above
342 func_append_uniq opt_warning_types
" $1"
345 opt_warning_types
=$warning_categories
348 opt_warning_types
=none
352 opt_warning_types
=$warning_categories
353 warning_func
=func_fatal_error
357 "unsupported warning category: '$1'"
363 --verbose|
-v) opt_verbose
=: ;;
364 --version) func_version
;;
365 -\?|
-h) func_usage
;;
368 # Separate optargs to long options (plugins may need this):
369 --*=*) func_split_equals
"$_G_opt"
370 set dummy
"$func_split_equals_lhs" \
371 "$func_split_equals_rhs" ${1+"$@"}
375 # Separate optargs to short options:
377 func_split_short_opt
"$_G_opt"
378 set dummy
"$func_split_short_opt_name" \
379 "$func_split_short_opt_arg" ${1+"$@"}
383 # Separate non-argument short options:
385 func_split_short_opt
"$_G_opt"
386 set dummy
"$func_split_short_opt_name" \
387 "-$func_split_short_opt_arg" ${1+"$@"}
392 -*) func_fatal_help
"unrecognised option: '$_G_opt'" ;;
393 *) set dummy
"$_G_opt" ${1+"$@"}; shift; break ;;
397 # save modified positional parameters for caller
398 func_quote_for_eval
${1+"$@"}
399 func_parse_options_result
=$func_quote_for_eval_result
403 # func_validate_options [ARG]...
404 # ------------------------------
405 # Perform any sanity checks on option settings and/or unconsumed
407 func_hookable func_validate_options
408 func_validate_options
()
412 # Display all warnings if -W was not given.
413 test -n "$opt_warning_types" || opt_warning_types
=" $warning_categories"
415 func_run_hooks func_validate_options
${1+"$@"}
417 # Bail if the options were screwed!
418 $exit_cmd $EXIT_FAILURE
420 # save modified positional parameters for caller
421 func_validate_options_result
=$func_run_hooks_result
426 ## ----------------- ##
427 ## Helper functions. ##
428 ## ----------------- ##
430 # This section contains the helper functions used by the rest of the
431 # hookable option parser framework in ascii-betical order.
434 # func_fatal_help ARG...
435 # ----------------------
436 # Echo program name prefixed message to standard error, followed by
437 # a help hint, and exit.
442 eval \
$ECHO \""Usage: $usage"\"
443 eval \
$ECHO \""$fatal_help"\"
451 # Echo long help message to standard output and exit.
457 $ECHO "$long_help_message"
462 # func_missing_arg ARGNAME
463 # ------------------------
464 # Echo program name prefixed message to standard error and set global
470 func_error
"Missing argument for '$1'."
475 # func_split_equals STRING
476 # ------------------------
477 # Set func_split_equals_lhs and func_split_equals_rhs shell variables after
478 # splitting STRING at the '=' sign.
479 test -z "$_G_HAVE_XSI_OPS" \
481 test 5aa/bb/cc = "${#x}${x%%/*}${x%/*}${x#*/}${x##*/}"') 2>/dev
/null \
482 && _G_HAVE_XSI_OPS
=yes
484 if test yes = "$_G_HAVE_XSI_OPS"
486 # This is an XSI compatible shell, allowing a faster implementation...
487 eval 'func_split_equals ()
491 func_split_equals_lhs=${1%%=*}
492 func_split_equals_rhs=${1#*=}
493 test "x$func_split_equals_lhs" = "x$1" \
494 && func_split_equals_rhs=
497 # ...otherwise fall back to using expr, which is often a shell builtin.
502 func_split_equals_lhs
=`expr "x$1" : 'x\([^=]*\)'`
503 func_split_equals_rhs
=
504 test "x$func_split_equals_lhs" = "x$1" \
505 || func_split_equals_rhs
=`expr "x$1" : 'x[^=]*=\(.*\)$'`
507 fi #func_split_equals
510 # func_split_short_opt SHORTOPT
511 # -----------------------------
512 # Set func_split_short_opt_name and func_split_short_opt_arg shell
513 # variables after splitting SHORTOPT after the 2nd character.
514 if test yes = "$_G_HAVE_XSI_OPS"
516 # This is an XSI compatible shell, allowing a faster implementation...
517 eval 'func_split_short_opt ()
521 func_split_short_opt_arg=${1#??}
522 func_split_short_opt_name=${1%"$func_split_short_opt_arg"}
525 # ...otherwise fall back to using expr, which is often a shell builtin.
526 func_split_short_opt
()
530 func_split_short_opt_name
=`expr "x$1" : 'x-\(.\)'`
531 func_split_short_opt_arg
=`expr "x$1" : 'x-.\(.*\)$'`
533 fi #func_split_short_opt
538 # Echo short help message to standard output and exit.
544 $ECHO "Run '$progname --help |${PAGER-more}' for full usage"
551 # Echo short help message to standard output.
552 func_usage_message
()
556 eval \
$ECHO \""Usage: $usage"\"
563 /^Written by/q' < "$progpath"
565 eval \
$ECHO \""$usage_message"\"
571 # Echo version message to standard output and exit.
576 printf '%s\n' "$progname $scriptversion"
586 /^# Written by /,/# warranty; / {
589 s|\((C)\)[ 0-9,-]*[ ,-]\([1-9][0-9]* \)|\1 \2|
596 /^warranty; /q' < "$progpath"
605 # eval: (add-hook 'before-save-hook 'time-stamp)
606 # time-stamp-pattern: "10/scriptversion=%:y-%02m-%02d.%02H; # UTC"
607 # time-stamp-time-zone: "UTC"