Zsh Mailing List Archive
Messages sorted by: Reverse Date, Date, Thread, Author

New options & arguments processing system for ZSH



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=<dir>' and `--directory <dir>'
#   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
#   <help text goes here>
#   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> [<results> [<argv>]]] [--] [<arguments> ...]
#
# 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] <filename-stem>
#
#   Options:
#     -h, --help         Provide this help
#                   # --help | -h
#
#   Arguments:
#     <filename-stem>         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.
#
#   `# ' [ <tag-part> ( ` = ' | ` += ' ) ] <option-part>
#   [ ` : ' <type-part> ] [ ` * ' <settings-part> ]
#
# 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 <option-part> 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:
#   `[' <key-name> `]'
# or
#   <variable-name>
#
# eg.
#   [name]
#   an_array
#   associative_array
#   associative_array[name]
#
# <key-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 <tag-part> is not provided, then it will default to using <key-name> of
# the first option or argument name in the <option-part> 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
# <option-part>. 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.
#
#   [ `[' <frequency> `] ' ] <option-name> [ ` | ' <option-name> ... ]
#
# eg.
#   --help | -h
#   [*] --directory | -d
#   [0,1] filename
#
# <option-name> gives the name of the option, as it will appear on the command
# line. eg. `-h' or `--help'. Multiple <option-name>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).
#
# <option-name> may consist of `-', `_', and alphanumeric characters only.
#
# <frequency> is of the form:
#   <count>
# or
#   `*'
# or
#   <min-count> `,' <max-count>
# or
#   <min-count> `,*'
# or
#   `*,' <max-count>
#
# Where <count>, <min-count>, and <max-count> are all positive integers. These
# give the allowable range of the number of occurrences of the option or
# argument. If only <count> is provided, then <min-count> and <max-count> 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.
#
#   [ `? ' ] <type-name> [ `=' <value> ]
#
# 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.
#
# <type-name> is one of:
#
# switch   - (2) Simple switch. Result will be 1. Default for options
# constant - (1) (2) Constant to be assigned. Result will be <value> given
# string   - Takes a parameter with no validation. Default for arguments
# values   - (1) Takes a parameter validated against the supplied <value>
#            interpreted as some kind of list of values. See below for a more
#            detailed explanation
# pattern  - (1) Takes a parameter validated against the supplied <value>
#            which will be interpreted as a shell glob pattern (with the
#            extended_glob option set)
# exec     - (1) Takes a parameter validated by executing the <value>
#            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 <value>). 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 <value>)
#
# (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 <value> before any of the following.
#
# If the <value> 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 <value> 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.
#
#   <setting-name> [ `=' <value> ] [ ` ' ... ]
#
# eg.
#   complete.hidden
#   excludes="-L --no-list"
#
# <setting-name> 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
#             <option-name>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 ' <type-name> ` ' <base-part> [ [ ` ' <description> ] ` ' <action> ]
#   [ ` * ' <settings-part> ]
#
# eg.
#   #type integer   pattern=([-+]|)<->      "signed integer number"  " "
#   #type function  values=${(k)functions}  "shell function"         _functions
#
# <type-name> is the identifier for this type to be assigned to. This is what
# appears as <type-name> in option/argument type sections, or as a base type
# to another user defined type.
#
# <type-name> may consist of `-', `_', and alphanumeric characters only.
#
# <base-part> is of the form:
#   <base-type-name> [ `=' <value> ]
#
# <base-type-name> and <value> 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 <value> must not be supplied).
#
# <description> 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.
#
# <action> 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 <description> and <action> 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.
#
# <settings-part> 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 [<n>,*]
#                        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] [<text> ...]
#
#   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 <count>, --lines=<count>
#                   Output only count times
#                   # --lines | -l : integer
#     -n, --no-newline        Suppress output of automatic newline
#                   # [echoopt] += --no-newline | -n : constant=-n
#     -s <seconds>, --sleep=<seconds>
#                   Pause for number of seconds between each echo
#                   # --sleep | -s : integer
#
#   Arguments:
#     [<text> ...]       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] [<text> ...]

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 <count>, --lines=<count> Output only count times
                    # --lines | -l : posinteger
  -n, --no-newline       Suppress output of automatic newline
                    # [echoopt] += --no-newline | -n : constant=-n
  -s <seconds>, --sleep=<seconds>
                    Pause for number of seconds between each echo
                    # --sleep | -s : posinteger

Arguments:
  [<text> ...]           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.



Messages sorted by: Reverse Date, Date, Thread, Author