"""Handle command line arguments.

This module contains functions to parse and access the arguments given
to the program on the command line.
"""

import types
import string
import sys

# Symbolic constants for the indexes into an argument specifier tuple.
NAME        = 0
MANDATORY   = 1
TYPE        = 2
HELP        = 3
DEFAULT     = 4
SPEC_LENGTH = 5

Bool = []

helpSpec = (
  ('help',            0, Bool,   'print help and exit'),
)

def parseArgs(title, argv, argSpecs, filesOK):
  """Parse and check command line arguments.

  Scan the command line arguments in *argv* according to the argument
  specifier *argSpecs*. Return **None** if there are no errors in
  the arguments, otherwise return an error string describing the error.

  This function must be called to initialise this module.

  title    -- The name of the program. This is used when returning
              error messages or help text.

  argv     -- A sequence containing the arguments given to the program.
              Normally **sys.argv**.

  argSpecs -- A sequence of argument specifiers.  Each specifier describes
              a valid command line argument and consists of 4 or 5 items:

	      - The argument name (without a leading minus sign **-**).

	      - A boolean value, true if the argument is mandatory.

	      - This should be **Args.Bool** if the argument has no option.
		Otherwise it should be a string describing the option
		required for this argument. This is used when printing help.

	      - A short string describing the argument.

	      - The default value of the argument.  This should only be used
	        for non-mandatory arguments expecting an option.
	      
	      For example:
		(
		  ('foreground', 0, 'colour',    'colour of text', 'black'),
		  ('geometry',   0, 'spec',      'geometry of initial window'),
		  ('server',     1, 'ompserver', 'ompserver to connect to'),
		  ('silent',     0, Args.Bool,   'do not sound bell'),
		)
  """

  global programName
  global _fileList

  errMsg = title + ' command line error: '
  programName = argv[0];

  argSpecs = helpSpec + argSpecs
  argSpecDic = {}
  for spec in argSpecs:
    arg = spec[NAME]
    argSpecDic[arg] = spec
    if len(spec) >= SPEC_LENGTH:
      set(arg, spec[DEFAULT])
    elif spec[TYPE] is Bool:
      set(arg, 0)
    else:
      set(arg, None)

  knownKeys = argSpecDic.keys()

  i = 1
  _fileList = []
  argc = len(argv)
  while i < argc:
    arg = argv[i]
    key = arg[1:]
    if key in knownKeys:
      spec = argSpecDic[key]
      if spec[TYPE] is Bool:
	set(key, 1)
      else:
	i = i + 1
	if i >= argc:
	  return errMsg + 'missing argument to \'' + arg + '\' option.'
	value = argv[i]
	if len(spec) >= SPEC_LENGTH:
	  try:
	      if type(spec[DEFAULT]) == types.IntType:
		  typeStr = 'integer'
		  value = string.atoi(value)
	      elif type(spec[DEFAULT]) == types.FloatType:
		  typeStr = 'float'
		  value = string.atof(value)
	  except:
	      sys.exc_traceback = None   # Clean up object references
	      return errMsg + 'cannot convert string \'' + value + \
	        '\' to ' + typeStr + ' for option \'-' + key + '\'.'
	set(key, value)
    else:
      _fileList.append(arg)
    i = i + 1

  if get('help'):
    return _helpString(title, argSpecs)

  if not filesOK and len(_fileList) > 0:
    if len(_fileList) == 1:
      return errMsg + 'unknown option \'' + str(_fileList[0]) + '\'.'
    else:
      return errMsg + 'unknown options ' + str(_fileList) + '.'


  _missing = []
  for spec in argSpecs:
    if spec[MANDATORY] and get(spec[NAME]) is None:
      _missing.append(spec[NAME])
  if len(_missing) == 1:
    return errMsg + 'required argument \'-' + \
      str(_missing[0]) + '\' is missing.'
  elif len(_missing) > 1:
    return errMsg + 'required arguments ' + \
      str(map(lambda s: '-' + s, _missing)) + ' are missing.'

  return None

def fileList():
  return _fileList

def _helpString(title, argSpecs):
  max = 0
  for spec in argSpecs:
    if spec[TYPE] is Bool:
      width = len(spec[NAME]) + 1
    else:
      width = len(spec[NAME]) + 4 + len(spec[TYPE])
    if width > max:
      max = width

  rtn = title + ' command line arguments:'
  format = '\n  %-' + str(max) + 's %s'
  for mandatory in (1, 0):
    needHeader = 1
    for spec in argSpecs:
      if mandatory and spec[MANDATORY] or not mandatory and not spec[MANDATORY]:
	if needHeader:
	  if mandatory:
	    rtn = rtn + '\n Mandatory arguments:'
	  else:
	    rtn = rtn + '\n Optional arguments (defaults in parentheses):'
	  needHeader = 0
	if spec[TYPE] is Bool:
	  arg = '-%s' % spec[NAME]
	else:
	  arg = '-%s <%s>' % (spec[NAME], spec[TYPE])
	if len(spec) >= SPEC_LENGTH:
	  if type(spec[DEFAULT]) == types.StringType:
	      definition = spec[HELP] + ' (' + spec[DEFAULT] + ')'
	  else:
	      definition = spec[HELP] + ' (' + str(spec[DEFAULT]) + ')'
	else:
	  definition = spec[HELP]
	rtn = rtn + format % (arg, definition)

  return rtn

def exists(key):
    return configDict.has_key(key)

def get(key):
    return configDict[key]

def set(key, value):
    global configDict

    configDict[key] = value

configDict = {}
