Example #1
0
def _construct_options_parser(config: PrescientConfig) -> ArgumentParser:
    '''
    Make a new parser that can parse standard and custom command line options.

    Custom options are provided by plugin modules. Plugins are specified
    as command-line arguments "--plugin=<alias>:<module name>", where <alias>
    is the name the plugin will be known as in the configuration, and 
    <module name> identifies a python module by name or by path. Any configuration
    items defined by the plugin will be available at config.plugin.alias.
    '''

    # To support the ability to add new command line options to the line that
    # is currently being parsed, we convert a standard ArgumentParser into a
    # two-pass parser by replacing the parser's parse_args method with a
    # modified version.  In the modified parse_args, a first pass through the
    # command line finds any --plugin arguments and allows the plugin to
    # add new options to the parser.  The second pass does a full parse of
    # the command line using the parser's original parse_args method.
    parser = ArgumentParser()
    parser._inner_parse = parser.parse_args

    def split_plugin_spec(spec: str) -> (str, str):
        ''' Return the plugin's alias and module from the plugin name/path
        '''
        result = spec.split(':', 1)
        if len(result) == 1:
            raise ValueError(
                "No alias found in plugin specification.  Correct format: <alias>:<module_path_or_name>"
            )
        return result

    def outer_parse(args=None, values=None):
        if args is None:
            args = sys.argv[1:]

        # Manually check each argument against --plugin=<alias>:<module>,
        # give plugins a chance to install their options.
        stand_alone_opt = '--plugin'
        prefix = "--plugin="
        next_arg_is_module = False
        found_plugin = False
        for arg in args:
            if next_arg_is_module:
                module_spec = arg
                alias, mod = split_plugin_spec(module_spec)
                config.plugin[alias] = mod
                next_arg_is_module = False
                found_plugin = True
            elif arg.startswith(prefix):
                module_spec = arg[len(prefix):]
                alias, mod = split_plugin_spec(module_spec)
                config.plugin[alias] = mod
                found_plugin = True
            elif arg == stand_alone_opt:
                next_arg_is_module = True

        # load the arguments into the ArgumentParser
        config.initialize_argparse(parser)

        # Remove plugins from args so they don't get re-handled
        if found_plugin:
            args = args.copy()
            i = 0
            while (i < len(args)):
                if args[i].startswith(prefix):
                    del (args[i])
                elif args[i] == stand_alone_opt:
                    del (args[i])
                    del (args[i])
                else:
                    i += 1

        # Now parse for real, with any new options in place.
        return parser._inner_parse(args, values)

    parser.parse_args = outer_parse
    return parser