diff -r 22d872615893 -r 64b3e323210f scripts/settings.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scripts/settings.py Fri May 16 19:46:16 2008 +0000 @@ -0,0 +1,164 @@ +#!/usr/bin/python2.5 +# +# Copyright 2008 the Melange authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Custom optparse OptionParser and functions for reading Python settings files. + + Option: class derived from optparse.Option that adds a 'required' parameter + OptionParser: class derived from optparse.OptionParser for use with Option + + readPythonSettings(): interprets a valid Python file as a settings file +""" + +__authors__ = [ + # alphabetical order by last name, please + '"Todd Larsen" ', +] + + +import os +import optparse +import sys + + +DEF_SETTINGS_FILE_DIR = "~" +DEF_SETTINGS_FILE_NAME = '.soc_scripts_settings' + + +class Error(Exception): + """Base exception class for all exceptions in the settings module.""" + pass + + +class Option(optparse.Option): + """Class derived from optparse.Option that adds a 'required' parameter.""" + + ATTRS = optparse.Option.ATTRS + ['required'] + + def _check_required(self): + """Insures that 'required' option can accept a value.""" + if self.required and (not self.takes_value()): + raise optparse.OptionError( + "'required' parameter set for option that does not take a value", + self) + + # Make sure _check_required() is called from the constructor! + CHECK_METHODS = optparse.Option.CHECK_METHODS + [_check_required] + + def process(self, opt, value, values, parser): + optparse.Option.process(self, opt, value, values, parser) + parser.option_seen[self] = 1 + + +class OptionParser(optparse.OptionParser): + """Class derived from optparse.OptionParser for use with Option.""" + + def _init_parsing_state(self): + """Sets up dict to track options seen so far.""" + optparse.OptionParser._init_parsing_state(self) + self.option_seen = {} + + def error(self, *args): + """Convert errors reported by optparse.OptionParser to Error exceptions. + + Args: + *args: passed through to the Error exception __init__() constructor, + usually a list of strings + + Raises: + Error with the supplied *args + """ + raise Error(*args) + + def check_values(self, values, args): + """Checks to make sure all required=True options were supplied. + + Args: + values, args: passed through unchanged (see Returns:) + + Returns: + (values, args) unchanged. + + Raises: + Error if an option was not supplied that had required=True; exception + positional arguments are the error message strings. + """ + errors = [] + + for option in self.option_list: + if (isinstance(option, Option) + and option.required + and (not self.option_seen.has_key(option))): + errors.append( + 'required %s option not supplied' % option) + + if errors: + self.error(*errors) + + return values, args + + +def readPythonSettings(defaults={}, # {} OK since defaults is always copied + settings_dir=DEF_SETTINGS_FILE_DIR, + settings_file=DEF_SETTINGS_FILE_NAME): + """Executes a Python-syntax settings file and returns the local variables. + + Args: + defaults: dict of default values to use when settings are not present + in the settings file (or if no settings file is present at all); this + dict is *copied* and is not altered at all + settings_dir: optional directory containing settings_file + settings_file: optional settings file name found in settings_dir + + Returns: + dict of setting name/value pairs (possibly including some values from the + defaults parameter). Since the settings file is full-fledged Python + source, the values could be any valid Python object. + + Raises: + Error if some error occurred parsing the present settings file; exception + positional arguments are the error message strings. + """ + # do not let the original defaults be altered + defaults = defaults.copy() + + # form absolute path to the settings file, expanding any environment + # variables and "~", then removing excess . and .. path elements + path = os.path.abspath( + os.path.normpath( + os.path.expanduser( + os.path.expandvars( + os.path.join(settings_dir, settings_file))))) + + # empty dict to capture the local variables in the settings file + settings_locals = {} + + try: + # execute the Python source file and recover the local variables as settings + execfile(path, {}, settings_locals) + except IOError: + # If the settings file is not present, there are no defaults. + pass + except Exception, error: + # Other exceptions usually mean a faulty settings file. + raise Error( + 'faulty settings file:', + (' %s: %s' % (error.__class__.__name__, str(error))), + (' %s' % path)) + + # overwrite defaults copy with values from the (possibly empty) settings file + defaults.update(settings_locals) + + return defaults