From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (qmail 2944 invoked from network); 17 Aug 2001 14:00:32 -0000 Received: from sunsite.dk (130.225.51.30) by ns1.primenet.com.au with SMTP; 17 Aug 2001 14:00:32 -0000 Received: (qmail 19157 invoked by alias); 17 Aug 2001 14:00:14 -0000 Mailing-List: contact zsh-users-help@sunsite.dk; run by ezmlm Precedence: bulk X-No-Archive: yes X-Seq: 4131 Received: (qmail 19140 invoked from network); 17 Aug 2001 14:00:12 -0000 From: martin.ebourne@arcordia.com Expiry-Date: Thu, 15 Nov 2001 -1:-1:-1 +0000 Subject: New options & arguments processing system for ZSH To: zsh-users@sunsite.dk Date: Fri, 17 Aug 2001 14:59:46 +0100 Message-ID: X-MIMETrack: Serialize by Router on LON-ARCMTA-01/ARCORDIA(Release 5.0.3 (Intl)|21 March 2000) at 08/17/2001 02:59:48 PM MIME-Version: 1.0 Content-type: text/plain; charset=us-ascii Hi, I've been working on a fully featured system for processing options & arguments in Zsh scripts/functions for a while now, and I think it's ready for other people to try out (mostly because I've just finished the documentation!). The main features are ease of use, processing and validation of both options and normal command arguments, and fully automatic completion when using the new Zsh completion system! For now I'd like any comments people have, especially bug reports and praise (yeah right!). It should work on any version of Zsh 4, and probably recent 3's as well. An example called 'yes' is also included for your perusal. All you need to do is put the enclosed parse_opts and _parse_opts files (and yes for the example) on your fpath and autoload -U parse_opts _parse_opts yes compdef -n _parse_opts yes If other people find it useful then then I'd like this to be added to the Zsh distribution sometime, though not until it's had a decent amount of external testing. Send me those bug reports! Documentation is all included at the top of the parse_opts function. 'bugs' in that also of interest. I've sent this to zsh-users because it may be useful to anyone. I don't expect to post it here in future though. (zsh-workers probably.) Cheers, Martin. PS. If anyone wants the equivalent Perl module version then let me know. ----------BEGIN parse_opts # ZSH function file # Provide options parsing and help output # (C) 2001 Martin Ebourne # # $Id: parse_opts,v 1.24 2001/08/17 13:32:02 mebourne Exp $ ### Documentation ## Introduction # # This module provides a complete option and argument parsing system, # configured entirely from a help string supplied by the user. The help string # is free-format text and describes options/arguments in the usual way; into # this is then embedded special descriptions which this module decodes to # allow it to process options and arguments on the command line. # # Some of the main features of this system are: # # - Easy to use, and configure # - Fully supports both options and normal command line arguments # - Option values and arguments may be type checked # - There is an extensible user defined type system, based on inheritence # - A range of default types is provided # - Completion for Zsh is handled fully automatically # - Automatic error reporting and handling of help displaying # - Both standard Unix single character options (which may be grouped, # eg. `-a -l' or `-al'), and GNU style long options (eg. --long-list) are # supported # - Long options with values are supported with two different formats for the # user's convenience. eg. both `--directory=' and `--directory ' # are valid # - There is a compatible equivalent module for Perl # # Please see the example section at the end of this help text to see what the # system is like in use. ## Use from a shell script # # In order to use parse_opts from a shell script, a construct such as the # following will generally be used: # # local -A opts # parse_opts - opts -- "$argv[@]" <<'EOF' || return 1 # # EOF # # Here the first argument tells parse_opts that the help text will be passed # in as stdin (done as a `here' doc in this case), the second argument gives # the name of the associative array which the results should be stored in, and # the command line arguments are all provided after the `--'. # # In the case that parse_opts finishes successfully, the decoded options are # in the opts array, otherwise the script will exit. Note that in the case of # a non-0 return code it may not necessarily be an error - maybe the user has # requested help. In either case an error message or whatever will already # have been printed so nothing further needs to be done. ## Syntax # # parse_opts [ [ []]] [--] [ ...] # # config - This is the help text used to configure the system. If missing or # `-' then this will instead be read from stdin to parse_opts (which # is not normally stdin to the caller) # results - If provided then this is the name of the default associative array # to store the results in, though it may be overridden later on a per # parameter basis. On an unsuccessful exit, this will be untouched # argv - This is the name of the array to find the command line arguments in. # On a successful exit the array will have been emptied due to all # the arguments being used. On an successful exit it will be # untouched # `--' - This is required if the arguments are to be given directly # arguments - As an alternative to providing the argv parameter, the arguments # may be supplied directly on the command line, after the `--' # # The return code is 0 if the arguments have been successfully decoded and the # script should proceed as normal, or non-0 for all other circumstances. In # the latter case the script should exit straight away. # # NB. When specifying the results and argv parameters, these are done by name, # and hence should not include the `$'. Note also that argv cannot actually be # `argv' since that will clash with the one inside parse_opts. ## Configuration help text # # The idea behind the configuration help text is that you write the free-form # help exactly as you wish to see it, and then add in extra lines with # embedded specifications in them. These lines are filtered out when the help # is displayed so you end up with what you wanted. Meanwhile, in addition to # processing the embedded specifications, parse_opts also decodes the help # itself to extract descriptions for each of the options. These are used when # automatically generating the Zsh completion. # # Just to get the idea, a very simple help text could look like this: # # Description: # View the latest of a set of files, given part of the filename # # Usage: # latest [options] # # Options: # -h, --help Provide this help # # --help | -h # # Arguments: # Start of filename of set of files to view. # May contain wildcards # # filename-stem : ? file # # For a slightly more elaborate example, see the `Example' section below. # # The basic rules for the help text are as follows: # # 1. All embedded specifications and commands are introduced with a `#' as the # first thing on the line. No non-whitespace characters may proceed it, # else the `#' will be treated as a normal character. # # 2. All option/argument syntax descriptions (ie. the bit for the user to # see), or other titles/general description, should generally not have, or # be completely before, any tabs on the line. This does not matter in the # case where the text preceeds another option, but otherwise it may end up # becoming part of an option decription as sent to the completion system. # # 3. All description text relating to an option/argument must have at least # one tab preceeding it. # # 4. Comments (ie. free text not included in the help output) are introduced # as for the embedded specifications, but with `##' instead. # # 5. The presence or otherwise of whitespace withing an embedded description # line (as described below) is generally crucial. The amount will however # not matter. # # 6. Long embedded specifications may be continued on another line by escaping # the newline with a `\' ## Embedded specification syntax # # The currently recognised types of specification line are introduced as # follows: # # `#' - These are option/argument lines. There are minor differences # between option and argument lines, but they are otherwise the # same # `#type' - These are type definition lines # `##' - As already mentioned, this is a comment line ## Option/argument definition lines # # The option/argument description lines have four parts, of which only one is # mandatory. # # `# ' [ ( ` = ' | ` += ' ) ] # [ ` : ' ] [ ` * ' ] # # Or as an abbreviated form: # # # tag-part = option-part : type-part * settings-part # # tag-part += option-part : type-part * settings-part # # eg. # # --help | -h # # --delimiter | -d : text # # columns += [1,*] column-number : integer # # The is the only mandatory section, and the other sections are # only present if their corresponding separator is present. Briefly, # # tag-part - This determines where the decoded result is stored # option-part - This gives the name of the option or argument, with aliases # type-part - This gives the type of value with the option or for the # argument, used for validation checking and completion # settings-part - This gives special settings for the option/argument, and is # rarely used # # These are now described in detail. # Tag part: # # This determines where the decoded result is stored. # # One of: # `[' `]' # or # # # eg. # [name] # an_array # associative_array # associative_array[name] # # is the name of the hash key the value will be stored in - using # the default `return values' associative array provided to parse_opts. # # The alternative of variable-name stores the value directly in the given # variable. The variable is expected to be local to the calling program, and # the exact behaviour depends on its type. # # If is not provided, then it will default to using of # the first option or argument name in the list. Option names # will have any leading `-' stripped off them for this purpose. # # The way the tag is accessed also depends on the separator used before the # . There are two possibilities - either `=' which overwrites any # value, or `+=' which appends to any current value. Note that this is # completely independent of the option/argument frequency, detailed later. # # The possible options when storing decoded values are: # # Type of storage Overwrite Append # # undefined (ie. variable doesn't exist) Type 1 Type 2 # index into default result array Type 1 Type 2 # scalar Type 1 Type 2 # array Type 3 Type 4 # associative array Type 1 Type 2 # # Type 1 - The old value is replaced each time a new value is stored # Type 2 - Each new value is appended as a string to the previous value, with # a space as separator if required # Type 3 - Any old entries are removed, leaving just the new entry # Type 4 - The new entry is appended onto the end of the array # Option part: # # This gives the name of the option or argument, with aliases. # # [ `[' `] ' ] [ ` | ' ... ] # # eg. # --help | -h # [*] --directory | -d # [0,1] filename # # gives the name of the option, as it will appear on the command # line. eg. `-h' or `--help'. Multiple s may be given in order to # supply aliases of the same option. Single letter options begin with `-', and # may be grouped. Other options are long options and begin with `--'. # # If there is no `-' at the start of the option name then this is taken to be # an argument definition. Obviously this doesn't appear on the command line, # but it will be used when reporting any errors. For this reason it is # recommended that it matches the argument name as specified in the help. Note # that aliases for arguments are not allowed (there would be no point). # # may consist of `-', `_', and alphanumeric characters only. # # is of the form: # # or # `*' # or # `,' # or # `,*' # or # `*,' # # Where , , and are all positive integers. These # give the allowable range of the number of occurrences of the option or # argument. If only is provided, then and will # both have this value. # # If a `*' is present then it means `any value' for that count. For an option # this would mean it could appear any number of times. For an argument it # means that any spare arguments on the command line will be used up for this # argument. Only one argument will normally have a frequency including `*', # otherwise there will be a clash as to what gets the remaining arguments. In # the event of such a clash (which may also happen if there is more than one # min/max range specified), the early arguments will all use up to their # maximum before moving on to processing the next one. Note that enough # arguments will always be left to meet the requirements of the minimum count # of any following arguments, so it is only `spare' ones which get treated in # this way. # # Typical frequencies for options are: # # [0,1] - The default. Optional - may only occur once or not at all. # [*] - Repeatable. May appear any number of times. # [1,1] - Mandatory. NOT YET CHECKED # NOTE: Min/max values are currently not fully checked for options # # Typical frequencies for arguments are: # # [1] - The default. Single mandatory argument. # [0,1] - Optional. # [*] - Any number of arguments, or none # [1,*] - Any number of arguments, but at least one # Type part: # # This gives the type of value with the option or for the argument, used for # validation checking and completion. # # [ `? ' ] [ `=' ] # # eg. # string # ? filename # constant=true # values="(value1 value2)" # values=${(k)functions} # # `?' indicates that validation should not be performed on the option # value/argument. The type is still used for options to determine if they take # a value, and for completion with both options and arguments. # # is one of: # # switch - (2) Simple switch. Result will be 1. Default for options # constant - (1) (2) Constant to be assigned. Result will be given # string - Takes a parameter with no validation. Default for arguments # values - (1) Takes a parameter validated against the supplied # interpreted as some kind of list of values. See below for a more # detailed explanation # pattern - (1) Takes a parameter validated against the supplied # which will be interpreted as a shell glob pattern (with the # extended_glob option set) # exec - (1) Takes a parameter validated by executing the # supplied as a command. The command will be provided with an extra # parameter on the end, which will be the word requiring # validation, and should return success/failure in the usual way # OR - Any pre-defined type (none of which take a ). For a # complete list of these, see the _parse_opts_setupdefinitions # function below, which is where they are defined # OR - Any user defined type (none of which take a ) # # (1) These types take a value, which must be supplied. Others do not and one # must not be supplied to those. The value will be subject to normal shell # quote removal. # (2) These are for use with options only. They clearly have no useful meaning # for arguments. # # values type: # # There are three modes of operation for the values type. Remember that quote # removal will happen on the before any of the following. # # If the is enclosed in `(' ... `)' then it is taken as a literal list # of valid values. These will be handled according to normal shell word # splitting and quote removal rules. # eg. # "(avalue another_value 'with space')" # # If the starts with `$' then it will be taken as a variable # substitution which is expected to return an array of results. If there is an # `@' expansion flag present then the expansion will automatically take place # in double quotes in order that it will have the desired effect. # eg. # $array # ${(k)associative_array} # ${(@)=string} # # In all other cases the value is assumed to be the name of some kind of shell # variable, and the behaviour will depend on this variable's type: # # scalar - Treated as a string and handled according to normal shell # word splitting and quote removal rules # array - The array values are used directly # associative array - The keys are used for validation. In this case the # values are also used for the completion - each value is # treated as the description for its key when passed to # the completion code # # eg. # string # associative_descriptions # Settings part: # # This gives special settings for the option/argument, and is rarely used. # # [ `=' ] [ ` ' ... ] # # eg. # complete.hidden # excludes="-L --no-list" # # is the identifier of whatever needs to be set. The value is # subject to quote removal, and is then assigned directly to the setting after # any of the other assignments have been made. If the value is not given, then # `1' will be assigned. # # Current identifiers in use are: # # NOTE: These identifiers subject to change for now # # tag - (1) This is as for the tag section described above # frequency - (1) This is the frequency from the option section described # above, with the surrounding brackets removed # type - (1) This is as the the type section described above # help - (2) This is the description for this option extracted from the # help # aliases - (2) This is a list of aliases of this option - ie. the other # s provided in the same specification. It is empty # for arguments # excludes - (2) This is a list of options which are not allowed if this one # is present. For a non-repeatable option, this is usually the same # as aliases. It is currently unused for arguments # complete.hidden - If true then the option is not supplied for completion # # (1) This should not normally be set, since it just duplicates information # already provided # (2) This is generated automatically, but it may on rare occasions be useful # to override it manually with a different value ## Type definition lines # # `#type ' ` ' [ [ ` ' ] ` ' ] # [ ` * ' ] # # eg. # #type integer pattern=([-+]|)<-> "signed integer number" " " # #type function values=${(k)functions} "shell function" _functions # # is the identifier for this type to be assigned to. This is what # appears as in option/argument type sections, or as a base type # to another user defined type. # # may consist of `-', `_', and alphanumeric characters only. # # is of the form: # [ `=' ] # # and are handled exactly the same as for the type # section in the option/argument description. The base type is what this user # defined type is an alias for. It may be either a built in type, or another # user defined type (in which case must not be supplied). # # is the description that the option value or argument will get # when supplied to the completion code, and is used in exactly the same way as # the description provided to the _arguments completion function. # # is the action that the option value or argument will have when # supplied to the completion code, and is used in exactly the same way as the # action provided to the _arguments completion function. # # Both and are optional, and if not supplied or if # empty then the respective field from the base type will be used, # recursively. In order to override the value in a base type with nothing, a # single space should be used. # # is identical to that of options/arguments, except for the # valid identifiers, which are: # # NOTE: These identifiers subject to change for now # # base - (1) This is as for the base-part described above # complete.description - (1) This is as for the description described above # complete.action - (1) This is as for the action described above # complete.restrict - This controls the way the `words' array is handled by # _arguments when it calls the action. See the # completion documentation for details. It may have any # of the following values: # none - Default. Used as is, corresponds to `*:' # normal - Modified to only include the arguments on # the command line, corresponds to `*::' # match - Modified to only include the matched # arguments, corresponds to `*:::' # NB. When using a type with restrict set other than to # `none', the frequency of the argument should be of # the form [*] or [,*] # NOTE: Currently not valid on options # # (1) This should not normally be set, since it just duplicates information # already provided ## Enabling completion with Zsh # # In order to enable completion with Zsh, all that is required is to register # the _parse_opts function as the completer for your command. # # eg. # compdef -n _parse_opts yes # # _parse_opts will need to be defined or autoloaded beforehand. # # Now you should find full completion available for options, option values, # and arguments. Note that not all types are completable. You may register # user defined types to provide any unusual completion actions, but for the # `switch', `constant', and `values' types sensible completions will be # generated automatically. No completion will be offered by default for values # with the `string', `pattern', or `exec' types. All pre-defined types already # have completions where appropriate. # # If you have problems with an automatic completion you can see the _arguments # call generated by simply running your command directly with the # --zsh-completion option. This will then enable you to find out where the # problem is. Currently there's no checking for sanity in the option # specifications so it is quite possible to generate invalid calls. ## Example: # # Here is an example of a small Zsh script/function which makes effective use # of parse_opts. It implements a command similar to 'yes' available on some # Unix systems. # # local -A opts # parse_opts - opts -- "$argv[@]" <<'EOF' || return 1 # Description: # Repeatedly print a string # # Usage: # yes [options] [ ...] # # Options: # -e, --escape Interpret escape characters as for echo (default unless # the BSD_ECHO option is set) # # [echoopt] += --escape | -e : constant=-e # -E, --no-escape Prevent interpretation of escape characters # # [echoopt] += --no-escape | -E : constant=-E # -h, --help Provide this help # # --help | -h # -l , --lines= # Output only count times # # --lines | -l : integer # -n, --no-newline Suppress output of automatic newline # # [echoopt] += --no-newline | -n : constant=-n # -s , --sleep= # Pause for number of seconds between each echo # # --sleep | -s : integer # # Arguments: # [ ...] Optional text to print. Defaults to 'yes' # # [text] += [*] text # EOF # # # Output as required # while (( ${opts[lines]:-1} )) # do # echo $=opts[echoopt] ${opts[text]:-yes} # (( opts[sleep] )) && sleep $opts[sleep] # (( opts[lines] && opts[lines]-- )) # done ### Configuration # To do: # excludes properly - options & arguments # frequency checking on options # optional values for options - or more fully, frequency for same # interface to extend excludes # type.complete.restrict for options # settings identifier names. decide on them properly # Calls _parse_opts_parsehelp in order to create the built-in types & other # default definitions _parse_opts_setupdefinitions() { _parse_opts_parsehelp ' ## These are extensions of the basic built in types #type switchoff constant=0 "" "" #type integer pattern=([-+]|)<-> "signed integer number" " " #type posinteger pattern=<-> "positive integer number" " " #type text string text " " ## These are file-like things validated with test #type directory exec="test -d" directory _directories #type file exec="test -f" file _files ## These are lists from the shell #type function values=${(k)functions} "shell function" _functions #type option exec=_parse_opts_validate_option "zsh option" _options #type parameter values=${(k)parameters} parameter _parameters ## Complex type for sub commands - ie. where a real command is passed as an ## argument. Note that this type may currently only be used for argument ## specifications, and only with a frequency of [*], [1,*], etc #type fullcommand string "" " _normal" \ * complete.restrict=match # --help # --zsh-completion * complete.hidden ' } ### Helper functions for default user types # Returns 0 for valid option, 1 for otherwise. Can't use values= due to the # '_' and case handling in option names _parse_opts_validate_option() { local option="$argv[1]" (( $+options[$option] )) } ### Implementation # NB. In some of the below functions (marked) locals must begin with _po_. # This is to prevent name clashes when options refer back to variables in the # calling program's name space # Print an associative array with multi-field keys ('.' delimited), as if it # were a real nested structure. Currently only handles one level of 'nesting' # though. Used for debugging _parse_opts_showdata() { local var="$argv[1]" # Make a copy of the array because otherwise we have trouble later local -A data data=("${(@Pkv)var}") echo "$var = {" # Collect the first field from the keys, uniqued local -aU stems stems=(${(k)data%%.*}) # Process each of the first fields local -a keys for stem in ${(o)stems} do echo " $stem = {" # Get a list of matching full keys keys=(${${(Mk)data:#$stem.*}#$stem.}) # Work out the width of field padding required integer length=${#${(O)keys//?/?}[1]} # Print each of the key/value pairs local key="" for key in ${(o)keys} do echo " ${(r:length:::::)key} = '$data[$stem.$key]'" done echo " }" done echo "}" } # This helper function handles the parsing of option definitions for # _parse_opts_parsehelp (uses locals directly) _parse_opts_parseoption() { # Extract the special parameters if there are any local -a parameters if (( paraminfo[(I)\*] )) then parameters=($paraminfo[paraminfo[(I)\*]+1,-1]) paraminfo=($paraminfo[1,paraminfo[(I)\*]-1]) fi # Extract the type information if there is any local type="" if (( paraminfo[(I):] )) then type=$paraminfo[paraminfo[(I):]+1,-1] paraminfo=($paraminfo[1,paraminfo[(I):]-1]) fi # Extract the tag information if there is any. Append (ie. where '+=' # instead of '=') is recorded by prefixing a '+' onto the stored tag local tag="" if (( paraminfo[(I)(+|)=] )) then tag=$paraminfo[1,paraminfo[(I)(+|)=]-1] if [[ $paraminfo[(r)(+|)=] == += ]] then tag="+$tag" fi paraminfo=($paraminfo[paraminfo[(I)(+|)=]+1,-1]) fi # Handle the optional frequency definition # (default is [1] for arguments, [0,1] for options) local frequency="" if [[ $paraminfo[1] == \[*\] ]] then frequency=${${paraminfo[1]#\[}%\]} shift paraminfo fi # Options is remainder less the '|' paraminfo=(${paraminfo:#\|}) # Check we've got something left if (( !$#paraminfo )) then echo "Missing option/argument name in definition: '" $=line "'" 1>&2 return 1 fi # If tag not provided then default to first option name with any leading '-'s removed if [[ -z $tag ]] then tag="[${paraminfo[1]##-#}]" fi # Assign all the details for the current options/argument local option="" for option in $paraminfo do local aliases="" excludes="" # Check for invalid characters in option name if [[ $option != [-_[:alnum:]]## ]] then echo "Invalid name for option/argument: '$option'" 1>&2 return 1 fi # Establish defaults for missing values if [[ $option == -* ]] then [[ -z $frequency ]] && frequency="0,1" [[ -z $type ]] && type="switch" # Aliases, excluding this option aliases=${paraminfo:#$option} # Excludes. Only if the max frequency of this option is 1, same as aliases integer min_frequency=0 max_frequency=0 _parse_opts_minmaxfrequency min_frequency max_frequency $frequency || return if (( max_frequency == 1 )) then excludes=$aliases fi else [[ -z $frequency ]] && frequency="1" [[ -z $type ]] && type="string" # Calculate the minimum number of arguments we need for the command to # be able to run. We need this later to know how many arguments are # spare when we have a choice on how many to use integer min_frequency=0 max_frequency=0 _parse_opts_minmaxfrequency min_frequency max_frequency $frequency || return (( _po_minargs += min_frequency )) fi # Store the main data for this option _po_params[$option.tag]=$tag _po_params[$option.frequency]=$frequency _po_params[$option.type]=$type _po_params[$option.help]=$helpline _po_params[$option.aliases]=$aliases _po_params[$option.excludes]=$excludes # Store option/argument name in appropriate array if [[ $option == -* ]] then _po_options[$option]=1 else _po_arguments=($_po_arguments $option) fi # Store the special parameters local parameter="" value="" for parameter in "$parameters[@]" do if [[ $parameter == *=* ]] then value=${(Q)parameter#*=} parameter=${parameter%%\=*} else value=1 fi _po_params[$option.$parameter]=$value done done return 0 } # This helper function handles the parsing of type definitions for # _parse_opts_parsehelp (uses locals directly) _parse_opts_parsetype() { # Extract the special parameters if there are any local -a parameters if (( paraminfo[(I)\*] )) then parameters=($paraminfo[paraminfo[(I)\*]+1,-1]) paraminfo=($paraminfo[1,paraminfo[(I)\*]-1]) fi local type="$paraminfo[1]" basetype="$paraminfo[2]" local description="${(Q)paraminfo[3]}" action="${(Q)paraminfo[4]}" # Check for invalid characters in type name if [[ $type != [-_[:alnum:]]## ]] then echo "Invalid name for type: '$type'" 1>&2 return 1 fi # Store the main data for this type _po_types[$type.base]=$basetype _po_types[$type.complete.description]=$description _po_types[$type.complete.action]=$action # Store the special parameters local parameter="" value="" for parameter in "$parameters[@]" do if [[ $parameter == *=* ]] then value=${(Q)parameter#*=} parameter=${parameter%%\=*} else value=1 fi _po_types[$type.$parameter]=$value done return 0 } # This function takes an encoded helpstring as its only parameter and # generates the various definition arrays _parse_opts_parsehelp() { local -a helptext # Process each line in the helptext. Note double newline (ie. empty line) is # converted to have a space so it doesn't get lost in the splitting local line="" helpline="" local -a paraminfo for line in ${(@f)${argv[1]//$'\\\n'}//$'\n\n'/$'\n \n'} do # If it starts with a '#' then its some kind of command line if [[ $line == [[:blank:]]#\#* ]] then # Decode the line into fields. Use 'z' not '=' to get proper quote handling paraminfo=(${(z)line}) local command="$paraminfo[1]" shift paraminfo case $command in \#) # An option description line _parse_opts_parseoption || return ;; \#type) # A type definition line _parse_opts_parsetype || return ;; \#\#*) # A comment ;; *) # This is a configuration error echo "Invalid command: '$command'" 1>&2 return 1 ;; esac else # Doesn't start with a # so append it to help helptext=($helptext $line) # If we have a line which has non-whitespace before any tabs it's an # option or argument or something similar. So we reset our current # helpline because the description will come after it if [[ $line == \ #[[:graph:]]* ]] then helpline="" fi # If the line has a tab on it then it might have a description after the tab if [[ $line == *$'\t'* ]] then # Extract the text after any tabs, and trim it of whitespace line=${${line##*$'\t'[[:blank:]]#}%%[[:blank:]]} if [[ -n $line ]] then # Add to current helpline if [[ -n $helpline ]] then helpline="$helpline $line" else helpline=$line fi fi fi fi done # Create the help string from the array of lines. In order to get a blank # line this far it must have a space on it _po_helptext=(${(F)helptext}) return 0 } # This function decodes the numeric expression within a frequency # specification to return an integer value. The variable given to var will be # updated with the results of processing the given expression substituting # number for `*' _parse_opts_frequencyvalue() { local var="$argv[1]" integer number="$argv[2]" local expression="$argv[3]" integer result=0 case $expression in \*) # Use default number result=$number ;; <->) # Use fixed number result=$expression ;; *) # This is a configuration error echo "Unrecognised frequency: '$expression'" 1>&2 return 1 ;; esac eval $var=$result return 0 } # This function decodes a frequency expression into min and max values. The # variables given to minvar & maxvar will be updated with the results of # processing the given expression substituting 0 or -1 for `*' _parse_opts_minmaxfrequency() { local minvar="$argv[1]" maxvar="$argv[2]" expression="$argv[3]" case $expression in <->,*) # Separate min & max values _parse_opts_frequencyvalue $minvar 0 ${expression%,*} || return _parse_opts_frequencyvalue $maxvar -1 ${expression#*,} || return ;; *,*) # Must be a number in the first field, this is invalid echo "Unrecognised frequency expression: '$expression'" 1>&2 return 1 ;; *) # Min & max values the same _parse_opts_frequencyvalue $minvar 0 $expression || return _parse_opts_frequencyvalue $maxvar -1 $expression || return ;; esac return 0 } # This function expands the parameter given after the 'values=' type into a # full list of valid values, which are then placed in the array named by # listname. If the hashname value is also given then a hash with values as # keys and descriptions as values is also updated if available # # NB. Locals in this function must begin _po_ _parse_opts_expandvalues() { local _po_param="$argv[1]" _po_list_name="$argv[2]" _po_hash_name="$argv[3]" local -a _po_local_list _po_local_hash case $_po_param in \(*\)) # Literal list _po_local_list=(${(z)${${_po_param#\(}%\)}}) ;; \$*) # Variable substitution # We don't know if there's an @ flag late in the substitution - in which # case it would need to be done in quotes to work properly. So try both # with and without and pick which way gave us the most results local -a _po_local_list_q eval _po_local_list="($_po_param)" eval _po_local_list_q="(\"$_po_param\")" if (( $#_po_local_list_q > $#_po_local_list )) then _po_local_list=("$_po_local_list_q[@]") fi ;; *) # External variable name # Behave according to the type of the external variable case ${(Pt)_po_param} in array*) # An array _po_local_list=("${(@P)_po_param}") ;; association*) # An associative array # Collect the keys for the values _po_local_list=("${(@kP)_po_param}") # The values will have descriptions, so we can update the hash as well _po_local_hash=("${(@kvP)_po_param}") ;; *) # Some kind of scalar. Split according to shell rules, remove quotes on each value _po_local_list=("${(@Q)${(z)_po_param}}") ;; esac ;; esac # Return results eval $_po_list_name='("$_po_local_list[@]")' [[ -n $_po_hash_name ]] && eval $_po_hash_name='("${(@kv)_po_local_hash}")' return 0 } # This helper function for _parse_opts_generatecompletion decodes our type # information into a form suitable for the completion system. It directly sets # the _po_description, _po_action, and _po_restrict variables in the calling # function # # NB. Locals in this function must begin _po_ _parse_opts_generatecompletiontype() { local _po_option="$argv[1]" # Extract the type without any leading '?' local _po_type="${_po_params[$_po_option.type]#? }" # Recursively convert user defined type into base type while (( $+_po_types[$_po_type.base] )) do # Take any settings supplied from the user defined type which we haven't already got [[ -z $_po_description ]] && _po_description=$_po_types[$_po_type.complete.description] [[ -z $_po_action ]] && _po_action=$_po_types[$_po_type.complete.action] [[ -z $_po_restrict ]] && _po_restrict=$_po_types[$_po_type.complete.restrict] _po_type=$_po_types[$_po_type.base] done # Decode built in type to set anything not done already local _po_param="${(Q)_po_type#*=}" case $_po_type in switch|constant=*) # No parameters, leave blank ;; string) # Takes a parameter with no validation [[ -z $_po_description ]] && _po_description="string" [[ -z $_po_action ]] && _po_action="_files" ;; values=*) # Takes a parameter, validated by list of values [[ -z $_po_description ]] && _po_description="value" if [[ -z $_po_action ]] then local -a _po_values local -A _po_hash _parse_opts_expandvalues $_po_param _po_values _po_hash || return if (( ! $#_po_hash )) then _po_values=(${(q)_po_values}) _po_action="($_po_values)" else _po_hash=("${(@qkv)_po_hash}") _po_action="((" local _po_key="" for _po_key in ${(k)_po_hash} do _po_action="$_po_action$_po_key\:$_po_hash[$_po_key] " done _po_action="$_po_action))" fi fi ;; pattern=*) # Takes a parameter which is a pattern for validation [[ -z $_po_description ]] && _po_description="value" [[ -z $_po_action ]] && _po_action=" " ;; exec=*) # Takes a parameter which is a command for validation [[ -z $_po_description ]] && _po_description="value" [[ -z $_po_action ]] && _po_action=" " ;; *) # This is a configuration error echo "Invalid option type for option $_po_option: '$_po_type'" 1>&2 return 1 ;; esac return 0 } # This function generates the _arguments call used by the completion system. # It is called when the special option --zsh-completion is passed on the # command line to the calling program. The command is written to stdout only # on success, and the completion system will then take that and execute it. On # error, errors are written to stderr instead. If returning the completion # information, parse_opts always exits with 1 in order to cause the calling # program to exit, so the return code can not be used to signal errors # # NB. Locals in this function must begin _po_ _parse_opts_generatecompletion() { local -a _po_specs # Process each option to build a list of specifications for _arguments. local _po_option="" for _po_option in ${(k)_po_options} $_po_arguments do if (( ! _po_params[$_po_option.complete.hidden] )) then local _po_frequency="$_po_params[$_po_option.frequency]" integer _po_min_frequency=0 _po_max_frequency=0 _parse_opts_minmaxfrequency _po_min_frequency _po_max_frequency $_po_frequency || return # Try to determine a description/completer from the type of this option local _po_description="" _po_action="" _po_restrict="" _parse_opts_generatecompletiontype $_po_option || return # Default restriction of none if [[ -z $_po_restrict ]] then _po_restrict="none" fi local _po_spec="" if [[ $_po_option == -* ]] then # Generate excludes clause if we have any if [[ -n $_po_params[$_po_option.excludes] ]] then _po_spec="$_po_spec($_po_params[$_po_option.excludes])" fi # Add repeating option flag if max frequency is not 1 if (( _po_max_frequency != 1 )) then _po_spec="$_po_spec*" fi # Add the main option name section _po_spec="$_po_spec$_po_option" # If we have a long option which takes a parameter then add the '=' flag # to say --option=value is valid if [[ $_po_option == --* && -n $_po_action ]] then _po_spec="$_po_spec=" fi # Add the description of the option if one was successfully extracted from the help text if [[ -n $_po_params[$_po_option.help] ]] then _po_spec="${_po_spec}[${_po_params[$_po_option.help]%%. *}]" fi # Now add the description and completer for the parameter, if we have one if [[ -n $_po_action ]] then _po_spec="$_po_spec:$_po_description:$_po_action" fi # Collect the specifications _po_specs=($_po_specs "'$_po_spec'") else _po_spec="$_po_spec:$_po_description" # Add the description of the argument if one was successfully extracted from the help text if [[ -n $_po_params[$_po_option.help] ]] then _po_spec="$_po_spec - ${${_po_params[$_po_option.help]%%. *}//:/\\:}" fi _po_spec="$_po_spec:$_po_action" # Collect the specifications if (( _po_min_frequency >= 0 )) && [[ $_po_restrict == none ]] then repeat $_po_min_frequency do _po_specs=($_po_specs "'$_po_spec'") done if (( _po_max_frequency >= _po_min_frequency )) then repeat $(( _po_max_frequency - _po_min_frequency )) do _po_specs=($_po_specs "':$_po_spec'") done else _po_specs=($_po_specs "'*$_po_spec'") fi else # Handle options to restrict 'words' array for completion action case $_po_restrict in match) _po_specs=($_po_specs "'*::$_po_spec'") ;; normal) _po_specs=($_po_specs "'*:$_po_spec'") ;; *) # none _po_specs=($_po_specs "'*$_po_spec'") ;; esac fi fi fi done # Only output the _arguments call on success # -s - Allow option grouping for single letter options # -w - Arguments follow single letter options, one for each relevant option # -S - Handle -- to terminate options # -A - No more options after first non-option argument echo "_arguments -s -w -S -A '-*' $_po_specs" return 0 } # This function stores the given value against the given option/argument name, # as defined by its related tag # # NB. Locals in this function must begin _po_ _parse_opts_store() { local _po_name="$argv[1]" _po_value="$argv[2]" local _po_tag="$_po_params[$_po_name.tag]" # If append is enabled for this tag, it will be prefixed with `+' integer append=0 if [[ $_po_tag == +* ]] then append=1 _po_tag=${_po_tag#+} fi case $_po_tag in \[*\]) # Store in the default options associative array _po_tag=${${_po_tag#\[}%\]} if (( append && $#_po_results[$_po_tag] )) then _po_results[$_po_tag]="$_po_results[$_po_tag] $_po_value" else _po_results[$_po_tag]=$_po_value fi ;; *) # Store in external variable/array # Behave according to the type of the external variable case ${(Pt)_po_tag} in array*) # An array if (( append )) then eval $_po_tag='("${(@P)_po_tag}" "$_po_value")' else eval $_po_tag='("$_po_value")' fi ;; association*) # An associative array. Add option/value pair local _po_oldvalue="" eval _po_oldvalue="\$${_po_tag}[$_po_name]" if (( append && $#_po_oldvalue )) then eval $_po_tag\[\$_po_name\]='"$_po_oldvalue $_po_value"' else eval $_po_tag\[\$_po_name\]='$_po_value' fi ;; *) # Some kind of scalar or undefined if (( append && ${(P)#_po_tag} )) then eval $_po_tag='"${(P)_po_tag} $_po_value"' else eval $_po_tag='$_po_value' fi ;; esac esac return 0 } # This helper function for _parse_opts_handletype gets the next value for an # option. It uses local variables in _parse_opts_handletype and so should not # be called from elsewhere _parse_opts_getnextvalue() { if (( _po_hasgivenvalue )) then # Value supplied with option so use that one first _po_value=$_po_givenvalue _po_hasgivenvalue=0 else # Get the next argument as the value. Note that if single letter options # taking a parameter are grouped, then the parameters follow straight # afterwards in the respective order # Check there's a value to get if (( $#_po_argv )) then _po_value=$_po_argv[1] shift _po_argv else echo "Missing parameter to option '$name'" 1>&2 return 1 fi fi return 0 } # This function handles processing of the option value or argument type. Any # values it needs will be fetched vie _parse_opts_getnextvalue, and the # appropriate result variable will be updated. Any validation of the values is # also performed here # # NB. Locals in this function must begin _po_ _parse_opts_handletype() { local _po_name="$argv[1]" _po_hasgivenvalue="$argv[2]" _po_givenvalue="$argv[3]" # Check for leading '?' to disable validation integer _po_validate=1 if [[ $_po_params[$_po_name.type] == ?\ * ]] then _po_validate=0 fi # Extract the type without any leading '?' local _po_type="${_po_params[$_po_name.type]#? }" # Recursively convert user defined type into base type while (( $+_po_types[$_po_type.base] )) do _po_type=$_po_types[$_po_type.base] done local _po_param="${(Q)_po_type#*=}" _po_value integer _po_isvalid=1 case $_po_type in switch) # Simple switch. Mark its presence _po_value=1 ;; constant=*) # Constant to be assigned (after quote removal). eg. constant=value or constant="more values" _po_value=$_po_param ;; string) # Takes a parameter with no validation _parse_opts_getnextvalue || return ;; values=*) # Takes a parameter, validated by list of values from external array _parse_opts_getnextvalue || return if (( $_po_validate )) then local -a _po_values _parse_opts_expandvalues $_po_param _po_values || return if (( ! $_po_values[(I)$_po_value] )) then _po_isvalid=0 fi fi ;; pattern=*) # Takes a parameter which is a pattern for validation _parse_opts_getnextvalue || return if [[ $_po_validate -ne 0 && $_po_value != $~_po_param ]] then _po_isvalid=0 fi ;; exec=*) # Takes a parameter which is a command for validation _parse_opts_getnextvalue || return if (( $_po_validate )) && ! eval $_po_param \$_po_value then _po_isvalid=0 fi ;; *) # This is a configuration error echo "Invalid option type for option $_po_name: '$_po_type'" 1>&2 return 1 ;; esac if (( !_po_isvalid )) then echo "Invalid value for option $_po_name: '$_po_value'" 1>&2 return 1 fi _parse_opts_store $_po_name $_po_value } # This function decodes any options present in the command line arguments, # using the pre-generated option arrays from _parse_opts_parsehelp. Arguments # are provided in _po_argv and are removed as and when processed. Decoded # options are returned in _po_results. Returns 1 if an error prevented the # options from being processed, else 0 # # NB. Locals in this function must begin _po_ _parse_opts_dooptions() { # Process each argument while it is an option while [[ $_po_argv[1] == -* ]] do local _po_option="$_po_argv[1]" shift _po_argv case $_po_option in --) # End of option list # Nothing else to do - return success return 0 ;; --*) # Long argument # Check to see if the option exists if (( $+_po_options[${_po_option%%\=*}] )) then # Check to see if value was in option, ie. --name=value if [[ $_po_option == *=* ]] then # Extract value in option and pass to type processing _parse_opts_handletype ${_po_option%%\=*} 1 ${_po_option#*=} || return else # Call type handling without a value _parse_opts_handletype $_po_option 0 || return fi else echo "Invalid option: $_po_option" 1>&2 return 1 fi ;; -?*) # Short option (may be grouped) # Extract the list of options from eg. -jkl local _po_optiongroup="${_po_option#-}" # Process each letter individually while [[ -n $_po_optiongroup ]] do # Extract the first letter local _po_optionletter="$_po_optiongroup[1]" _po_optiongroup=${_po_optiongroup#?} # Check to see if is a valid short option if (( $+_po_options[-$_po_optionletter] )) then # It is so process it as the type determines _parse_opts_handletype -$_po_optionletter 0 || return else echo "Invalid option: -$_po_optionletter" 1>&2 return 1 fi done ;; *) # Must be a lone - echo "Missing option after -" 1>&2 return 1 ;; esac done return 0 } # This function decodes any arguments present in the command line after the # options have been removed, using the pre-generated option arrays from # _parse_opts_parsehelp. Arguments are provided in _po_argv and are removed as # and when processed. Decoded values are returned in _po_results. Returns 1 if # an error prevented the arguments from being processed, else 0 # # NB. Locals in this function must begin _po_ _parse_opts_doarguments() { integer _po_spareargs (( _po_spareargs = $#_po_argv - _po_minargs )) if (( _po_spareargs < 0 )) then echo "Insufficient arguments" 1>&2 return 1 fi # Process each argument specification local _po_argument="" for _po_argument in $_po_arguments do local _po_frequency="$_po_params[$_po_argument.frequency]" # Ensure number of args to use is within required range integer _po_min=0 _po_max=0 _parse_opts_minmaxfrequency _po_min _po_max $_po_frequency || return # Try to grab all the spare arguments integer _po_use (( _po_use = _po_min + _po_spareargs )) # Restrict this if that's too many if (( _po_max >= 0 && _po_use > _po_max )) then _po_use=$_po_max fi # Recalculate how many arguments are spare for the next option (( _po_spareargs -= _po_use - _po_min )) # This shouldn't happen due to being checked above if (( _po_use < _po_min || $#_po_argv < _po_use )) then echo "Argument processing error" 1>&2 return 1 fi # Process each of them according to the type repeat $_po_use do _parse_opts_handletype $_po_argument 1 $_po_argv[1] || return shift _po_argv done done # If there's any surplus arguments that's an error if (( $#_po_argv )) then echo "Too many arguments" 1>&2 return 1 fi return 0 } # This function controls the overall processing # # NB. Locals in this function must begin _po_ _parse_opts_process() { local -a _po_arguments local -A _po_options _po_params _po_types local _po_helptext="" integer _po_minargs=0 # Check for arguments. If we have one and it is not '-' then it is the help # string. Otherwise need to to get that from stdin local _po_config="" if [[ -n $argv[1] && $argv[1] != - ]] then _po_config=$argv[1] else _po_config=$(cat) fi # Parse the default definitions for types etc. _parse_opts_setupdefinitions || return # Parse the help string we've got to determine the type of options _parse_opts_parsehelp $_po_config || return # Useful for debugging if (( _po_debug )) then _parse_opts_showdata _po_params echo _parse_opts_showdata _po_types echo echo "_po_options=(${(ok)_po_options})" echo "_po_arguments=($_po_arguments)" echo "_po_minargs=$_po_minargs" fi # Perform the option recognition if _parse_opts_dooptions then # If success, and help was requested then give it. Note that it is up to # the user to supply the help option before we can use it if (( _po_results[help] )) then echo $_po_helptext return 1 fi # Entry point for automatic zsh completion. We always return 1 in order to # get the calling program to exit. The completion system knows this is not # an error if (( _po_results[zsh-completion] )) then _parse_opts_generatecompletion return 1 fi # Perform the argument recognition if ! _parse_opts_doarguments then # There was an error in processing. Mention the --help option echo "Use --help for more information" 1>&2 return 1 fi else # There was an error in processing. Mention the --help option echo "Use --help for more information" 1>&2 return 1 fi return 0 } # This is the only entry point for the parse_opts system. # Parse the command line arguments to extract both long and short form # options. # # This function is a wrapper for _parse_opts_process to handle accessing the # arrays in the calling function's namespace # # NB. Locals in this function must begin _po_ parse_opts() { # Ensure options are in a known & useful state emulate -L zsh setopt extended_glob # Extract arguments passed to us local _po_ext_results="" _po_ext_argv="" _po_config="" [[ $argv[1] != -- ]] && _po_config=$argv[1] && shift [[ $argv[1] != -- ]] && _po_ext_results=$argv[1] && shift [[ $argv[1] != -- ]] && _po_ext_argv=$argv[1] && shift [[ $argv[1] == -- ]] && shift # Copy any current values of external arrays into our copies. This allows for # default state in the options, etc. local -a _po_argv local -A _po_results _po_argv=("$argv[@]") [[ -n $_po_ext_argv ]] && _po_argv=("${(@P)_po_ext_argv}") [[ -n $_po_ext_results ]] && _po_results=("${(@Pkv)_po_ext_results}") # Do the work. Only update the external arrays if we succeeded and they exist if _parse_opts_process $_po_config then [[ -n $_po_ext_argv ]] && eval $_po_ext_argv='("$_po_argv[@]")' [[ -n $_po_ext_results ]] && eval $_po_ext_results='("${(@kv)_po_results}")' else return 1 fi return 0 } parse_opts "$argv[@]" ----------END parse_opts ----------BEGIN _parse_opts # ZSH function file # Automatic completer for functions which use parse_opts # (C) 2001 Martin Ebourne # # See documentation with parse_opts for details on usage. # # $Id: _parse_opts,v 1.2 2001/08/17 13:31:44 mebourne Exp $ _parse_opts() { local command="$words[1]" # Return code is meaningless. We need to just check for stdout instead local toeval="$(_call_program "$command" "$command" --zsh-completion 2>/dev/null)" if [[ -n $toeval ]] then eval $toeval else _message "error executing '$command --zsh-completion'" fi } _parse_opts "$argv[@]" ----------END _parse_opts ----------BEGIN yes local -A opts parse_opts - opts -- "$argv[@]" <<'EOF' || return 1 Description: Repeatedly print a string Usage: yes [options] [ ...] Options: -e, --escape Interpret escape characters as for echo (default unless the BSD_ECHO option is set) # [echoopt] += --escape | -e : constant=-e -E, --no-escape Prevent interpretation of escape characters # [echoopt] += --no-escape | -E : constant=-E -h, --help Provide this help # --help | -h -l , --lines= Output only count times # --lines | -l : posinteger -n, --no-newline Suppress output of automatic newline # [echoopt] += --no-newline | -n : constant=-n -s , --sleep= Pause for number of seconds between each echo # --sleep | -s : posinteger Arguments: [ ...] Optional text to print. Defaults to 'yes' # [text] += [*] text EOF # Output as required while (( ${opts[lines]:-1} )) do echo $=opts[echoopt] ${opts[text]:-yes} (( opts[sleep] )) && sleep $opts[sleep] (( opts[lines] && opts[lines]-- )) done ----------END yes This e-mail message is CONFIDENTIAL and may contain legally privileged information. If you are not the intended recipient you should not read, copy, distribute, disclose or otherwise use the information in this e-mail. Please also telephone or fax us immediately and delete the message from your system. E-mail may be susceptible to data corruption, interception and unauthorised amendment, and we do not accept liability for any such corruption, interception or amendment or the consequences thereof.