| OLD | NEW | 
| (Empty) |  | 
 |     1 # copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. | 
 |     2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr | 
 |     3 # | 
 |     4 # This file is part of logilab-common. | 
 |     5 # | 
 |     6 # logilab-common is free software: you can redistribute it and/or modify it unde
      r | 
 |     7 # the terms of the GNU Lesser General Public License as published by the Free | 
 |     8 # Software Foundation, either version 2.1 of the License, or (at your option) an
      y | 
 |     9 # later version. | 
 |    10 # | 
 |    11 # logilab-common is distributed in the hope that it will be useful, but WITHOUT | 
 |    12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | 
 |    13 # FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more | 
 |    14 # details. | 
 |    15 # | 
 |    16 # You should have received a copy of the GNU Lesser General Public License along | 
 |    17 # with logilab-common.  If not, see <http://www.gnu.org/licenses/>. | 
 |    18 """Classes to handle advanced configuration in simple to complex applications. | 
 |    19  | 
 |    20 Allows to load the configuration from a file or from command line | 
 |    21 options, to generate a sample configuration file or to display | 
 |    22 program's usage. Fills the gap between optik/optparse and ConfigParser | 
 |    23 by adding data types (which are also available as a standalone optik | 
 |    24 extension in the `optik_ext` module). | 
 |    25  | 
 |    26  | 
 |    27 Quick start: simplest usage | 
 |    28 --------------------------- | 
 |    29  | 
 |    30 .. python :: | 
 |    31  | 
 |    32   >>> import sys | 
 |    33   >>> from logilab.common.configuration import Configuration | 
 |    34   >>> options = [('dothis', {'type':'yn', 'default': True, 'metavar': '<y or n>'
      }), | 
 |    35   ...            ('value', {'type': 'string', 'metavar': '<string>'}), | 
 |    36   ...            ('multiple', {'type': 'csv', 'default': ('yop',), | 
 |    37   ...                          'metavar': '<comma separated values>', | 
 |    38   ...                          'help': 'you can also document the option'}), | 
 |    39   ...            ('number', {'type': 'int', 'default':2, 'metavar':'<int>'}), | 
 |    40   ...           ] | 
 |    41   >>> config = Configuration(options=options, name='My config') | 
 |    42   >>> print config['dothis'] | 
 |    43   True | 
 |    44   >>> print config['value'] | 
 |    45   None | 
 |    46   >>> print config['multiple'] | 
 |    47   ('yop',) | 
 |    48   >>> print config['number'] | 
 |    49   2 | 
 |    50   >>> print config.help() | 
 |    51   Usage:  [options] | 
 |    52  | 
 |    53   Options: | 
 |    54     -h, --help            show this help message and exit | 
 |    55     --dothis=<y or n> | 
 |    56     --value=<string> | 
 |    57     --multiple=<comma separated values> | 
 |    58                           you can also document the option [current: none] | 
 |    59     --number=<int> | 
 |    60  | 
 |    61   >>> f = open('myconfig.ini', 'w') | 
 |    62   >>> f.write('''[MY CONFIG] | 
 |    63   ... number = 3 | 
 |    64   ... dothis = no | 
 |    65   ... multiple = 1,2,3 | 
 |    66   ... ''') | 
 |    67   >>> f.close() | 
 |    68   >>> config.load_file_configuration('myconfig.ini') | 
 |    69   >>> print config['dothis'] | 
 |    70   False | 
 |    71   >>> print config['value'] | 
 |    72   None | 
 |    73   >>> print config['multiple'] | 
 |    74   ['1', '2', '3'] | 
 |    75   >>> print config['number'] | 
 |    76   3 | 
 |    77   >>> sys.argv = ['mon prog', '--value', 'bacon', '--multiple', '4,5,6', | 
 |    78   ...             'nonoptionargument'] | 
 |    79   >>> print config.load_command_line_configuration() | 
 |    80   ['nonoptionargument'] | 
 |    81   >>> print config['value'] | 
 |    82   bacon | 
 |    83   >>> config.generate_config() | 
 |    84   # class for simple configurations which don't need the | 
 |    85   # manager / providers model and prefer delegation to inheritance | 
 |    86   # | 
 |    87   # configuration values are accessible through a dict like interface | 
 |    88   # | 
 |    89   [MY CONFIG] | 
 |    90  | 
 |    91   dothis=no | 
 |    92  | 
 |    93   value=bacon | 
 |    94  | 
 |    95   # you can also document the option | 
 |    96   multiple=4,5,6 | 
 |    97  | 
 |    98   number=3 | 
 |    99   >>> | 
 |   100 """ | 
 |   101 __docformat__ = "restructuredtext en" | 
 |   102  | 
 |   103 __all__ = ('OptionsManagerMixIn', 'OptionsProviderMixIn', | 
 |   104            'ConfigurationMixIn', 'Configuration', | 
 |   105            'OptionsManager2ConfigurationAdapter') | 
 |   106  | 
 |   107 import os | 
 |   108 import sys | 
 |   109 import re | 
 |   110 from os.path import exists, expanduser | 
 |   111 from copy import copy | 
 |   112 from ConfigParser import ConfigParser, NoOptionError, NoSectionError, \ | 
 |   113      DuplicateSectionError | 
 |   114 from warnings import warn | 
 |   115  | 
 |   116 from logilab.common.compat import callable, raw_input, str_encode as _encode | 
 |   117  | 
 |   118 from logilab.common.textutils import normalize_text, unquote | 
 |   119 from logilab.common import optik_ext as optparse | 
 |   120  | 
 |   121 OptionError = optparse.OptionError | 
 |   122  | 
 |   123 REQUIRED = [] | 
 |   124  | 
 |   125 class UnsupportedAction(Exception): | 
 |   126     """raised by set_option when it doesn't know what to do for an action""" | 
 |   127  | 
 |   128  | 
 |   129 def _get_encoding(encoding, stream): | 
 |   130     encoding = encoding or getattr(stream, 'encoding', None) | 
 |   131     if not encoding: | 
 |   132         import locale | 
 |   133         encoding = locale.getpreferredencoding() | 
 |   134     return encoding | 
 |   135  | 
 |   136  | 
 |   137 # validation functions ######################################################## | 
 |   138  | 
 |   139 def choice_validator(optdict, name, value): | 
 |   140     """validate and return a converted value for option of type 'choice' | 
 |   141     """ | 
 |   142     if not value in optdict['choices']: | 
 |   143         msg = "option %s: invalid value: %r, should be in %s" | 
 |   144         raise optparse.OptionValueError(msg % (name, value, optdict['choices'])) | 
 |   145     return value | 
 |   146  | 
 |   147 def multiple_choice_validator(optdict, name, value): | 
 |   148     """validate and return a converted value for option of type 'choice' | 
 |   149     """ | 
 |   150     choices = optdict['choices'] | 
 |   151     values = optparse.check_csv(None, name, value) | 
 |   152     for value in values: | 
 |   153         if not value in choices: | 
 |   154             msg = "option %s: invalid value: %r, should be in %s" | 
 |   155             raise optparse.OptionValueError(msg % (name, value, choices)) | 
 |   156     return values | 
 |   157  | 
 |   158 def csv_validator(optdict, name, value): | 
 |   159     """validate and return a converted value for option of type 'csv' | 
 |   160     """ | 
 |   161     return optparse.check_csv(None, name, value) | 
 |   162  | 
 |   163 def yn_validator(optdict, name, value): | 
 |   164     """validate and return a converted value for option of type 'yn' | 
 |   165     """ | 
 |   166     return optparse.check_yn(None, name, value) | 
 |   167  | 
 |   168 def named_validator(optdict, name, value): | 
 |   169     """validate and return a converted value for option of type 'named' | 
 |   170     """ | 
 |   171     return optparse.check_named(None, name, value) | 
 |   172  | 
 |   173 def file_validator(optdict, name, value): | 
 |   174     """validate and return a filepath for option of type 'file'""" | 
 |   175     return optparse.check_file(None, name, value) | 
 |   176  | 
 |   177 def color_validator(optdict, name, value): | 
 |   178     """validate and return a valid color for option of type 'color'""" | 
 |   179     return optparse.check_color(None, name, value) | 
 |   180  | 
 |   181 def password_validator(optdict, name, value): | 
 |   182     """validate and return a string for option of type 'password'""" | 
 |   183     return optparse.check_password(None, name, value) | 
 |   184  | 
 |   185 def date_validator(optdict, name, value): | 
 |   186     """validate and return a mx DateTime object for option of type 'date'""" | 
 |   187     return optparse.check_date(None, name, value) | 
 |   188  | 
 |   189 def time_validator(optdict, name, value): | 
 |   190     """validate and return a time object for option of type 'time'""" | 
 |   191     return optparse.check_time(None, name, value) | 
 |   192  | 
 |   193 def bytes_validator(optdict, name, value): | 
 |   194     """validate and return an integer for option of type 'bytes'""" | 
 |   195     return optparse.check_bytes(None, name, value) | 
 |   196  | 
 |   197  | 
 |   198 VALIDATORS = {'string': unquote, | 
 |   199               'int': int, | 
 |   200               'float': float, | 
 |   201               'file': file_validator, | 
 |   202               'font': unquote, | 
 |   203               'color': color_validator, | 
 |   204               'regexp': re.compile, | 
 |   205               'csv': csv_validator, | 
 |   206               'yn': yn_validator, | 
 |   207               'bool': yn_validator, | 
 |   208               'named': named_validator, | 
 |   209               'password': password_validator, | 
 |   210               'date': date_validator, | 
 |   211               'time': time_validator, | 
 |   212               'bytes': bytes_validator, | 
 |   213               'choice': choice_validator, | 
 |   214               'multiple_choice': multiple_choice_validator, | 
 |   215               } | 
 |   216  | 
 |   217 def _call_validator(opttype, optdict, option, value): | 
 |   218     if opttype not in VALIDATORS: | 
 |   219         raise Exception('Unsupported type "%s"' % opttype) | 
 |   220     try: | 
 |   221         return VALIDATORS[opttype](optdict, option, value) | 
 |   222     except TypeError: | 
 |   223         try: | 
 |   224             return VALIDATORS[opttype](value) | 
 |   225         except optparse.OptionValueError: | 
 |   226             raise | 
 |   227         except: | 
 |   228             raise optparse.OptionValueError('%s value (%r) should be of type %s'
       % | 
 |   229                                    (option, value, opttype)) | 
 |   230  | 
 |   231 # user input functions ######################################################## | 
 |   232  | 
 |   233 def input_password(optdict, question='password:'): | 
 |   234     from getpass import getpass | 
 |   235     while True: | 
 |   236         value = getpass(question) | 
 |   237         value2 = getpass('confirm: ') | 
 |   238         if value == value2: | 
 |   239             return value | 
 |   240         print 'password mismatch, try again' | 
 |   241  | 
 |   242 def input_string(optdict, question): | 
 |   243     value = raw_input(question).strip() | 
 |   244     return value or None | 
 |   245  | 
 |   246 def _make_input_function(opttype): | 
 |   247     def input_validator(optdict, question): | 
 |   248         while True: | 
 |   249             value = raw_input(question) | 
 |   250             if not value.strip(): | 
 |   251                 return None | 
 |   252             try: | 
 |   253                 return _call_validator(opttype, optdict, None, value) | 
 |   254             except optparse.OptionValueError, ex: | 
 |   255                 msg = str(ex).split(':', 1)[-1].strip() | 
 |   256                 print 'bad value: %s' % msg | 
 |   257     return input_validator | 
 |   258  | 
 |   259 INPUT_FUNCTIONS = { | 
 |   260     'string': input_string, | 
 |   261     'password': input_password, | 
 |   262     } | 
 |   263  | 
 |   264 for opttype in VALIDATORS.keys(): | 
 |   265     INPUT_FUNCTIONS.setdefault(opttype, _make_input_function(opttype)) | 
 |   266  | 
 |   267 def expand_default(self, option): | 
 |   268     """monkey patch OptionParser.expand_default since we have a particular | 
 |   269     way to handle defaults to avoid overriding values in the configuration | 
 |   270     file | 
 |   271     """ | 
 |   272     if self.parser is None or not self.default_tag: | 
 |   273         return option.help | 
 |   274     optname = option._long_opts[0][2:] | 
 |   275     try: | 
 |   276         provider = self.parser.options_manager._all_options[optname] | 
 |   277     except KeyError: | 
 |   278         value = None | 
 |   279     else: | 
 |   280         optdict = provider.get_option_def(optname) | 
 |   281         optname = provider.option_name(optname, optdict) | 
 |   282         value = getattr(provider.config, optname, optdict) | 
 |   283         value = format_option_value(optdict, value) | 
 |   284     if value is optparse.NO_DEFAULT or not value: | 
 |   285         value = self.NO_DEFAULT_VALUE | 
 |   286     return option.help.replace(self.default_tag, str(value)) | 
 |   287  | 
 |   288  | 
 |   289 def convert(value, optdict, name=''): | 
 |   290     """return a validated value for an option according to its type | 
 |   291  | 
 |   292     optional argument name is only used for error message formatting | 
 |   293     """ | 
 |   294     try: | 
 |   295         _type = optdict['type'] | 
 |   296     except KeyError: | 
 |   297         # FIXME | 
 |   298         return value | 
 |   299     return _call_validator(_type, optdict, name, value) | 
 |   300  | 
 |   301 def comment(string): | 
 |   302     """return string as a comment""" | 
 |   303     lines = [line.strip() for line in string.splitlines()] | 
 |   304     return '# ' + ('%s# ' % os.linesep).join(lines) | 
 |   305  | 
 |   306 def format_time(value): | 
 |   307     if not value: | 
 |   308         return '0' | 
 |   309     if value != int(value): | 
 |   310         return '%.2fs' % value | 
 |   311     value = int(value) | 
 |   312     nbmin, nbsec = divmod(value, 60) | 
 |   313     if nbsec: | 
 |   314         return '%ss' % value | 
 |   315     nbhour, nbmin_ = divmod(nbmin, 60) | 
 |   316     if nbmin_: | 
 |   317         return '%smin' % nbmin | 
 |   318     nbday, nbhour_ = divmod(nbhour, 24) | 
 |   319     if nbhour_: | 
 |   320         return '%sh' % nbhour | 
 |   321     return '%sd' % nbday | 
 |   322  | 
 |   323 def format_bytes(value): | 
 |   324     if not value: | 
 |   325         return '0' | 
 |   326     if value != int(value): | 
 |   327         return '%.2fB' % value | 
 |   328     value = int(value) | 
 |   329     prevunit = 'B' | 
 |   330     for unit in ('KB', 'MB', 'GB', 'TB'): | 
 |   331         next, remain = divmod(value, 1024) | 
 |   332         if remain: | 
 |   333             return '%s%s' % (value, prevunit) | 
 |   334         prevunit = unit | 
 |   335         value = next | 
 |   336     return '%s%s' % (value, unit) | 
 |   337  | 
 |   338 def format_option_value(optdict, value): | 
 |   339     """return the user input's value from a 'compiled' value""" | 
 |   340     if isinstance(value, (list, tuple)): | 
 |   341         value = ','.join(value) | 
 |   342     elif isinstance(value, dict): | 
 |   343         value = ','.join(['%s:%s' % (k, v) for k, v in value.items()]) | 
 |   344     elif hasattr(value, 'match'): # optdict.get('type') == 'regexp' | 
 |   345         # compiled regexp | 
 |   346         value = value.pattern | 
 |   347     elif optdict.get('type') == 'yn': | 
 |   348         value = value and 'yes' or 'no' | 
 |   349     elif isinstance(value, (str, unicode)) and value.isspace(): | 
 |   350         value = "'%s'" % value | 
 |   351     elif optdict.get('type') == 'time' and isinstance(value, (float, int, long))
      : | 
 |   352         value = format_time(value) | 
 |   353     elif optdict.get('type') == 'bytes' and hasattr(value, '__int__'): | 
 |   354         value = format_bytes(value) | 
 |   355     return value | 
 |   356  | 
 |   357 def ini_format_section(stream, section, options, encoding=None, doc=None): | 
 |   358     """format an options section using the INI format""" | 
 |   359     encoding = _get_encoding(encoding, stream) | 
 |   360     if doc: | 
 |   361         print >> stream, _encode(comment(doc), encoding) | 
 |   362     print >> stream, '[%s]' % section | 
 |   363     ini_format(stream, options, encoding) | 
 |   364  | 
 |   365 def ini_format(stream, options, encoding): | 
 |   366     """format options using the INI format""" | 
 |   367     for optname, optdict, value in options: | 
 |   368         value = format_option_value(optdict, value) | 
 |   369         help = optdict.get('help') | 
 |   370         if help: | 
 |   371             help = normalize_text(help, line_len=79, indent='# ') | 
 |   372             print >> stream | 
 |   373             print >> stream, _encode(help, encoding) | 
 |   374         else: | 
 |   375             print >> stream | 
 |   376         if value is None: | 
 |   377             print >> stream, '#%s=' % optname | 
 |   378         else: | 
 |   379             value = _encode(value, encoding).strip() | 
 |   380             print >> stream, '%s=%s' % (optname, value) | 
 |   381  | 
 |   382 format_section = ini_format_section | 
 |   383  | 
 |   384 def rest_format_section(stream, section, options, encoding=None, doc=None): | 
 |   385     """format an options section using the INI format""" | 
 |   386     encoding = _get_encoding(encoding, stream) | 
 |   387     if section: | 
 |   388         print >> stream, '%s\n%s' % (section, "'"*len(section)) | 
 |   389     if doc: | 
 |   390         print >> stream, _encode(normalize_text(doc, line_len=79, indent=''), | 
 |   391                                  encoding) | 
 |   392         print >> stream | 
 |   393     for optname, optdict, value in options: | 
 |   394         help = optdict.get('help') | 
 |   395         print >> stream, ':%s:' % optname | 
 |   396         if help: | 
 |   397             help = normalize_text(help, line_len=79, indent='  ') | 
 |   398             print >> stream, _encode(help, encoding) | 
 |   399         if value: | 
 |   400             value = _encode(format_option_value(optdict, value), encoding) | 
 |   401             print >> stream, '' | 
 |   402             print >> stream, '  Default: ``%s``' % value.replace("`` ", "```` ``
      ") | 
 |   403  | 
 |   404  | 
 |   405 class OptionsManagerMixIn(object): | 
 |   406     """MixIn to handle a configuration from both a configuration file and | 
 |   407     command line options | 
 |   408     """ | 
 |   409  | 
 |   410     def __init__(self, usage, config_file=None, version=None, quiet=0): | 
 |   411         self.config_file = config_file | 
 |   412         self.reset_parsers(usage, version=version) | 
 |   413         # list of registered options providers | 
 |   414         self.options_providers = [] | 
 |   415         # dictionary associating option name to checker | 
 |   416         self._all_options = {} | 
 |   417         self._short_options = {} | 
 |   418         self._nocallback_options = {} | 
 |   419         self._mygroups = dict() | 
 |   420         # verbosity | 
 |   421         self.quiet = quiet | 
 |   422         self._maxlevel = 0 | 
 |   423  | 
 |   424     def reset_parsers(self, usage='', version=None): | 
 |   425         # configuration file parser | 
 |   426         self.cfgfile_parser = ConfigParser() | 
 |   427         # command line parser | 
 |   428         self.cmdline_parser = optparse.OptionParser(usage=usage, version=version
      ) | 
 |   429         self.cmdline_parser.options_manager = self | 
 |   430         self._optik_option_attrs = set(self.cmdline_parser.option_class.ATTRS) | 
 |   431  | 
 |   432     def register_options_provider(self, provider, own_group=True): | 
 |   433         """register an options provider""" | 
 |   434         assert provider.priority <= 0, "provider's priority can't be >= 0" | 
 |   435         for i in range(len(self.options_providers)): | 
 |   436             if provider.priority > self.options_providers[i].priority: | 
 |   437                 self.options_providers.insert(i, provider) | 
 |   438                 break | 
 |   439         else: | 
 |   440             self.options_providers.append(provider) | 
 |   441         non_group_spec_options = [option for option in provider.options | 
 |   442                                   if 'group' not in option[1]] | 
 |   443         groups = getattr(provider, 'option_groups', ()) | 
 |   444         if own_group and non_group_spec_options: | 
 |   445             self.add_option_group(provider.name.upper(), provider.__doc__, | 
 |   446                                   non_group_spec_options, provider) | 
 |   447         else: | 
 |   448             for opt, optdict in non_group_spec_options: | 
 |   449                 self.add_optik_option(provider, self.cmdline_parser, opt, optdic
      t) | 
 |   450         for gname, gdoc in groups: | 
 |   451             gname = gname.upper() | 
 |   452             goptions = [option for option in provider.options | 
 |   453                         if option[1].get('group', '').upper() == gname] | 
 |   454             self.add_option_group(gname, gdoc, goptions, provider) | 
 |   455  | 
 |   456     def add_option_group(self, group_name, doc, options, provider): | 
 |   457         """add an option group including the listed options | 
 |   458         """ | 
 |   459         assert options | 
 |   460         # add option group to the command line parser | 
 |   461         if group_name in self._mygroups: | 
 |   462             group = self._mygroups[group_name] | 
 |   463         else: | 
 |   464             group = optparse.OptionGroup(self.cmdline_parser, | 
 |   465                                          title=group_name.capitalize()) | 
 |   466             self.cmdline_parser.add_option_group(group) | 
 |   467             group.level = provider.level | 
 |   468             self._mygroups[group_name] = group | 
 |   469             # add section to the config file | 
 |   470             if group_name != "DEFAULT": | 
 |   471                 self.cfgfile_parser.add_section(group_name) | 
 |   472         # add provider's specific options | 
 |   473         for opt, optdict in options: | 
 |   474             self.add_optik_option(provider, group, opt, optdict) | 
 |   475  | 
 |   476     def add_optik_option(self, provider, optikcontainer, opt, optdict): | 
 |   477         if 'inputlevel' in optdict: | 
 |   478             warn('[0.50] "inputlevel" in option dictionary for %s is deprecated,
      ' | 
 |   479                  ' use "level"' % opt, DeprecationWarning) | 
 |   480             optdict['level'] = optdict.pop('inputlevel') | 
 |   481         args, optdict = self.optik_option(provider, opt, optdict) | 
 |   482         option = optikcontainer.add_option(*args, **optdict) | 
 |   483         self._all_options[opt] = provider | 
 |   484         self._maxlevel = max(self._maxlevel, option.level or 0) | 
 |   485  | 
 |   486     def optik_option(self, provider, opt, optdict): | 
 |   487         """get our personal option definition and return a suitable form for | 
 |   488         use with optik/optparse | 
 |   489         """ | 
 |   490         optdict = copy(optdict) | 
 |   491         others = {} | 
 |   492         if 'action' in optdict: | 
 |   493             self._nocallback_options[provider] = opt | 
 |   494         else: | 
 |   495             optdict['action'] = 'callback' | 
 |   496             optdict['callback'] = self.cb_set_provider_option | 
 |   497         # default is handled here and *must not* be given to optik if you | 
 |   498         # want the whole machinery to work | 
 |   499         if 'default' in optdict: | 
 |   500             if (optparse.OPTPARSE_FORMAT_DEFAULT and 'help' in optdict and | 
 |   501                 optdict.get('default') is not None and | 
 |   502                 not optdict['action'] in ('store_true', 'store_false')): | 
 |   503                 optdict['help'] += ' [current: %default]' | 
 |   504             del optdict['default'] | 
 |   505         args = ['--' + str(opt)] | 
 |   506         if 'short' in optdict: | 
 |   507             self._short_options[optdict['short']] = opt | 
 |   508             args.append('-' + optdict['short']) | 
 |   509             del optdict['short'] | 
 |   510         # cleanup option definition dict before giving it to optik | 
 |   511         for key in optdict.keys(): | 
 |   512             if not key in self._optik_option_attrs: | 
 |   513                 optdict.pop(key) | 
 |   514         return args, optdict | 
 |   515  | 
 |   516     def cb_set_provider_option(self, option, opt, value, parser): | 
 |   517         """optik callback for option setting""" | 
 |   518         if opt.startswith('--'): | 
 |   519             # remove -- on long option | 
 |   520             opt = opt[2:] | 
 |   521         else: | 
 |   522             # short option, get its long equivalent | 
 |   523             opt = self._short_options[opt[1:]] | 
 |   524         # trick since we can't set action='store_true' on options | 
 |   525         if value is None: | 
 |   526             value = 1 | 
 |   527         self.global_set_option(opt, value) | 
 |   528  | 
 |   529     def global_set_option(self, opt, value): | 
 |   530         """set option on the correct option provider""" | 
 |   531         self._all_options[opt].set_option(opt, value) | 
 |   532  | 
 |   533     def generate_config(self, stream=None, skipsections=(), encoding=None): | 
 |   534         """write a configuration file according to the current configuration | 
 |   535         into the given stream or stdout | 
 |   536         """ | 
 |   537         options_by_section = {} | 
 |   538         sections = [] | 
 |   539         for provider in self.options_providers: | 
 |   540             for section, options in provider.options_by_section(): | 
 |   541                 if section is None: | 
 |   542                     section = provider.name | 
 |   543                 if section in skipsections: | 
 |   544                     continue | 
 |   545                 options = [(n, d, v) for (n, d, v) in options | 
 |   546                            if d.get('type') is not None] | 
 |   547                 if not options: | 
 |   548                     continue | 
 |   549                 if not section in sections: | 
 |   550                     sections.append(section) | 
 |   551                 alloptions = options_by_section.setdefault(section, []) | 
 |   552                 alloptions += options | 
 |   553         stream = stream or sys.stdout | 
 |   554         encoding = _get_encoding(encoding, stream) | 
 |   555         printed = False | 
 |   556         for section in sections: | 
 |   557             if printed: | 
 |   558                 print >> stream, '\n' | 
 |   559             format_section(stream, section.upper(), options_by_section[section], | 
 |   560                            encoding) | 
 |   561             printed = True | 
 |   562  | 
 |   563     def generate_manpage(self, pkginfo, section=1, stream=None): | 
 |   564         """write a man page for the current configuration into the given | 
 |   565         stream or stdout | 
 |   566         """ | 
 |   567         self._monkeypatch_expand_default() | 
 |   568         try: | 
 |   569             optparse.generate_manpage(self.cmdline_parser, pkginfo, | 
 |   570                                       section, stream=stream or sys.stdout, | 
 |   571                                       level=self._maxlevel) | 
 |   572         finally: | 
 |   573             self._unmonkeypatch_expand_default() | 
 |   574  | 
 |   575     # initialization methods ################################################## | 
 |   576  | 
 |   577     def load_provider_defaults(self): | 
 |   578         """initialize configuration using default values""" | 
 |   579         for provider in self.options_providers: | 
 |   580             provider.load_defaults() | 
 |   581  | 
 |   582     def load_file_configuration(self, config_file=None): | 
 |   583         """load the configuration from file""" | 
 |   584         self.read_config_file(config_file) | 
 |   585         self.load_config_file() | 
 |   586  | 
 |   587     def read_config_file(self, config_file=None): | 
 |   588         """read the configuration file but do not load it (i.e. dispatching | 
 |   589         values to each options provider) | 
 |   590         """ | 
 |   591         helplevel = 1 | 
 |   592         while helplevel <= self._maxlevel: | 
 |   593             opt = '-'.join(['long'] * helplevel) + '-help' | 
 |   594             if opt in self._all_options: | 
 |   595                 break # already processed | 
 |   596             def helpfunc(option, opt, val, p, level=helplevel): | 
 |   597                 print self.help(level) | 
 |   598                 sys.exit(0) | 
 |   599             helpmsg = '%s verbose help.' % ' '.join(['more'] * helplevel) | 
 |   600             optdict = {'action' : 'callback', 'callback' : helpfunc, | 
 |   601                        'help' : helpmsg} | 
 |   602             provider = self.options_providers[0] | 
 |   603             self.add_optik_option(provider, self.cmdline_parser, opt, optdict) | 
 |   604             provider.options += ( (opt, optdict), ) | 
 |   605             helplevel += 1 | 
 |   606         if config_file is None: | 
 |   607             config_file = self.config_file | 
 |   608         if config_file is not None: | 
 |   609             config_file = expanduser(config_file) | 
 |   610         if config_file and exists(config_file): | 
 |   611             parser = self.cfgfile_parser | 
 |   612             parser.read([config_file]) | 
 |   613             # normalize sections'title | 
 |   614             for sect, values in parser._sections.items(): | 
 |   615                 if not sect.isupper() and values: | 
 |   616                     parser._sections[sect.upper()] = values | 
 |   617         elif not self.quiet: | 
 |   618             msg = 'No config file found, using default configuration' | 
 |   619             print >> sys.stderr, msg | 
 |   620             return | 
 |   621  | 
 |   622     def input_config(self, onlysection=None, inputlevel=0, stream=None): | 
 |   623         """interactively get configuration values by asking to the user and gene
      rate | 
 |   624         a configuration file | 
 |   625         """ | 
 |   626         if onlysection is not None: | 
 |   627             onlysection = onlysection.upper() | 
 |   628         for provider in self.options_providers: | 
 |   629             for section, option, optdict in provider.all_options(): | 
 |   630                 if onlysection is not None and section != onlysection: | 
 |   631                     continue | 
 |   632                 if not 'type' in optdict: | 
 |   633                     # ignore action without type (callback, store_true...) | 
 |   634                     continue | 
 |   635                 provider.input_option(option, optdict, inputlevel) | 
 |   636         # now we can generate the configuration file | 
 |   637         if stream is not None: | 
 |   638             self.generate_config(stream) | 
 |   639  | 
 |   640     def load_config_file(self): | 
 |   641         """dispatch values previously read from a configuration file to each | 
 |   642         options provider) | 
 |   643         """ | 
 |   644         parser = self.cfgfile_parser | 
 |   645         for provider in self.options_providers: | 
 |   646             for section, option, optdict in provider.all_options(): | 
 |   647                 try: | 
 |   648                     value = parser.get(section, option) | 
 |   649                     provider.set_option(option, value, optdict=optdict) | 
 |   650                 except (NoSectionError, NoOptionError), ex: | 
 |   651                     continue | 
 |   652  | 
 |   653     def load_configuration(self, **kwargs): | 
 |   654         """override configuration according to given parameters | 
 |   655         """ | 
 |   656         for opt, opt_value in kwargs.items(): | 
 |   657             opt = opt.replace('_', '-') | 
 |   658             provider = self._all_options[opt] | 
 |   659             provider.set_option(opt, opt_value) | 
 |   660  | 
 |   661     def load_command_line_configuration(self, args=None): | 
 |   662         """override configuration according to command line parameters | 
 |   663  | 
 |   664         return additional arguments | 
 |   665         """ | 
 |   666         self._monkeypatch_expand_default() | 
 |   667         try: | 
 |   668             if args is None: | 
 |   669                 args = sys.argv[1:] | 
 |   670             else: | 
 |   671                 args = list(args) | 
 |   672             (options, args) = self.cmdline_parser.parse_args(args=args) | 
 |   673             for provider in self._nocallback_options.keys(): | 
 |   674                 config = provider.config | 
 |   675                 for attr in config.__dict__.keys(): | 
 |   676                     value = getattr(options, attr, None) | 
 |   677                     if value is None: | 
 |   678                         continue | 
 |   679                     setattr(config, attr, value) | 
 |   680             return args | 
 |   681         finally: | 
 |   682             self._unmonkeypatch_expand_default() | 
 |   683  | 
 |   684  | 
 |   685     # help methods ############################################################ | 
 |   686  | 
 |   687     def add_help_section(self, title, description, level=0): | 
 |   688         """add a dummy option section for help purpose """ | 
 |   689         group = optparse.OptionGroup(self.cmdline_parser, | 
 |   690                                      title=title.capitalize(), | 
 |   691                                      description=description) | 
 |   692         group.level = level | 
 |   693         self._maxlevel = max(self._maxlevel, level) | 
 |   694         self.cmdline_parser.add_option_group(group) | 
 |   695  | 
 |   696     def _monkeypatch_expand_default(self): | 
 |   697         # monkey patch optparse to deal with our default values | 
 |   698         try: | 
 |   699             self.__expand_default_backup = optparse.HelpFormatter.expand_default | 
 |   700             optparse.HelpFormatter.expand_default = expand_default | 
 |   701         except AttributeError: | 
 |   702             # python < 2.4: nothing to be done | 
 |   703             pass | 
 |   704     def _unmonkeypatch_expand_default(self): | 
 |   705         # remove monkey patch | 
 |   706         if hasattr(optparse.HelpFormatter, 'expand_default'): | 
 |   707             # unpatch optparse to avoid side effects | 
 |   708             optparse.HelpFormatter.expand_default = self.__expand_default_backup | 
 |   709  | 
 |   710     def help(self, level=0): | 
 |   711         """return the usage string for available options """ | 
 |   712         self.cmdline_parser.formatter.output_level = level | 
 |   713         self._monkeypatch_expand_default() | 
 |   714         try: | 
 |   715             return self.cmdline_parser.format_help() | 
 |   716         finally: | 
 |   717             self._unmonkeypatch_expand_default() | 
 |   718  | 
 |   719  | 
 |   720 class Method(object): | 
 |   721     """used to ease late binding of default method (so you can define options | 
 |   722     on the class using default methods on the configuration instance) | 
 |   723     """ | 
 |   724     def __init__(self, methname): | 
 |   725         self.method = methname | 
 |   726         self._inst = None | 
 |   727  | 
 |   728     def bind(self, instance): | 
 |   729         """bind the method to its instance""" | 
 |   730         if self._inst is None: | 
 |   731             self._inst = instance | 
 |   732  | 
 |   733     def __call__(self, *args, **kwargs): | 
 |   734         assert self._inst, 'unbound method' | 
 |   735         return getattr(self._inst, self.method)(*args, **kwargs) | 
 |   736  | 
 |   737  | 
 |   738 class OptionsProviderMixIn(object): | 
 |   739     """Mixin to provide options to an OptionsManager""" | 
 |   740  | 
 |   741     # those attributes should be overridden | 
 |   742     priority = -1 | 
 |   743     name = 'default' | 
 |   744     options = () | 
 |   745     level = 0 | 
 |   746  | 
 |   747     def __init__(self): | 
 |   748         self.config = optparse.Values() | 
 |   749         for option in self.options: | 
 |   750             try: | 
 |   751                 option, optdict = option | 
 |   752             except ValueError: | 
 |   753                 raise Exception('Bad option: %r' % option) | 
 |   754             if isinstance(optdict.get('default'), Method): | 
 |   755                 optdict['default'].bind(self) | 
 |   756             elif isinstance(optdict.get('callback'), Method): | 
 |   757                 optdict['callback'].bind(self) | 
 |   758         self.load_defaults() | 
 |   759  | 
 |   760     def load_defaults(self): | 
 |   761         """initialize the provider using default values""" | 
 |   762         for opt, optdict in self.options: | 
 |   763             action = optdict.get('action') | 
 |   764             if action != 'callback': | 
 |   765                 # callback action have no default | 
 |   766                 default = self.option_default(opt, optdict) | 
 |   767                 if default is REQUIRED: | 
 |   768                     continue | 
 |   769                 self.set_option(opt, default, action, optdict) | 
 |   770  | 
 |   771     def option_default(self, opt, optdict=None): | 
 |   772         """return the default value for an option""" | 
 |   773         if optdict is None: | 
 |   774             optdict = self.get_option_def(opt) | 
 |   775         default = optdict.get('default') | 
 |   776         if callable(default): | 
 |   777             default = default() | 
 |   778         return default | 
 |   779  | 
 |   780     def option_name(self, opt, optdict=None): | 
 |   781         """get the config attribute corresponding to opt | 
 |   782         """ | 
 |   783         if optdict is None: | 
 |   784             optdict = self.get_option_def(opt) | 
 |   785         return optdict.get('dest', opt.replace('-', '_')) | 
 |   786  | 
 |   787     def option_value(self, opt): | 
 |   788         """get the current value for the given option""" | 
 |   789         return getattr(self.config, self.option_name(opt), None) | 
 |   790  | 
 |   791     def set_option(self, opt, value, action=None, optdict=None): | 
 |   792         """method called to set an option (registered in the options list) | 
 |   793         """ | 
 |   794         # print "************ setting option", opt," to value", value | 
 |   795         if optdict is None: | 
 |   796             optdict = self.get_option_def(opt) | 
 |   797         if value is not None: | 
 |   798             value = convert(value, optdict, opt) | 
 |   799         if action is None: | 
 |   800             action = optdict.get('action', 'store') | 
 |   801         if optdict.get('type') == 'named': # XXX need specific handling | 
 |   802             optname = self.option_name(opt, optdict) | 
 |   803             currentvalue = getattr(self.config, optname, None) | 
 |   804             if currentvalue: | 
 |   805                 currentvalue.update(value) | 
 |   806                 value = currentvalue | 
 |   807         if action == 'store': | 
 |   808             setattr(self.config, self.option_name(opt, optdict), value) | 
 |   809         elif action in ('store_true', 'count'): | 
 |   810             setattr(self.config, self.option_name(opt, optdict), 0) | 
 |   811         elif action == 'store_false': | 
 |   812             setattr(self.config, self.option_name(opt, optdict), 1) | 
 |   813         elif action == 'append': | 
 |   814             opt = self.option_name(opt, optdict) | 
 |   815             _list = getattr(self.config, opt, None) | 
 |   816             if _list is None: | 
 |   817                 if isinstance(value, (list, tuple)): | 
 |   818                     _list = value | 
 |   819                 elif value is not None: | 
 |   820                     _list = [] | 
 |   821                     _list.append(value) | 
 |   822                 setattr(self.config, opt, _list) | 
 |   823             elif isinstance(_list, tuple): | 
 |   824                 setattr(self.config, opt, _list + (value,)) | 
 |   825             else: | 
 |   826                 _list.append(value) | 
 |   827         elif action == 'callback': | 
 |   828             optdict['callback'](None, opt, value, None) | 
 |   829         else: | 
 |   830             raise UnsupportedAction(action) | 
 |   831  | 
 |   832     def input_option(self, option, optdict, inputlevel=99): | 
 |   833         default = self.option_default(option, optdict) | 
 |   834         if default is REQUIRED: | 
 |   835             defaultstr = '(required): ' | 
 |   836         elif optdict.get('level', 0) > inputlevel: | 
 |   837             return | 
 |   838         elif optdict['type'] == 'password' or default is None: | 
 |   839             defaultstr = ': ' | 
 |   840         else: | 
 |   841             defaultstr = '(default: %s): ' % format_option_value(optdict, defaul
      t) | 
 |   842         print ':%s:' % option | 
 |   843         print optdict.get('help') or option | 
 |   844         inputfunc = INPUT_FUNCTIONS[optdict['type']] | 
 |   845         value = inputfunc(optdict, defaultstr) | 
 |   846         while default is REQUIRED and not value: | 
 |   847             print 'please specify a value' | 
 |   848             value = inputfunc(optdict, '%s: ' % option) | 
 |   849         if value is None and default is not None: | 
 |   850             value = default | 
 |   851         self.set_option(option, value, optdict=optdict) | 
 |   852  | 
 |   853     def get_option_def(self, opt): | 
 |   854         """return the dictionary defining an option given it's name""" | 
 |   855         assert self.options | 
 |   856         for option in self.options: | 
 |   857             if option[0] == opt: | 
 |   858                 return option[1] | 
 |   859         raise OptionError('no such option %s in section %r' | 
 |   860                           % (opt, self.name), opt) | 
 |   861  | 
 |   862  | 
 |   863     def all_options(self): | 
 |   864         """return an iterator on available options for this provider | 
 |   865         option are actually described by a 3-uple: | 
 |   866         (section, option name, option dictionary) | 
 |   867         """ | 
 |   868         for section, options in self.options_by_section(): | 
 |   869             if section is None: | 
 |   870                 if self.name is None: | 
 |   871                     continue | 
 |   872                 section = self.name.upper() | 
 |   873             for option, optiondict, value in options: | 
 |   874                 yield section, option, optiondict | 
 |   875  | 
 |   876     def options_by_section(self): | 
 |   877         """return an iterator on options grouped by section | 
 |   878  | 
 |   879         (section, [list of (optname, optdict, optvalue)]) | 
 |   880         """ | 
 |   881         sections = {} | 
 |   882         for optname, optdict in self.options: | 
 |   883             sections.setdefault(optdict.get('group'), []).append( | 
 |   884                 (optname, optdict, self.option_value(optname))) | 
 |   885         if None in sections: | 
 |   886             yield None, sections.pop(None) | 
 |   887         for section, options in sections.items(): | 
 |   888             yield section.upper(), options | 
 |   889  | 
 |   890     def options_and_values(self, options=None): | 
 |   891         if options is None: | 
 |   892             options = self.options | 
 |   893         for optname, optdict in options: | 
 |   894             yield (optname, optdict, self.option_value(optname)) | 
 |   895  | 
 |   896  | 
 |   897 class ConfigurationMixIn(OptionsManagerMixIn, OptionsProviderMixIn): | 
 |   898     """basic mixin for simple configurations which don't need the | 
 |   899     manager / providers model | 
 |   900     """ | 
 |   901     def __init__(self, *args, **kwargs): | 
 |   902         if not args: | 
 |   903             kwargs.setdefault('usage', '') | 
 |   904         kwargs.setdefault('quiet', 1) | 
 |   905         OptionsManagerMixIn.__init__(self, *args, **kwargs) | 
 |   906         OptionsProviderMixIn.__init__(self) | 
 |   907         if not getattr(self, 'option_groups', None): | 
 |   908             self.option_groups = [] | 
 |   909             for option, optdict in self.options: | 
 |   910                 try: | 
 |   911                     gdef = (optdict['group'].upper(), '') | 
 |   912                 except KeyError: | 
 |   913                     continue | 
 |   914                 if not gdef in self.option_groups: | 
 |   915                     self.option_groups.append(gdef) | 
 |   916         self.register_options_provider(self, own_group=0) | 
 |   917  | 
 |   918     def register_options(self, options): | 
 |   919         """add some options to the configuration""" | 
 |   920         options_by_group = {} | 
 |   921         for optname, optdict in options: | 
 |   922             options_by_group.setdefault(optdict.get('group', self.name.upper()),
       []).append((optname, optdict)) | 
 |   923         for group, options in options_by_group.items(): | 
 |   924             self.add_option_group(group, None, options, self) | 
 |   925         self.options += tuple(options) | 
 |   926  | 
 |   927     def load_defaults(self): | 
 |   928         OptionsProviderMixIn.load_defaults(self) | 
 |   929  | 
 |   930     def __iter__(self): | 
 |   931         return iter(self.config.__dict__.iteritems()) | 
 |   932  | 
 |   933     def __getitem__(self, key): | 
 |   934         try: | 
 |   935             return getattr(self.config, self.option_name(key)) | 
 |   936         except (optparse.OptionValueError, AttributeError): | 
 |   937             raise KeyError(key) | 
 |   938  | 
 |   939     def __setitem__(self, key, value): | 
 |   940         self.set_option(key, value) | 
 |   941  | 
 |   942     def get(self, key, default=None): | 
 |   943         try: | 
 |   944             return getattr(self.config, self.option_name(key)) | 
 |   945         except (OptionError, AttributeError): | 
 |   946             return default | 
 |   947  | 
 |   948  | 
 |   949 class Configuration(ConfigurationMixIn): | 
 |   950     """class for simple configurations which don't need the | 
 |   951     manager / providers model and prefer delegation to inheritance | 
 |   952  | 
 |   953     configuration values are accessible through a dict like interface | 
 |   954     """ | 
 |   955  | 
 |   956     def __init__(self, config_file=None, options=None, name=None, | 
 |   957                  usage=None, doc=None, version=None): | 
 |   958         if options is not None: | 
 |   959             self.options = options | 
 |   960         if name is not None: | 
 |   961             self.name = name | 
 |   962         if doc is not None: | 
 |   963             self.__doc__ = doc | 
 |   964         super(Configuration, self).__init__(config_file=config_file, usage=usage
      , version=version) | 
 |   965  | 
 |   966  | 
 |   967 class OptionsManager2ConfigurationAdapter(object): | 
 |   968     """Adapt an option manager to behave like a | 
 |   969     `logilab.common.configuration.Configuration` instance | 
 |   970     """ | 
 |   971     def __init__(self, provider): | 
 |   972         self.config = provider | 
 |   973  | 
 |   974     def __getattr__(self, key): | 
 |   975         return getattr(self.config, key) | 
 |   976  | 
 |   977     def __getitem__(self, key): | 
 |   978         provider = self.config._all_options[key] | 
 |   979         try: | 
 |   980             return getattr(provider.config, provider.option_name(key)) | 
 |   981         except AttributeError: | 
 |   982             raise KeyError(key) | 
 |   983  | 
 |   984     def __setitem__(self, key, value): | 
 |   985         self.config.global_set_option(self.config.option_name(key), value) | 
 |   986  | 
 |   987     def get(self, key, default=None): | 
 |   988         provider = self.config._all_options[key] | 
 |   989         try: | 
 |   990             return getattr(provider.config, provider.option_name(key)) | 
 |   991         except AttributeError: | 
 |   992             return default | 
 |   993  | 
 |   994  | 
 |   995 def read_old_config(newconfig, changes, configfile): | 
 |   996     """initialize newconfig from a deprecated configuration file | 
 |   997  | 
 |   998     possible changes: | 
 |   999     * ('renamed', oldname, newname) | 
 |  1000     * ('moved', option, oldgroup, newgroup) | 
 |  1001     * ('typechanged', option, oldtype, newvalue) | 
 |  1002     """ | 
 |  1003     # build an index of changes | 
 |  1004     changesindex = {} | 
 |  1005     for action in changes: | 
 |  1006         if action[0] == 'moved': | 
 |  1007             option, oldgroup, newgroup = action[1:] | 
 |  1008             changesindex.setdefault(option, []).append((action[0], oldgroup, new
      group)) | 
 |  1009             continue | 
 |  1010         if action[0] == 'renamed': | 
 |  1011             oldname, newname = action[1:] | 
 |  1012             changesindex.setdefault(newname, []).append((action[0], oldname)) | 
 |  1013             continue | 
 |  1014         if action[0] == 'typechanged': | 
 |  1015             option, oldtype, newvalue = action[1:] | 
 |  1016             changesindex.setdefault(option, []).append((action[0], oldtype, newv
      alue)) | 
 |  1017             continue | 
 |  1018         if action[1] in ('added', 'removed'): | 
 |  1019             continue # nothing to do here | 
 |  1020         raise Exception('unknown change %s' % action[0]) | 
 |  1021     # build a config object able to read the old config | 
 |  1022     options = [] | 
 |  1023     for optname, optdef in newconfig.options: | 
 |  1024         for action in changesindex.pop(optname, ()): | 
 |  1025             if action[0] == 'moved': | 
 |  1026                 oldgroup, newgroup = action[1:] | 
 |  1027                 optdef = optdef.copy() | 
 |  1028                 optdef['group'] = oldgroup | 
 |  1029             elif action[0] == 'renamed': | 
 |  1030                 optname = action[1] | 
 |  1031             elif action[0] == 'typechanged': | 
 |  1032                 oldtype = action[1] | 
 |  1033                 optdef = optdef.copy() | 
 |  1034                 optdef['type'] = oldtype | 
 |  1035         options.append((optname, optdef)) | 
 |  1036     if changesindex: | 
 |  1037         raise Exception('unapplied changes: %s' % changesindex) | 
 |  1038     oldconfig = Configuration(options=options, name=newconfig.name) | 
 |  1039     # read the old config | 
 |  1040     oldconfig.load_file_configuration(configfile) | 
 |  1041     # apply values reverting changes | 
 |  1042     changes.reverse() | 
 |  1043     done = set() | 
 |  1044     for action in changes: | 
 |  1045         if action[0] == 'renamed': | 
 |  1046             oldname, newname = action[1:] | 
 |  1047             newconfig[newname] = oldconfig[oldname] | 
 |  1048             done.add(newname) | 
 |  1049         elif action[0] == 'typechanged': | 
 |  1050             optname, oldtype, newvalue = action[1:] | 
 |  1051             newconfig[optname] = newvalue | 
 |  1052             done.add(optname) | 
 |  1053     for optname, optdef in newconfig.options: | 
 |  1054         if optdef.get('type') and not optname in done: | 
 |  1055             newconfig.set_option(optname, oldconfig[optname], optdict=optdef) | 
 |  1056  | 
 |  1057  | 
 |  1058 def merge_options(options): | 
 |  1059     """preprocess options to remove duplicate""" | 
 |  1060     alloptions = {} | 
 |  1061     options = list(options) | 
 |  1062     for i in range(len(options)-1, -1, -1): | 
 |  1063         optname, optdict = options[i] | 
 |  1064         if optname in alloptions: | 
 |  1065             options.pop(i) | 
 |  1066             alloptions[optname].update(optdict) | 
 |  1067         else: | 
 |  1068             alloptions[optname] = optdict | 
 |  1069     return tuple(options) | 
| OLD | NEW |