Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(217)

Side by Side Diff: third_party/logilab/common/configuration.py

Issue 10447014: Add pylint to depot_tools. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/depot_tools
Patch Set: Fix unittests. Created 8 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « third_party/logilab/common/compat.py ('k') | third_party/logilab/common/contexts.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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)
OLDNEW
« no previous file with comments | « third_party/logilab/common/compat.py ('k') | third_party/logilab/common/contexts.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698