1 # SPDX-License-Identifier: GPL-2.0+
2 # Copyright (c) 2011 The Chromium OS Authors.
6 import configparser as ConfigParser
14 """Default settings per-project.
16 These are used by _ProjectConfigParser. Settings names should match
17 the "dest" of the option parser from patman.py.
22 "process_tags": "False",
23 "check_patch_use_tree": "True",
26 "process_tags": "False",
27 "add_signoff": "False",
28 "check_patch": "False",
33 class _ProjectConfigParser(ConfigParser.ConfigParser):
34 """ConfigParser that handles projects.
36 There are two main goals of this class:
37 - Load project-specific default settings.
38 - Merge general default settings/aliases with project-specific ones.
40 # Sample config used for tests below...
41 >>> from io import StringIO
42 >>> sample_config = '''
44 ... me: Peter P. <likesspiders@example.com>
45 ... enemies: Evil <evil@example.com>
48 ... enemies: Green G. <ugly@example.com>
51 ... enemies: Doc O. <pus@example.com>
57 # Check to make sure that bogus project gets general alias.
58 >>> config = _ProjectConfigParser("zzz")
59 >>> config.readfp(StringIO(sample_config))
60 >>> str(config.get("alias", "enemies"))
61 'Evil <evil@example.com>'
63 # Check to make sure that alias gets overridden by project.
64 >>> config = _ProjectConfigParser("sm")
65 >>> config.readfp(StringIO(sample_config))
66 >>> str(config.get("alias", "enemies"))
67 'Green G. <ugly@example.com>'
69 # Check to make sure that settings get merged with project.
70 >>> config = _ProjectConfigParser("linux")
71 >>> config.readfp(StringIO(sample_config))
72 >>> sorted((str(a), str(b)) for (a, b) in config.items("settings"))
73 [('am_hero', 'True'), ('check_patch_use_tree', 'True'), ('process_tags', 'False')]
75 # Check to make sure that settings works with unknown project.
76 >>> config = _ProjectConfigParser("unknown")
77 >>> config.readfp(StringIO(sample_config))
78 >>> sorted((str(a), str(b)) for (a, b) in config.items("settings"))
81 def __init__(self, project_name):
82 """Construct _ProjectConfigParser.
84 In addition to standard ConfigParser initialization, this also
85 loads project defaults.
88 project_name: The name of the project.
90 self._project_name = project_name
91 ConfigParser.ConfigParser.__init__(self)
93 # Update the project settings in the config based on
94 # the _default_settings global.
95 project_settings = "%s_settings" % project_name
96 if not self.has_section(project_settings):
97 self.add_section(project_settings)
98 project_defaults = _default_settings.get(project_name, {})
99 for setting_name, setting_value in project_defaults.items():
100 self.set(project_settings, setting_name, setting_value)
102 def get(self, section, option, *args, **kwargs):
103 """Extend ConfigParser to try project_section before section.
111 val = ConfigParser.ConfigParser.get(
112 self, "%s_%s" % (self._project_name, section), option,
115 except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
116 val = ConfigParser.ConfigParser.get(
117 self, section, option, *args, **kwargs
121 def items(self, section, *args, **kwargs):
122 """Extend ConfigParser to add project_section to section.
130 has_project_section = False
133 # Get items from the project section
135 project_items = ConfigParser.ConfigParser.items(
136 self, "%s_%s" % (self._project_name, section), *args, **kwargs
138 has_project_section = True
139 except ConfigParser.NoSectionError:
142 # Get top-level items
144 top_items = ConfigParser.ConfigParser.items(
145 self, section, *args, **kwargs
147 except ConfigParser.NoSectionError:
148 # If neither section exists raise the error on...
149 if not has_project_section:
152 item_dict = dict(top_items)
153 item_dict.update(project_items)
154 return {(item, val) for item, val in item_dict.items()}
157 def ReadGitAliases(fname):
158 """Read a git alias file. This is in the form used by git:
160 alias uboot u-boot@lists.denx.de
161 alias wd Wolfgang Denk <wd@denx.de>
164 fname: Filename to read
167 fd = open(fname, 'r', encoding='utf-8')
169 print("Warning: Cannot find alias file '%s'" % fname)
172 re_line = re.compile(r'alias\s+(\S+)\s+(.*)')
173 for line in fd.readlines():
175 if not line or line[0] == '#':
178 m = re_line.match(line)
180 print("Warning: Alias file line '%s' not understood" % line)
183 list = alias.get(m.group(1), [])
184 for item in m.group(2).split(','):
188 alias[m.group(1)] = list
193 def CreatePatmanConfigFile(gitutil, config_fname):
194 """Creates a config file under $(HOME)/.patman if it can't find one.
197 config_fname: Default config filename i.e., $(HOME)/.patman
202 name = gitutil.get_default_user_name()
204 name = input("Enter name: ")
206 email = gitutil.get_default_user_email()
209 email = input("Enter email: ")
212 f = open(config_fname, 'w')
214 print("Couldn't create patman config file\n")
221 nxp = Zhikang Zhang <zhikang.zhang@nxp.com>
222 ''' % (name, email), file=f)
226 def _UpdateDefaults(main_parser, config):
227 """Update the given OptionParser defaults based on config.
229 We'll walk through all of the settings from all parsers.
230 For each setting we'll look for a default in the option parser.
231 If it's found we'll update the option parser default.
233 The idea here is that the .patman file should be able to update
234 defaults but that command line flags should still have the final
238 parser: An instance of an ArgumentParser whose defaults will be
240 config: An instance of _ProjectConfigParser that we will query
243 # Find all the parsers and subparsers
244 parsers = [main_parser]
245 parsers += [subparser for action in main_parser._actions
246 if isinstance(action, argparse._SubParsersAction)
247 for _, subparser in action.choices.items()]
249 # Collect the defaults from each parser
252 for parser in parsers:
253 pdefs = parser.parse_known_args()[0]
254 parser_defaults.append(pdefs)
255 defaults.update(vars(pdefs))
257 # Go through the settings and collect defaults
258 for name, val in config.items('settings'):
260 default_val = defaults[name]
261 if isinstance(default_val, bool):
262 val = config.getboolean('settings', name)
263 elif isinstance(default_val, int):
264 val = config.getint('settings', name)
265 elif isinstance(default_val, str):
266 val = config.get('settings', name)
269 print("WARNING: Unknown setting %s" % name)
271 # Set all the defaults and manually propagate them to subparsers
272 main_parser.set_defaults(**defaults)
273 for parser, pdefs in zip(parsers, parser_defaults):
274 parser.set_defaults(**{k: v for k, v in defaults.items()
278 def _ReadAliasFile(fname):
279 """Read in the U-Boot git alias file if it exists.
282 fname: Filename to read.
284 if os.path.exists(fname):
286 with open(fname, encoding='utf-8') as fd:
291 if not line or line.startswith('#'):
293 words = line.split(None, 2)
294 if len(words) < 3 or words[0] != 'alias':
296 bad_line = "%s:%d:Invalid line '%s'" % (fname, linenum,
299 alias[words[1]] = [s.strip() for s in words[2].split(',')]
304 def _ReadBouncesFile(fname):
305 """Read in the bounces file if it exists
308 fname: Filename to read.
310 if os.path.exists(fname):
311 with open(fname) as fd:
313 if line.startswith('#'):
315 bounces.add(line.strip())
318 def GetItems(config, section):
319 """Get the items from a section of the config.
322 config: _ProjectConfigParser object containing settings
323 section: name of section to retrieve
326 List of (name, value) tuples for the section
329 return config.items(section)
330 except ConfigParser.NoSectionError:
334 def Setup(gitutil, parser, project_name, config_fname=''):
335 """Set up the settings module by reading config files.
338 parser: The parser to update
339 project_name: Name of project that we're working on; we'll look
340 for sections named "project_section" as well.
341 config_fname: Config filename to read ('' for default)
343 # First read the git alias file if available
344 _ReadAliasFile('doc/git-mailrc')
345 config = _ProjectConfigParser(project_name)
346 if config_fname == '':
347 config_fname = '%s/.patman' % os.getenv('HOME')
349 if not os.path.exists(config_fname):
350 print("No config file found ~/.patman\nCreating one...\n")
351 CreatePatmanConfigFile(gitutil, config_fname)
353 config.read(config_fname)
355 for name, value in GetItems(config, 'alias'):
356 alias[name] = value.split(',')
358 _ReadBouncesFile('doc/bounces')
359 for name, value in GetItems(config, 'bounces'):
362 _UpdateDefaults(parser, config)
365 # These are the aliases we understand, indexed by alias. Each member is a list.
369 if __name__ == "__main__":