#
# $Header: /usr/local/cvsroot/pythondoc/options.py,v 1.2 1999/05/01 01:06:08 daniel Exp $
#
# Copyright (C) Daniel Larsson
# All Rights Reserved.
#
# See copyright notice in the file 'LICENSE.TXT', which should have accompanied
# this distribution.
#

"""Options database.

Extension packages can register command line options here.
"""

__author__ = "Daniel Larsson, Daniel.Larsson@telia.com"
__version__ = "$Revision: 1.2 $"[11:-2]


import exceptions

class AlreadyRegisteredOption(exceptions.Exception):
    pass

import types

class Options:
    """Manages an option database for extensions.

    An option database contains a list of available options for
    a list of extensions. These options can be given values using
    either the option name, or <extension name>_<option name> (see
    [add_value].

    .. [add_value] add_value
    """

    BOOL, INT, STRING, FILE, DIR, URL, ENUM = range(7)

    _typecheck = {
	BOOL: (types.IntType, 'bool'),
	INT: (types.IntType, 'int'),
	STRING: (types.StringType, 'string'),
	FILE: (types.StringType, 'file path'),
	DIR: (types.StringType, 'directory path'),
	URL: (types.StringType, 'URL'),
	ENUM: (types.StringType, 'Enumeration')
	}

    def __init__(self):
	self.options = {}
	self.values = {}

    def add_option(self, extension, option, type,
		   description, default, values=None):
	"""Adds an available option to the database.

	Arguments:

	  extension -- name of the extension adding the option

	  option -- name of the option

	  type -- type of the option (BOOL, INT, STRING, FILE, DIR, URL or ENUM)

	  description -- descriptive text

	  default -- default value

	  values -- a sequence of available values for ENUM type
	"""
	if not self.options.has_key(extension):
	    self.options[extension] = {}

	if self.options[extension].has_key(option):
	    from message import error
	    error("Extension %s has already registered an option %s" \
		  % (extension, option))
	    raise AlreadyRegisteredOption(extension, option, description)

	# Check if another extension has this option
	for ext, options in self.options.items():
	    if options.has_key(option):
		from message import warning
		warning("There is already an option %s present (%s: %s)" \
			% ((option, ext, options[option][0])))

	self.options[extension][option] = (type, description, default, values)

    def add_value(self, option, value):
	"""Adds a value for an option."""
	found_value = 0
	for extension, options in self.options.items():
	    for opt, (typ, description, default, values) in options.items():
		if option == opt or option == "%s_%s" % (extension, opt):
		    self.values[extension] = self.values.get(extension, {})
		    # "<ext>_<opt>" has higher priority...
		    if not self.values[extension].has_key(opt) or \
		       option != opt:
			found_value = 1
			pytype = self._typecheck[typ][0]

			# Make sure value is of correct type, and
			# if this is an enum, is a valid value
			if type(value) == pytype and \
			   (not values or value in values):
			    self.values[extension][opt] = value
			else:
			    print pytype, type(value)
			    raise TypeError("Not a matching type")
	if not found_value:
	    raise KeyError, "No such option: %s" % option

    def read_value(self, extension, option):
	"""Returns the value of the given option.

	Returns a tuple, where the first element is a boolean
	indicating whether this is the default value (0) or given
	using [add_value] (1), and the second element contains
	the value.

	.. [add_value] add_value
	"""
	try:
	    return (1, self.values[extension][option])
	except KeyError:
	    return (0, self.options[extension][option][2])

    def __str__(self):
	s = ''
	for extension, options in self.options.items():
	    for option, (type, description, default, vals) in options.items():
		type = Options._typecheck[type][1]
		if vals:
		    values = ', valid values: ' + str(vals)
		else:
		    values = ''
		s = s + " %s -- %s: %s (type: %s, default: %s%s)\n\n" % \
		    (option, extension, description, type, default, values)
	return s

_options = Options()

add_option = _options.add_option

def options():
    return _options

class OptionAssigner:
    def __init__(self, options):
	self.__dict__['options'] = options

    def __setattr__(self, attr, value):
	import message
	message.information("Setting option %s to %s" % (attr, value))
	self.options.add_value(attr, value)



def read_options(filename):
    """Read options from given file.

    The file is interpreted as a standard Python source file, and the
    initial namespace contains a variable called 'options', which
    is an instance of the [OptionAssigner] class.

    Do 'pythondoc -h' to learn what options are available, and look
    in '<pythondoc package dir>/pythondoc.opts' to learn how to
    set options.

    .. [OptionAssigner] OptionAssigner
    """
    ns = {'options': OptionAssigner(options())}
    _execfile(filename, ns)

def read_default_options(filename):
    """Read options from the default files.

    Looks for a file with given name both in /etc/pythondoc
    and the user's home directory. The file
    is interpreted as a standard Python source file, and the
    initial namespace contains a variable called 'options', which
    is an instance of the [OptionAssigner] class.

    .. [OptionAssigner] OptionAssigner
    """
    import os
    #read_options(os.path.join(os.path.dirname(__file__), filename))
    read_options(os.path.join("/etc/pythondoc", filename))
    if os.environ.has_key('HOME'):
	d = os.environ['HOME']
	read_options(os.path.join(d, filename))

def _execfile(filename, globals={}, locals={}):
    try:
	import message
	message.information("Trying to read option file %s" % filename)
	execfile(filename, globals, locals)
    except IOError:
	pass
    except KeyError, msg:
	import sys, traceback
	extract = traceback.extract_tb(sys.exc_traceback)
	extract.reverse()
	for entry in extract:
	    if entry[0] == filename:
		traceback.print_list([entry])
		traceback.print_exception(sys.exc_type, sys.exc_value, None)
	print options()

def store_options(filename):
    "Not yet implemented..."
    for ext, options in _options.values.items():
	for option, value in options.items():
	    print "options.%s_%s = %s" % (ext, option, repr(value))

def test():
    opt = Options()
    opt.add_option("foo", "bar", opt.STRING, 'testing...', 1)
    opt.add_option("foo", "tee", opt.STRING, 'testing 2...', 2)
    opt.add_option("bar", "bar", opt.STRING, 'testing...', 3)
    opt.add_value('bar_bar', 'Hiho')
    opt.add_value('bar', 'Hello')
    print opt
    print opt.read_value('foo', 'bar')
    print opt.read_value('foo', 'tee')
    print opt.read_value('bar', 'bar')

if __name__ == "__main__":
    test()

#
# $Log: options.py,v $
# Revision 1.2  1999/05/01 01:06:08  daniel
# Removed Windows style line endings.
#
# 
# *****************  Version 5  *****************
# User: Daniel       Date: 98-12-13   Time: 16:27
# Updated in $/Pythondoc
# New email address.
# 
# *****************  Version 4  *****************
# User: Daniel       Date: 98-10-04   Time: 19:39
# Updated in $/Pythondoc
# Splitted 'read_options' into two separate functions.
# 
# *****************  Version 3  *****************
# User: Daniel       Date: 98-08-11   Time: 20:55
# Updated in $/Pythondoc
# Added ENUM type.
# 'add_value' now raises an exception if no matching option was found.
# Added code to read options from a file.
# Should add code to let the user choose an option file later...
# 
# *****************  Version 2  *****************
# User: Daniel       Date: 98-08-06   Time: 17:21
# Updated in $/Pythondoc
# Added 'DIR' as possible option type.
# Fixed a bug where two variables had the same name, causing big
# confusion...
# 
