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