[PATCH 14/20] patman: Split parser creation from parsing

Simon Glass sjg at chromium.org
Thu May 8 09:28:38 CEST 2025


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 at chromium.org>
---

 tools/patman/cmdline.py       | 32 +++++++++++++++++++++++++-------
 tools/patman/settings.py      | 28 ++++++++++++++++++----------
 tools/patman/test_settings.py |  2 +-
 3 files changed, 44 insertions(+), 18 deletions(-)

diff --git a/tools/patman/cmdline.py b/tools/patman/cmdline.py
index cf32716b8e0..e5ac4fb1684 100644
--- a/tools/patman/cmdline.py
+++ b/tools/patman/cmdline.py
@@ -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
diff --git a/tools/patman/settings.py b/tools/patman/settings.py
index d66b22be1df..7a0866cd370 100644
--- a/tools/patman/settings.py
+++ b/tools/patman/settings.py
@@ -226,7 +226,7 @@ nxp = Zhikang Zhang <zhikang.zhang at 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.
diff --git a/tools/patman/test_settings.py b/tools/patman/test_settings.py
index 06b7cbc3ab6..c117836de31 100644
--- a/tools/patman/test_settings.py
+++ b/tools/patman/test_settings.py
@@ -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([])
-- 
2.43.0



More information about the U-Boot mailing list