Source

confargparse / confargparse.py

import argparse
import ConfigParser
import sys
from types import MethodType

def _add_argument(self, *args, **kwargs):
        chars = self.prefix_chars
        if not args or len(args) == 1 and args[0][0] not in chars:
            my_kwargs = self._get_positional_kwargs(*args, **kwargs)
        else:
            my_kwargs = self._get_optional_kwargs(*args, **kwargs)

        dest = my_kwargs["dest"]

        section = my_kwargs.get("conf_section", "defaults")
        name = my_kwargs.get("conf_name", dest)
        tipe = my_kwargs.get("conf_type", my_kwargs.get("type",str))
        exclude = my_kwargs.get("conf_exclude", False)

        if "conf_section" in kwargs: del kwargs["conf_section"]
        if "conf_name" in kwargs: del kwargs["conf_name"]
        if "conf_type" in kwargs: del kwargs["conf_type"]
        if "conf_exclude" in kwargs: del kwargs["conf_exclude"]

        if not exclude: self.add_mapping(dest, section, name, tipe)

        return argparse._ActionsContainer.add_argument(self, *args, **kwargs)

def _add_parser(self, *args, **kwargs):
    parser = _prior_add_parser(self, *args, **kwargs)
    parser.add_mapping = self.add_mapping
    return parser

_prior_add_parser = argparse._SubParsersAction.add_parser
argparse._SubParsersAction.add_parser = _add_parser

class ConfArgParser(argparse.ArgumentParser):
    def __init__(self, *args, **kwargs):
        ckwargs = dict(kwargs)
        ckwargs["add_help"] = False
        self.conf_parser = argparse.ArgumentParser( *args, **ckwargs )
        group = self.conf_parser.add_argument_group("configuration file options")
        group.add_argument("-c", "--conf_file",  help="specify config files", nargs="+", metavar="FILE")
        group.add_argument("--export_conf_file", help="translate arguments into a config file",
                metavar="FILE", const=sys.stdout, default=None, nargs="?", type=argparse.FileType('w'))

        self._added_config_args = False
        kwargs["formatter_class"] = argparse.RawDescriptionHelpFormatter

        self.config_mapping = {}
        self.dest_mapping = {}

        argparse.ArgumentParser.__init__(self,*args, **kwargs)

    add_argument = _add_argument

    def add_argument_group(self, *args, **kwargs):
        import types
        group = argparse.ArgumentParser.add_argument_group(self, *args, **kwargs)
        group.add_argument = MethodType(_add_argument, group, group.__class__)
        group.add_mapping = self.add_mapping
        return group

    def add_subparsers(self, *args, **kwargs):
        parser = argparse.ArgumentParser.add_subparsers(self, *args, **kwargs)
        parser.add_mapping = self.add_mapping
        return parser

    def add_mutually_exclusive_groupi(self, *args, **kwargs):
        group = argparse.ArgumentParser.add_mutually_exclusive_group(self, *args, **kwargs)
        group.add_argument = MethodType(_add_argument, group, group.__class__)
        group.add_mapping = self.add_mapping
        return group

    def add_mapping(self, dest, section, name, type=str, warn=True):
        name = name.lower()
        section = section.lower()

        if warn and (section, name) in self.dest_mapping:
            d,t = self.dest_mapping[(section, name)]
            if d!= dest:
                raise ValueError("Changing destination of this config address!")
        if warn and dest in self.config_mapping:
            s,n,t = self.config_mapping[dest]
            if s != section or n != name:
                raise ValueError("Changing config address of this destination!")

        self.config_mapping[dest] = (section, name, type)
        self.dest_mapping[(section, name)] = (dest, type)

    def config2dest(self, config):
        args = {}
        for dest, (section, name, tipe) in self.config_mapping.items():
            try: val = tipe(config.get(section, name))
            except: continue
            if val == "<stdin>": args[dest] = sys.stdin
            elif val == "<stdout>": args[dest] = sys.stdout
            elif val != "":  args[dest] = tipe(val)
        return args

    def args2config(self, args=sys.argv[1:]):
        args = self.parse_args(args)
        config = ConfigParser.RawConfigParser()
        sections = set()
        config.add_section("defaults")

        for dest, (section, name, tipe) in self.config_mapping.items():
            if section not in sections and section.lower() != "defaults": config.add_section(section)
            try: a = args.__getattribute__(dest)
            except: continue

            if type(a) == file: val = a.name
            else: val = a

            if val == None: config.set(section, name, "")
            else: config.set(section, name, val)

        return config

    def _add_config_args(self):
        if not self._added_config_args:
           self._added_config_args=True
           if self._subparsers == None: self._add_container_actions(self.conf_parser)

    def _parse_config(self, args):
        args, remaining_argv = self.conf_parser.parse_known_args(args)

        if args.conf_file:
            config = ConfigParser.SafeConfigParser()
            config.read(args.conf_file)
            config_options = self.config2dest(config)
            self.set_defaults(**config_options)
        if args.export_conf_file:
            self.args2config(remaining_argv).write(args.export_conf_file)
            sys.exit(0)
        return remaining_argv

    def parse_known_args(self, args=sys.argv[1:], namespace=None):
        self._add_config_args()
        args = self._parse_config(args)
        return argparse.ArgumentParser.parse_known_args(self, args, namespace)

    def parse_args(self, args=sys.argv[1:], namespace=None):
        self._add_config_args()
        args = self._parse_config(args)
        return argparse.ArgumentParser.parse_args(self, args, namespace)



parser = ConfArgParser()
#parser.add_argument("--option1", default=None, conf_section="opt1", dest="opt11", help="some option")
#parser.add_argument("--option2", default=sys.stdin, help="some other option")
group = parser.add_argument_group("test group")
group.add_argument("-f", type=int)

subparsers = parser.add_subparsers(help='sub-command help')
parser_a = subparsers.add_parser('a', help='a help')
parser_a.add_argument('bar', type=int, help='bar help')

parser_b = subparsers.add_parser('b', help='b help')
parser_b.add_argument('--baz', choices='XYZ', help='baz help')

parser.args2config().write(sys.stdout)
print parser.parse_args()