# OVERVIEW:
#
# MPDgetopt provides a general means for an MPD program to parse command
# line arguments in accordance with the standard Unix conventions;
# it is analogous to, and based on, getopt(3) for C programs.
# (The following documentation is based on the man page for getopt(3).)

# DESCRIPTION:
#
# MPDgetopt is an MPD global that provides one procedure, getopt,
# and some variables that control behavior of or return additional
# information from getopt.
#
# getopt interprets command arguments in accordance with the standard
# Unix conventions: option arguments of a command are introduced by "-"
# followed by a key character, and a non-option argument terminates
# the processing of options.  getopt's option interpretation is controlled
# by its parameter optstring, which specifies what characters designate
# legal options and which of them require associated values.
#
# getopt returns the next, moving left to right, option letter
# in the command line arguments that matches a letter in optstring.
# optstring must contain the option letters the command using getopt
# will recognize.  For example, getopt("ab") specifies that the command
# line should contain no options, only "-a", only "-b", or both "-a" and
# "-b" in either order.  (The command line can also contain non-option
# arguments after any option arguments.)  Multiple options per argument
# are allowed, e.g., "-ab" for the last case above.
#
# If a letter in optstring is followed by a colon, the option is expected
# to have an argument.  The argument may or may not be separated by
# whitespace from the option letter.  For example, getopt("w:") allows
# either "-w 80" or "-w80".  The variable optarg is set to the option
# argument, e.g., "80" in either of the previous examples.  Predefined
# conversion functions such as int, char, etc. can then be applied to
# optarg.  (Use optarg[1] instead of char(optarg) if the first blank or
# non-blank character is wanted.)  The constant optMAXLEN defines the
# length of the longest string that getopt can read from the command line
# (extra characters are truncated silently) or will return in optarg.
#
# getopt places in the variable optind the index of the next command
# line argument to be processed; optind is automatically initialized
# to 1 before the first call to getopt.
#
# When all options have been processed (that is, up to the first
# non-option argument), getopt returns optEOF.  getopt recognizes the
# command line argument "--" (i.e., two dashes) to delimit the end of
# the options; getopt returns optEOF and skips "--".  Subsequent,
# non-option arguments can be retrieved using the predefined
# function getarg, beginning with argument number optind.

# DIAGNOSTICS:
#
# getopt prints an error message on stderr and returns a question mark
# ('?') when it encounters an option letter in a command line argument
# that is not included in optstring.  Setting the variable opterr to
# false disables this error message.

# NOTES:
#
# The following notes describe MPDgetopt's behavior in a few interesting
# or special cases; these behaviors are consistent with getopt(3)'s behaviors.
#
# -- A '-' by itself is treated as a non-option argument.
# -- If optstring is "a:" and the command line arguments are "-a -x",
#    then "-x" is treated as the argument associated with the "-a".
# -- Duplicate command line options are allowed; it is up to user to
#    deal with them as appropriate.
# -- A command line option like "-b-" is considered as the two options
#    "b" and "-" (so "-" should appear in option string); this differs
#    from "-b --".
# -- Sun and DEC getopt(3)'s differ w.r.t. how "---" is handled.
#    Sun treats "---" (or anything starting with "--") the same as "--"
#    DEC treats "---" as two separate "-" options
#    (so "-" should appear in option string).
#    MPDgetopt follows the DEC convention.
# -- An option `letter' can be a letter, number, or most special character.
#    Like getopt(3), MPDgetopt disallows a colon as an option letter.

# EXAMPLE:
#
# The following code fragment shows how to use MPDgetopt to
# process a command that takes the options 'a', 'f', and 'w'
# where 'f' is followed by a file name and 'w' is followed by an integer.

/****
  resource main()
      import MPDgetopt
      var ch: char
      # command line arguments
      var aflg := 0
      var filename: string[optMAXLEN] := "out"
      var width := 80
      do (ch := getopt("abf:w:")) != optEOF ->
         if ch = 'a' ->
            aflg++
         [] ch = 'f' ->
            filename := optarg
         [] ch = 'w' ->
            width := int(optarg)
         [] else ->
            stop(1)
         fi
      od
      write("-a", aflg)
      write("-f", filename)
      write("-w", width)
      fa k := optind to numargs() ->
         var xx: string[40]
         getarg(k,xx)
         write("normal argument", k, "is", xx)
      af
   end
****/

# SEE ALSO:
#
# getopt(3)

# WARNINGS:
#
# Changing the value of the variable optind may lead to unexpected behavior.
# getopt, like the predefined functions numargs and getarg, is valid only
# from the main virtual machine.


global MPDgetopt

   int optind = 1
   bool opterr = true

   # maximum length of an argument
   # (it is a const, not a var, so not redefinable by user,
   # because optarg declaration must be here in spec.)
   const int optMAXLEN = 256

   string[optMAXLEN] optarg

   # optEOF is a var so user can redefine it if desired.
   # (also, as a const, it uncovered a bug in 2.2.1
   # which requires a patch to fold.c (94-04-06)).

   char optEOF = char(EOF)

   op getopt(string[*] optstring) returns char

body MPDgetopt

   procedure write_error(string[*] msg, char ch) {
      string[optMAXLEN] cmd
      getarg(0, cmd)
      writes(stderr, cmd, ": ", msg, " -- ", ch, "\n")
   }

   const int argc = numargs()
   int optpos = 2

   proc getopt(optstring) returns ch {
      optarg = ""
      if (optind < 1 | optind > argc) {
         ch = optEOF
         return
      }
      string[optMAXLEN] thisarg
      getarg(optind, thisarg)
      int len = length(thisarg)
      # handle special cases
      if (len <= 1 | thisarg[1] != '-') {
         # e.g., "", "a", "abc", or just "-"
         ch = optEOF
         return
      } else if (thisarg == "--") {
         # end of non-option args
         ch = optEOF
	 optind++
         return
      }
      # get next "letter" from option argument
      ch = thisarg[optpos]
      # find this option in optstring
      bool found = false
      int pos
      for [ x = 1 to length(optstring) ] {
         if (optstring[x] == ch) {
            found = true
            pos = x
            exit
         }
      }
      if (not found | ch == ':') {
         if (opterr) {
            write_error("illegal option", ch)
         }
         ch = '?'
      } else {
         # handle colon, if present
         if (pos < length(optstring) & optstring[pos+1] == ':') {
            if (optpos != len) {
               # take rest of current arg as optarg
               optarg = thisarg[optpos+1:*]
               optpos = len	# to force advance to next arg below
            } else {
               # take next arg as optarg
               optind++
               if (optind <= argc) {
                  getarg(optind,optarg)
               } else {
                  if (opterr) {
                     write_error("option requires an argument", ch)
                  }
                  ch = '?'
               }
            }
         }
      }
      # advance to next option argument,
      # which might be in thisarg or next arg
      optpos++
      if (optpos > len) {
         optind++
         optpos = 2
      }
   }

end
