#!/usr/bin/env python2 # -*- coding: utf-8 -*- """Combined config file and argument parser.""" from ConfigParser import SafeConfigParser, NoOptionError from argparse import ArgumentParser import sys class _Dummy(object): """Placeholder for config sections.""" pass class ComboParser(object): """Parse configuration from INI file and command-line arguments. Command-line arguments override INI file values. """ def __init__(self, ini): self.items = [] self.cparser = SafeConfigParser() self.aparser = ArgumentParser() self.ini = ini self.loaded = False self.args = None def add_item(self, section, name, type, default, desc, required): """Add a configuration item.""" def str2bool(val): return val.lower() in ('true', '1', 'yes') self.items.append({ 'section': section, 'name': name, 'type': type, 'default': default, 'required': required, }) self.aparser.add_argument( '--%s.%s' % (section, name), help='%s (default: %s)' % (desc, default), type=type if type is not bool else str2bool, default=None, required=False ) def load(self): """Load configuration from file and command-line.""" if self.loaded: return self.loaded = True try: self.cparser.read(self.ini) except Exception: pass # Config file missing or unreadable, use defaults self.args = self.aparser.parse_args() for item in self.items: section = item['section'] name = item['name'] # Ensure section object exists if not hasattr(self, section): setattr(self, section, _Dummy()) obj = getattr(self, section) # Start with default value value = item['default'] found = False # Try to read from config file try: if item['type'] is bool: value = self.cparser.getboolean(section, name) elif item['type'] is float: value = self.cparser.getfloat(section, name) elif item['type'] is int: value = self.cparser.getint(section, name) elif item['type'] is str: value = self.cparser.get(section, name) found = True except NoOptionError: pass # Command-line overrides config file arg_name = '%s.%s' % (section, name) arg_value = getattr(self.args, arg_name, None) if arg_value is not None: value = arg_value found = True # Handle missing required items if not found: if item['required']: sys.stderr.write( 'error: required config item "%s" not found in section "%s" of "%s"\n' % (name, section, self.ini) ) sys.exit(1) else: sys.stderr.write( 'warning: assigned default value of "%s" to "%s.%s"\n' % (item['default'], section, name) ) setattr(obj, name, value)