view configuration/config.py @ 23:73e72a764c3a

fix bool parser, i hope
author Jeff Hammel <jhammel@mozilla.com>
date Mon, 26 Mar 2012 15:14:26 -0700
parents 0fe74db6a56c
children 39f2611db9be
line wrap: on
line source

#!/usr/bin/env python

"""
multi-level unified configuration
"""

import sys
import optparse

# imports for contigent configuration providers
try:
    import json
except ImportError:
    try:
        import simplejson as json
    except ImportError:
        json = None
try:
    import yaml
except ImportError:
    yaml = None

__all__ = ['Configuration', 'configuration_providers']

configuration_providers = []
if json:
    class JSON(object):
        extensions = ['json']
        def read(self, filename):
            return json.loads(file(filename).read())
    configuration_providers.append(JSON)

if yaml:
    class YAML(object):
        extensions = ['yml']
        def read(self, filename):
            f = file(filename)
            config = yaml.load(f)
            f.close()
            return config

    configuration_providers.append(YAML)

### plugins for option types
### TODO: this could use a bit of thought
def base_cli(name, value):
    # CLI arguments
    args = value.get('flags', ['--%s' % name])
    if not args:
        # No CLI interface
        return (), {}

    kw = {'dest': name}
    help = value.get('help', name)
    if 'default' in value:
        kw['default'] = value['default']
        # TODO: use default pattern a la
        # - http://hg.mozilla.org/build/talos/file/c6013a2f09ce/talos/PerfConfigurator.py#l358
        # - http://k0s.org/mozilla/hg/bzconsole/file/d5e88dadde69/bzconsole/command.py#l12

        help += ' [DEFAULT: %s]' % value['default']
    kw['help'] = help
    kw['action'] = 'store'
    return args, kw

def bool_cli(name, value):

    # preserve the default values
    help = value.get('help')
    flags = value.get('flags')

    args, kw = base_cli(name, value)
    kw['help'] = help # reset
    if value.get('default'):
        kw['action'] = 'store_false'
        if not flags:
            args = ['--no-%s' % name]
        if not help:
            kw['help'] = 'disable %s' % name
    else:
        kw['action'] = 'store_true'
        if not help:
            kw['help'] = 'enable %s' % name
    return args, kw

def list_cli(name, value):
    args, kw = base_cli(name, value)

    # TODO: could use 'extend'
    # - http://hg.mozilla.org/build/mozharness/file/5f44ba08f4be/mozharness/base/config.py#l41

    # TODO: what about nested types?
    kw['action'] = 'append'
    return args, kw

def int_cli(name, value):
    args, kw = base_cli(name, value)
    kw['type'] = 'int'
    return args, kw

def float_cli(name, value):
    args, kw = base_cli(name, value)
    kw['type'] = 'float'
    return args, kw

types = {bool:  bool_cli,
         int:   int_cli,
         float: float_cli,
         list:  list_cli,
         None:  base_cli} # default

class Configuration(object):
    options = {}

    def __init__(self, configuration_providers=configuration_providers, types=types):
        self.config = {}
        self.configuration_providers = configuration_providers
        self.types = types

    def items(self):
        # TODO: allow options to be a list of 2-tuples
        return self.options.items()

    def check(self, config):
        """check validity of configuration"""

        # TODO: ensure options in configuration are in self.options
        unknown_options = []

        # TODO: ensure options are of the right type (if specified)

    def __call__(self, *args):
        """add items to configuration and check it"""

    def add(self, config):
        """update configuration: not undoable"""

        self.check(config)

        self.config.update(config)
        # TODO: option to extend; augment lists/dicts

    ### methods for optparse
    ### XXX could go in a subclass

    def optparse_options(self, parser):
        """add optparse options to a OptionParser instance"""
        for key, value in self.items():
            handler = self.types[value.get('type')]
            args, kw = handler(key, value)
            if not args:
                # No CLI interface
                continue
            parser.add_option(*args, **kw)

    def parser(self, configuration_provider_option=None, **parser_args):
        """
        return OptionParser for this Configuration instance
        - configuration_provider_options : option for configuration files [TODO]
        (also TODO: a special value that equates to the first file extension value
        for the configuration_providers)
        - parser_args : arguments to the OptionParser constructor
        """
        if 'description' not in parser_args:
            parser_args['description'] = getattr(self, '__doc__', '')
            if 'formatter' not in parser_args:
                class PlainDescriptionFormatter(optparse.IndentedHelpFormatter):
                    """description formatter for console script entry point"""
                    def format_description(self, description):
                        if description:
                            return description.strip() + '\n'
                        else:
                            return ''
                parser_args['formatter'] = PlainDescriptionFormatter()
        parser = optparse.OptionParser(**parser_args)
        self.optparse_options(parser)
        return parser

    def parse(self, args=sys.argv[1:], parser=None):
        """parse configuration including command line options"""

        # parse arguments
        if parser is None:
            parser = self.parser()
        options, args = parser.parse_args(args)

        # return parsed arguments
        return options, args