| Index: third_party/logilab/common/configuration.py
|
| ===================================================================
|
| --- third_party/logilab/common/configuration.py (revision 292986)
|
| +++ third_party/logilab/common/configuration.py (working copy)
|
| @@ -1,4 +1,4 @@
|
| -# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
|
| +# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
|
| # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
|
| #
|
| # This file is part of logilab-common.
|
| @@ -96,8 +96,19 @@
|
| multiple=4,5,6
|
|
|
| number=3
|
| - >>>
|
| +
|
| + Note : starting with Python 2.7 ConfigParser is able to take into
|
| + account the order of occurrences of the options into a file (by
|
| + using an OrderedDict). If you have two options changing some common
|
| + state, like a 'disable-all-stuff' and a 'enable-some-stuff-a', their
|
| + order of appearance will be significant : the last specified in the
|
| + file wins. For earlier version of python and logilab.common newer
|
| + than 0.61 the behaviour is unspecified.
|
| +
|
| """
|
| +
|
| +from __future__ import print_function
|
| +
|
| __docformat__ = "restructuredtext en"
|
|
|
| __all__ = ('OptionsManagerMixIn', 'OptionsProviderMixIn',
|
| @@ -109,16 +120,17 @@
|
| import re
|
| from os.path import exists, expanduser
|
| from copy import copy
|
| -from ConfigParser import ConfigParser, NoOptionError, NoSectionError, \
|
| - DuplicateSectionError
|
| from warnings import warn
|
|
|
| -from logilab.common.compat import callable, raw_input, str_encode as _encode
|
| +from six import string_types
|
| +from six.moves import range, configparser as cp, input
|
|
|
| +from logilab.common.compat import str_encode as _encode
|
| +from logilab.common.deprecation import deprecated
|
| from logilab.common.textutils import normalize_text, unquote
|
| -from logilab.common import optik_ext as optparse
|
| +from logilab.common import optik_ext
|
|
|
| -OptionError = optparse.OptionError
|
| +OptionError = optik_ext.OptionError
|
|
|
| REQUIRED = []
|
|
|
| @@ -136,12 +148,15 @@
|
|
|
| # validation functions ########################################################
|
|
|
| +# validators will return the validated value or raise optparse.OptionValueError
|
| +# XXX add to documentation
|
| +
|
| def choice_validator(optdict, name, value):
|
| """validate and return a converted value for option of type 'choice'
|
| """
|
| if not value in optdict['choices']:
|
| msg = "option %s: invalid value: %r, should be in %s"
|
| - raise optparse.OptionValueError(msg % (name, value, optdict['choices']))
|
| + raise optik_ext.OptionValueError(msg % (name, value, optdict['choices']))
|
| return value
|
|
|
| def multiple_choice_validator(optdict, name, value):
|
| @@ -148,51 +163,51 @@
|
| """validate and return a converted value for option of type 'choice'
|
| """
|
| choices = optdict['choices']
|
| - values = optparse.check_csv(None, name, value)
|
| + values = optik_ext.check_csv(None, name, value)
|
| for value in values:
|
| if not value in choices:
|
| msg = "option %s: invalid value: %r, should be in %s"
|
| - raise optparse.OptionValueError(msg % (name, value, choices))
|
| + raise optik_ext.OptionValueError(msg % (name, value, choices))
|
| return values
|
|
|
| def csv_validator(optdict, name, value):
|
| """validate and return a converted value for option of type 'csv'
|
| """
|
| - return optparse.check_csv(None, name, value)
|
| + return optik_ext.check_csv(None, name, value)
|
|
|
| def yn_validator(optdict, name, value):
|
| """validate and return a converted value for option of type 'yn'
|
| """
|
| - return optparse.check_yn(None, name, value)
|
| + return optik_ext.check_yn(None, name, value)
|
|
|
| def named_validator(optdict, name, value):
|
| """validate and return a converted value for option of type 'named'
|
| """
|
| - return optparse.check_named(None, name, value)
|
| + return optik_ext.check_named(None, name, value)
|
|
|
| def file_validator(optdict, name, value):
|
| """validate and return a filepath for option of type 'file'"""
|
| - return optparse.check_file(None, name, value)
|
| + return optik_ext.check_file(None, name, value)
|
|
|
| def color_validator(optdict, name, value):
|
| """validate and return a valid color for option of type 'color'"""
|
| - return optparse.check_color(None, name, value)
|
| + return optik_ext.check_color(None, name, value)
|
|
|
| def password_validator(optdict, name, value):
|
| """validate and return a string for option of type 'password'"""
|
| - return optparse.check_password(None, name, value)
|
| + return optik_ext.check_password(None, name, value)
|
|
|
| def date_validator(optdict, name, value):
|
| """validate and return a mx DateTime object for option of type 'date'"""
|
| - return optparse.check_date(None, name, value)
|
| + return optik_ext.check_date(None, name, value)
|
|
|
| def time_validator(optdict, name, value):
|
| """validate and return a time object for option of type 'time'"""
|
| - return optparse.check_time(None, name, value)
|
| + return optik_ext.check_time(None, name, value)
|
|
|
| def bytes_validator(optdict, name, value):
|
| """validate and return an integer for option of type 'bytes'"""
|
| - return optparse.check_bytes(None, name, value)
|
| + return optik_ext.check_bytes(None, name, value)
|
|
|
|
|
| VALIDATORS = {'string': unquote,
|
| @@ -222,14 +237,18 @@
|
| except TypeError:
|
| try:
|
| return VALIDATORS[opttype](value)
|
| - except optparse.OptionValueError:
|
| + except optik_ext.OptionValueError:
|
| raise
|
| except:
|
| - raise optparse.OptionValueError('%s value (%r) should be of type %s' %
|
| + raise optik_ext.OptionValueError('%s value (%r) should be of type %s' %
|
| (option, value, opttype))
|
|
|
| # user input functions ########################################################
|
|
|
| +# user input functions will ask the user for input on stdin then validate
|
| +# the result and return the validated value or raise optparse.OptionValueError
|
| +# XXX add to documentation
|
| +
|
| def input_password(optdict, question='password:'):
|
| from getpass import getpass
|
| while True:
|
| @@ -237,23 +256,23 @@
|
| value2 = getpass('confirm: ')
|
| if value == value2:
|
| return value
|
| - print 'password mismatch, try again'
|
| + print('password mismatch, try again')
|
|
|
| def input_string(optdict, question):
|
| - value = raw_input(question).strip()
|
| + value = input(question).strip()
|
| return value or None
|
|
|
| def _make_input_function(opttype):
|
| def input_validator(optdict, question):
|
| while True:
|
| - value = raw_input(question)
|
| + value = input(question)
|
| if not value.strip():
|
| return None
|
| try:
|
| return _call_validator(opttype, optdict, None, value)
|
| - except optparse.OptionValueError, ex:
|
| + except optik_ext.OptionValueError as ex:
|
| msg = str(ex).split(':', 1)[-1].strip()
|
| - print 'bad value: %s' % msg
|
| + print('bad value: %s' % msg)
|
| return input_validator
|
|
|
| INPUT_FUNCTIONS = {
|
| @@ -264,6 +283,8 @@
|
| for opttype in VALIDATORS.keys():
|
| INPUT_FUNCTIONS.setdefault(opttype, _make_input_function(opttype))
|
|
|
| +# utility functions ############################################################
|
| +
|
| def expand_default(self, option):
|
| """monkey patch OptionParser.expand_default since we have a particular
|
| way to handle defaults to avoid overriding values in the configuration
|
| @@ -278,15 +299,15 @@
|
| value = None
|
| else:
|
| optdict = provider.get_option_def(optname)
|
| - optname = provider.option_name(optname, optdict)
|
| + optname = provider.option_attrname(optname, optdict)
|
| value = getattr(provider.config, optname, optdict)
|
| value = format_option_value(optdict, value)
|
| - if value is optparse.NO_DEFAULT or not value:
|
| + if value is optik_ext.NO_DEFAULT or not value:
|
| value = self.NO_DEFAULT_VALUE
|
| return option.help.replace(self.default_tag, str(value))
|
|
|
|
|
| -def convert(value, optdict, name=''):
|
| +def _validate(value, optdict, name=''):
|
| """return a validated value for an option according to its type
|
|
|
| optional argument name is only used for error message formatting
|
| @@ -297,7 +318,10 @@
|
| # FIXME
|
| return value
|
| return _call_validator(_type, optdict, name, value)
|
| +convert = deprecated('[0.60] convert() was renamed _validate()')(_validate)
|
|
|
| +# format and output functions ##################################################
|
| +
|
| def comment(string):
|
| """return string as a comment"""
|
| lines = [line.strip() for line in string.splitlines()]
|
| @@ -346,7 +370,7 @@
|
| value = value.pattern
|
| elif optdict.get('type') == 'yn':
|
| value = value and 'yes' or 'no'
|
| - elif isinstance(value, (str, unicode)) and value.isspace():
|
| + elif isinstance(value, string_types) and value.isspace():
|
| value = "'%s'" % value
|
| elif optdict.get('type') == 'time' and isinstance(value, (float, int, long)):
|
| value = format_time(value)
|
| @@ -358,8 +382,8 @@
|
| """format an options section using the INI format"""
|
| encoding = _get_encoding(encoding, stream)
|
| if doc:
|
| - print >> stream, _encode(comment(doc), encoding)
|
| - print >> stream, '[%s]' % section
|
| + print(_encode(comment(doc), encoding), file=stream)
|
| + print('[%s]' % section, file=stream)
|
| ini_format(stream, options, encoding)
|
|
|
| def ini_format(stream, options, encoding):
|
| @@ -369,20 +393,20 @@
|
| help = optdict.get('help')
|
| if help:
|
| help = normalize_text(help, line_len=79, indent='# ')
|
| - print >> stream
|
| - print >> stream, _encode(help, encoding)
|
| + print(file=stream)
|
| + print(_encode(help, encoding), file=stream)
|
| else:
|
| - print >> stream
|
| + print(file=stream)
|
| if value is None:
|
| - print >> stream, '#%s=' % optname
|
| + print('#%s=' % optname, file=stream)
|
| else:
|
| value = _encode(value, encoding).strip()
|
| - print >> stream, '%s=%s' % (optname, value)
|
| + print('%s=%s' % (optname, value), file=stream)
|
|
|
| format_section = ini_format_section
|
|
|
| def rest_format_section(stream, section, options, encoding=None, doc=None):
|
| - """format an options section using the INI format"""
|
| + """format an options section using as ReST formatted output"""
|
| encoding = _get_encoding(encoding, stream)
|
| if section:
|
| print >> stream, '%s\n%s' % (section, "'"*len(section))
|
| @@ -401,6 +425,7 @@
|
| print >> stream, ''
|
| print >> stream, ' Default: ``%s``' % value.replace("`` ", "```` ``")
|
|
|
| +# Options Manager ##############################################################
|
|
|
| class OptionsManagerMixIn(object):
|
| """MixIn to handle a configuration from both a configuration file and
|
| @@ -423,9 +448,9 @@
|
|
|
| def reset_parsers(self, usage='', version=None):
|
| # configuration file parser
|
| - self.cfgfile_parser = ConfigParser()
|
| + self.cfgfile_parser = cp.ConfigParser()
|
| # command line parser
|
| - self.cmdline_parser = optparse.OptionParser(usage=usage, version=version)
|
| + self.cmdline_parser = optik_ext.OptionParser(usage=usage, version=version)
|
| self.cmdline_parser.options_manager = self
|
| self._optik_option_attrs = set(self.cmdline_parser.option_class.ATTRS)
|
|
|
| @@ -461,7 +486,7 @@
|
| if group_name in self._mygroups:
|
| group = self._mygroups[group_name]
|
| else:
|
| - group = optparse.OptionGroup(self.cmdline_parser,
|
| + group = optik_ext.OptionGroup(self.cmdline_parser,
|
| title=group_name.capitalize())
|
| self.cmdline_parser.add_option_group(group)
|
| group.level = provider.level
|
| @@ -497,9 +522,9 @@
|
| # default is handled here and *must not* be given to optik if you
|
| # want the whole machinery to work
|
| if 'default' in optdict:
|
| - if (optparse.OPTPARSE_FORMAT_DEFAULT and 'help' in optdict and
|
| - optdict.get('default') is not None and
|
| - not optdict['action'] in ('store_true', 'store_false')):
|
| + if ('help' in optdict
|
| + and optdict.get('default') is not None
|
| + and not optdict['action'] in ('store_true', 'store_false')):
|
| optdict['help'] += ' [current: %default]'
|
| del optdict['default']
|
| args = ['--' + str(opt)]
|
| @@ -508,7 +533,7 @@
|
| args.append('-' + optdict['short'])
|
| del optdict['short']
|
| # cleanup option definition dict before giving it to optik
|
| - for key in optdict.keys():
|
| + for key in list(optdict.keys()):
|
| if not key in self._optik_option_attrs:
|
| optdict.pop(key)
|
| return args, optdict
|
| @@ -555,7 +580,7 @@
|
| printed = False
|
| for section in sections:
|
| if printed:
|
| - print >> stream, '\n'
|
| + print('\n', file=stream)
|
| format_section(stream, section.upper(), options_by_section[section],
|
| encoding)
|
| printed = True
|
| @@ -566,7 +591,7 @@
|
| """
|
| self._monkeypatch_expand_default()
|
| try:
|
| - optparse.generate_manpage(self.cmdline_parser, pkginfo,
|
| + optik_ext.generate_manpage(self.cmdline_parser, pkginfo,
|
| section, stream=stream or sys.stdout,
|
| level=self._maxlevel)
|
| finally:
|
| @@ -594,7 +619,7 @@
|
| if opt in self._all_options:
|
| break # already processed
|
| def helpfunc(option, opt, val, p, level=helplevel):
|
| - print self.help(level)
|
| + print(self.help(level))
|
| sys.exit(0)
|
| helpmsg = '%s verbose help.' % ' '.join(['more'] * helplevel)
|
| optdict = {'action' : 'callback', 'callback' : helpfunc,
|
| @@ -616,7 +641,7 @@
|
| parser._sections[sect.upper()] = values
|
| elif not self.quiet:
|
| msg = 'No config file found, using default configuration'
|
| - print >> sys.stderr, msg
|
| + print(msg, file=sys.stderr)
|
| return
|
|
|
| def input_config(self, onlysection=None, inputlevel=0, stream=None):
|
| @@ -642,13 +667,13 @@
|
| options provider)
|
| """
|
| parser = self.cfgfile_parser
|
| - for provider in self.options_providers:
|
| - for section, option, optdict in provider.all_options():
|
| - try:
|
| - value = parser.get(section, option)
|
| - provider.set_option(option, value, optdict=optdict)
|
| - except (NoSectionError, NoOptionError), ex:
|
| - continue
|
| + for section in parser.sections():
|
| + for option, value in parser.items(section):
|
| + try:
|
| + self.global_set_option(option, value)
|
| + except (KeyError, OptionError):
|
| + # TODO handle here undeclared options appearing in the config file
|
| + continue
|
|
|
| def load_configuration(self, **kwargs):
|
| """override configuration according to given parameters
|
| @@ -686,7 +711,7 @@
|
|
|
| def add_help_section(self, title, description, level=0):
|
| """add a dummy option section for help purpose """
|
| - group = optparse.OptionGroup(self.cmdline_parser,
|
| + group = optik_ext.OptionGroup(self.cmdline_parser,
|
| title=title.capitalize(),
|
| description=description)
|
| group.level = level
|
| @@ -694,18 +719,18 @@
|
| self.cmdline_parser.add_option_group(group)
|
|
|
| def _monkeypatch_expand_default(self):
|
| - # monkey patch optparse to deal with our default values
|
| + # monkey patch optik_ext to deal with our default values
|
| try:
|
| - self.__expand_default_backup = optparse.HelpFormatter.expand_default
|
| - optparse.HelpFormatter.expand_default = expand_default
|
| + self.__expand_default_backup = optik_ext.HelpFormatter.expand_default
|
| + optik_ext.HelpFormatter.expand_default = expand_default
|
| except AttributeError:
|
| # python < 2.4: nothing to be done
|
| pass
|
| def _unmonkeypatch_expand_default(self):
|
| # remove monkey patch
|
| - if hasattr(optparse.HelpFormatter, 'expand_default'):
|
| - # unpatch optparse to avoid side effects
|
| - optparse.HelpFormatter.expand_default = self.__expand_default_backup
|
| + if hasattr(optik_ext.HelpFormatter, 'expand_default'):
|
| + # unpatch optik_ext to avoid side effects
|
| + optik_ext.HelpFormatter.expand_default = self.__expand_default_backup
|
|
|
| def help(self, level=0):
|
| """return the usage string for available options """
|
| @@ -734,6 +759,7 @@
|
| assert self._inst, 'unbound method'
|
| return getattr(self._inst, self.method)(*args, **kwargs)
|
|
|
| +# Options Provider #############################################################
|
|
|
| class OptionsProviderMixIn(object):
|
| """Mixin to provide options to an OptionsManager"""
|
| @@ -745,7 +771,7 @@
|
| level = 0
|
|
|
| def __init__(self):
|
| - self.config = optparse.Values()
|
| + self.config = optik_ext.Values()
|
| for option in self.options:
|
| try:
|
| option, optdict = option
|
| @@ -777,41 +803,41 @@
|
| default = default()
|
| return default
|
|
|
| - def option_name(self, opt, optdict=None):
|
| + def option_attrname(self, opt, optdict=None):
|
| """get the config attribute corresponding to opt
|
| """
|
| if optdict is None:
|
| optdict = self.get_option_def(opt)
|
| return optdict.get('dest', opt.replace('-', '_'))
|
| + option_name = deprecated('[0.60] OptionsProviderMixIn.option_name() was renamed to option_attrname()')(option_attrname)
|
|
|
| def option_value(self, opt):
|
| """get the current value for the given option"""
|
| - return getattr(self.config, self.option_name(opt), None)
|
| + return getattr(self.config, self.option_attrname(opt), None)
|
|
|
| def set_option(self, opt, value, action=None, optdict=None):
|
| """method called to set an option (registered in the options list)
|
| """
|
| - # print "************ setting option", opt," to value", value
|
| if optdict is None:
|
| optdict = self.get_option_def(opt)
|
| if value is not None:
|
| - value = convert(value, optdict, opt)
|
| + value = _validate(value, optdict, opt)
|
| if action is None:
|
| action = optdict.get('action', 'store')
|
| if optdict.get('type') == 'named': # XXX need specific handling
|
| - optname = self.option_name(opt, optdict)
|
| + optname = self.option_attrname(opt, optdict)
|
| currentvalue = getattr(self.config, optname, None)
|
| if currentvalue:
|
| currentvalue.update(value)
|
| value = currentvalue
|
| if action == 'store':
|
| - setattr(self.config, self.option_name(opt, optdict), value)
|
| + setattr(self.config, self.option_attrname(opt, optdict), value)
|
| elif action in ('store_true', 'count'):
|
| - setattr(self.config, self.option_name(opt, optdict), 0)
|
| + setattr(self.config, self.option_attrname(opt, optdict), 0)
|
| elif action == 'store_false':
|
| - setattr(self.config, self.option_name(opt, optdict), 1)
|
| + setattr(self.config, self.option_attrname(opt, optdict), 1)
|
| elif action == 'append':
|
| - opt = self.option_name(opt, optdict)
|
| + opt = self.option_attrname(opt, optdict)
|
| _list = getattr(self.config, opt, None)
|
| if _list is None:
|
| if isinstance(value, (list, tuple)):
|
| @@ -839,12 +865,12 @@
|
| defaultstr = ': '
|
| else:
|
| defaultstr = '(default: %s): ' % format_option_value(optdict, default)
|
| - print ':%s:' % option
|
| - print optdict.get('help') or option
|
| + print(':%s:' % option)
|
| + print(optdict.get('help') or option)
|
| inputfunc = INPUT_FUNCTIONS[optdict['type']]
|
| value = inputfunc(optdict, defaultstr)
|
| while default is REQUIRED and not value:
|
| - print 'please specify a value'
|
| + print('please specify a value')
|
| value = inputfunc(optdict, '%s: ' % option)
|
| if value is None and default is not None:
|
| value = default
|
| @@ -893,6 +919,7 @@
|
| for optname, optdict in options:
|
| yield (optname, optdict, self.option_value(optname))
|
|
|
| +# configuration ################################################################
|
|
|
| class ConfigurationMixIn(OptionsManagerMixIn, OptionsProviderMixIn):
|
| """basic mixin for simple configurations which don't need the
|
| @@ -913,7 +940,7 @@
|
| continue
|
| if not gdef in self.option_groups:
|
| self.option_groups.append(gdef)
|
| - self.register_options_provider(self, own_group=0)
|
| + self.register_options_provider(self, own_group=False)
|
|
|
| def register_options(self, options):
|
| """add some options to the configuration"""
|
| @@ -932,8 +959,8 @@
|
|
|
| def __getitem__(self, key):
|
| try:
|
| - return getattr(self.config, self.option_name(key))
|
| - except (optparse.OptionValueError, AttributeError):
|
| + return getattr(self.config, self.option_attrname(key))
|
| + except (optik_ext.OptionValueError, AttributeError):
|
| raise KeyError(key)
|
|
|
| def __setitem__(self, key, value):
|
| @@ -941,7 +968,7 @@
|
|
|
| def get(self, key, default=None):
|
| try:
|
| - return getattr(self.config, self.option_name(key))
|
| + return getattr(self.config, self.option_attrname(key))
|
| except (OptionError, AttributeError):
|
| return default
|
|
|
| @@ -977,20 +1004,21 @@
|
| def __getitem__(self, key):
|
| provider = self.config._all_options[key]
|
| try:
|
| - return getattr(provider.config, provider.option_name(key))
|
| + return getattr(provider.config, provider.option_attrname(key))
|
| except AttributeError:
|
| raise KeyError(key)
|
|
|
| def __setitem__(self, key, value):
|
| - self.config.global_set_option(self.config.option_name(key), value)
|
| + self.config.global_set_option(self.config.option_attrname(key), value)
|
|
|
| def get(self, key, default=None):
|
| provider = self.config._all_options[key]
|
| try:
|
| - return getattr(provider.config, provider.option_name(key))
|
| + return getattr(provider.config, provider.option_attrname(key))
|
| except AttributeError:
|
| return default
|
|
|
| +# other functions ##############################################################
|
|
|
| def read_old_config(newconfig, changes, configfile):
|
| """initialize newconfig from a deprecated configuration file
|
| @@ -1055,8 +1083,13 @@
|
| newconfig.set_option(optname, oldconfig[optname], optdict=optdef)
|
|
|
|
|
| -def merge_options(options):
|
| - """preprocess options to remove duplicate"""
|
| +def merge_options(options, optgroup=None):
|
| + """preprocess a list of options and remove duplicates, returning a new list
|
| + (tuple actually) of options.
|
| +
|
| + Options dictionaries are copied to avoid later side-effect. Also, if
|
| + `otpgroup` argument is specified, ensure all options are in the given group.
|
| + """
|
| alloptions = {}
|
| options = list(options)
|
| for i in range(len(options)-1, -1, -1):
|
| @@ -1065,5 +1098,9 @@
|
| options.pop(i)
|
| alloptions[optname].update(optdict)
|
| else:
|
| + optdict = optdict.copy()
|
| + options[i] = (optname, optdict)
|
| alloptions[optname] = optdict
|
| + if optgroup is not None:
|
| + alloptions[optname]['group'] = optgroup
|
| return tuple(options)
|
|
|