patman: Split parser creation from parsing

Tests may want to parse their own arguments. Refactor the parser code to
support this and allow settings to receive arguments as well.

Signed-off-by: Simon Glass <sjg@chromium.org>
This commit is contained in:
Simon Glass
2025-05-08 05:58:10 +02:00
parent a52e80aee7
commit 79addcc904
3 changed files with 44 additions and 18 deletions

View File

@@ -20,13 +20,11 @@ from patman import settings
PATMAN_DIR = pathlib.Path(__file__).parent
HAS_TESTS = os.path.exists(PATMAN_DIR / "func_test.py")
def parse_args():
"""Parse command line arguments from sys.argv[]
def setup_parser():
"""Set up command-line parser
Returns:
tuple containing:
options: command line options
args: command lin arguments
argparse.Parser object
"""
epilog = '''Create patches from commits in a branch, check them and email
them as specified by tags you place in the commits. Use -n to do a dry
@@ -132,14 +130,34 @@ def parse_args():
help='Force overwriting an existing branch')
status.add_argument('-T', '--single-thread', action='store_true',
help='Disable multithreading when reading patchwork')
return parser
def parse_args(argv=None, config_fname=None, parser=None):
"""Parse command line arguments from sys.argv[]
Args:
argv (str or None): Arguments to process, or None to use sys.argv[1:]
config_fname (str): Config file to read, or None for default, or False
for an empty config
Returns:
tuple containing:
options: command line options
args: command lin arguments
"""
if not parser:
parser = setup_parser()
# Parse options twice: first to get the project and second to handle
# defaults properly (which depends on project)
# Use parse_known_args() in case 'cmd' is omitted
argv = sys.argv[1:]
if not argv:
argv = sys.argv[1:]
args, rest = parser.parse_known_args(argv)
if hasattr(args, 'project'):
settings.Setup(parser, args.project)
settings.Setup(parser, args.project, argv, config_fname)
args, rest = parser.parse_known_args(argv)
# If we have a command, it is safe to parse all arguments

View File

@@ -226,7 +226,7 @@ nxp = Zhikang Zhang <zhikang.zhang@nxp.com>
f.close()
def _UpdateDefaults(main_parser, config):
def _UpdateDefaults(main_parser, config, argv):
"""Update the given OptionParser defaults based on config.
We'll walk through all of the settings from all parsers.
@@ -242,6 +242,7 @@ def _UpdateDefaults(main_parser, config):
updated.
config: An instance of _ProjectConfigParser that we will query
for settings.
argv (list of str or None): Arguments to parse
"""
# Find all the parsers and subparsers
parsers = [main_parser]
@@ -252,6 +253,7 @@ def _UpdateDefaults(main_parser, config):
# Collect the defaults from each parser
defaults = {}
parser_defaults = []
argv = list(argv)
for parser in parsers:
pdefs = parser.parse_known_args()[0]
parser_defaults.append(pdefs)
@@ -273,9 +275,11 @@ def _UpdateDefaults(main_parser, config):
# Set all the defaults and manually propagate them to subparsers
main_parser.set_defaults(**defaults)
assert len(parsers) == len(parser_defaults)
for parser, pdefs in zip(parsers, parser_defaults):
parser.set_defaults(**{k: v for k, v in defaults.items()
if k in pdefs})
return defaults
def _ReadAliasFile(fname):
@@ -334,7 +338,7 @@ def GetItems(config, section):
return []
def Setup(parser, project_name, config_fname=None):
def Setup(parser, project_name, argv, config_fname=None):
"""Set up the settings module by reading config files.
Unless `config_fname` is specified, a `.patman` config file local
@@ -347,8 +351,9 @@ def Setup(parser, project_name, config_fname=None):
parser: The parser to update.
project_name: Name of project that we're working on; we'll look
for sections named "project_section" as well.
config_fname: Config filename to read. An error is raised if it
does not exist.
config_fname: Config filename to read, or None for default, or False
for an empty config. An error is raised if it does not exist.
argv (list of str or None): Arguments to parse, or None for default
"""
# First read the git alias file if available
_ReadAliasFile('doc/git-mailrc')
@@ -357,12 +362,15 @@ def Setup(parser, project_name, config_fname=None):
if config_fname and not os.path.exists(config_fname):
raise Exception(f'provided {config_fname} does not exist')
if not config_fname:
if config_fname is None:
config_fname = '%s/.patman' % os.getenv('HOME')
has_config = os.path.exists(config_fname)
git_local_config_fname = os.path.join(gitutil.get_top_level(), '.patman')
has_git_local_config = os.path.exists(git_local_config_fname)
has_config = False
has_git_local_config = False
if config_fname is not False:
has_config = os.path.exists(config_fname)
has_git_local_config = os.path.exists(git_local_config_fname)
# Read the git local config last, so that its values override
# those of the global config, if any.
@@ -371,7 +379,7 @@ def Setup(parser, project_name, config_fname=None):
if has_git_local_config:
config.read(git_local_config_fname)
if not (has_config or has_git_local_config):
if config_fname is not False and not (has_config or has_git_local_config):
print("No config file found.\nCreating ~/.patman...\n")
CreatePatmanConfigFile(config_fname)
@@ -382,7 +390,7 @@ def Setup(parser, project_name, config_fname=None):
for name, value in GetItems(config, 'bounces'):
bounces.add(value)
_UpdateDefaults(parser, config)
return _UpdateDefaults(parser, config, argv)
# These are the aliases we understand, indexed by alias. Each member is a list.

View File

@@ -49,7 +49,7 @@ def test_git_local_config():
dest='check_patch', default=True)
# Test "global" config is used.
settings.Setup(parser, 'unknown', global_config.name)
settings.Setup(parser, 'unknown', None, global_config.name)
args, _ = parser.parse_known_args([])
assert args.project == 'u-boot'
send_args, _ = send.parse_known_args([])