1 # SPDX-License-Identifier: GPL-2.0+
2 # Copyright (c) 2011 The Chromium OS Authors.
6 import configparser as ConfigParser
14 from patman import command
15 from patman import tools
17 """Default settings per-project.
19 These are used by _ProjectConfigParser. Settings names should match
20 the "dest" of the option parser from patman.py.
25 "process_tags": "False",
28 "process_tags": "False",
29 "add_signoff": "False",
30 "check_patch": "False",
34 class _ProjectConfigParser(ConfigParser.SafeConfigParser):
35 """ConfigParser that handles projects.
37 There are two main goals of this class:
38 - Load project-specific default settings.
39 - Merge general default settings/aliases with project-specific ones.
41 # Sample config used for tests below...
42 >>> from io import StringIO
43 >>> sample_config = '''
58 # Check to make sure that bogus project gets general alias.
59 >>> config = _ProjectConfigParser("zzz")
60 >>> config.readfp(StringIO(sample_config))
61 >>> str(config.get("alias", "enemies"))
64 # Check to make sure that alias gets overridden by project.
65 >>> config = _ProjectConfigParser("sm")
66 >>> config.readfp(StringIO(sample_config))
67 >>> str(config.get("alias", "enemies"))
70 # Check to make sure that settings get merged with project.
71 >>> config = _ProjectConfigParser("linux")
72 >>> config.readfp(StringIO(sample_config))
73 >>> sorted((str(a), str(b)) for (a, b) in config.items("settings"))
74 [('am_hero', 'True'), ('process_tags', 'False')]
76 # Check to make sure that settings works with unknown project.
77 >>> config = _ProjectConfigParser("unknown")
78 >>> config.readfp(StringIO(sample_config))
79 >>> sorted((str(a), str(b)) for (a, b) in config.items("settings"))
82 def __init__(self, project_name):
83 """Construct _ProjectConfigParser.
85 In addition to standard SafeConfigParser initialization, this also loads
89 project_name: The name of the project.
91 self._project_name = project_name
92 ConfigParser.SafeConfigParser.__init__(self)
94 # Update the project settings in the config based on
95 # the _default_settings global.
96 project_settings = "%s_settings" % project_name
97 if not self.has_section(project_settings):
98 self.add_section(project_settings)
99 project_defaults = _default_settings.get(project_name, {})
100 for setting_name, setting_value in project_defaults.items():
101 self.set(project_settings, setting_name, setting_value)
103 def get(self, section, option, *args, **kwargs):
104 """Extend SafeConfigParser to try project_section before section.
107 See SafeConfigParser.
109 See SafeConfigParser.
112 val = ConfigParser.SafeConfigParser.get(
113 self, "%s_%s" % (self._project_name, section), option,
116 except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
117 val = ConfigParser.SafeConfigParser.get(
118 self, section, option, *args, **kwargs
122 def items(self, section, *args, **kwargs):
123 """Extend SafeConfigParser to add project_section to section.
126 See SafeConfigParser.
128 See SafeConfigParser.
131 has_project_section = False
134 # Get items from the project section
136 project_items = ConfigParser.SafeConfigParser.items(
137 self, "%s_%s" % (self._project_name, section), *args, **kwargs
139 has_project_section = True
140 except ConfigParser.NoSectionError:
143 # Get top-level items
145 top_items = ConfigParser.SafeConfigParser.items(
146 self, section, *args, **kwargs
148 except ConfigParser.NoSectionError:
149 # If neither section exists raise the error on...
150 if not has_project_section:
153 item_dict = dict(top_items)
154 item_dict.update(project_items)
155 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:
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('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
192 def CreatePatmanConfigFile(gitutil, config_fname):
193 """Creates a config file under $(HOME)/.patman if it can't find one.
196 config_fname: Default config filename i.e., $(HOME)/.patman
201 name = gitutil.GetDefaultUserName()
203 name = raw_input("Enter name: ")
205 email = gitutil.GetDefaultUserEmail()
208 email = raw_input("Enter email: ")
211 f = open(config_fname, 'w')
213 print("Couldn't create patman config file\n")
221 ''' % (name, email), file=f)
224 def _UpdateDefaults(main_parser, config):
225 """Update the given OptionParser defaults based on config.
227 We'll walk through all of the settings from all parsers.
228 For each setting we'll look for a default in the option parser.
229 If it's found we'll update the option parser default.
231 The idea here is that the .patman file should be able to update
232 defaults but that command line flags should still have the final
236 parser: An instance of an ArgumentParser whose defaults will be
238 config: An instance of _ProjectConfigParser that we will query
241 # Find all the parsers and subparsers
242 parsers = [main_parser]
243 parsers += [subparser for action in main_parser._actions
244 if isinstance(action, argparse._SubParsersAction)
245 for _, subparser in action.choices.items()]
247 # Collect the defaults from each parser
249 for parser in parsers:
250 pdefs = parser.parse_known_args()[0]
251 defaults.update(vars(pdefs))
253 # Go through the settings and collect defaults
254 for name, val in config.items('settings'):
256 default_val = defaults[name]
257 if isinstance(default_val, bool):
258 val = config.getboolean('settings', name)
259 elif isinstance(default_val, int):
260 val = config.getint('settings', name)
261 elif isinstance(default_val, str):
262 val = config.get('settings', name)
265 print("WARNING: Unknown setting %s" % name)
267 # Set all the defaults (this propagates through all subparsers)
268 main_parser.set_defaults(**defaults)
270 def _ReadAliasFile(fname):
271 """Read in the U-Boot git alias file if it exists.
274 fname: Filename to read.
276 if os.path.exists(fname):
278 with open(fname, encoding='utf-8') as fd:
283 if not line or line.startswith('#'):
285 words = line.split(None, 2)
286 if len(words) < 3 or words[0] != 'alias':
288 bad_line = "%s:%d:Invalid line '%s'" % (fname, linenum,
291 alias[words[1]] = [s.strip() for s in words[2].split(',')]
295 def _ReadBouncesFile(fname):
296 """Read in the bounces file if it exists
299 fname: Filename to read.
301 if os.path.exists(fname):
302 with open(fname) as fd:
304 if line.startswith('#'):
306 bounces.add(line.strip())
308 def GetItems(config, section):
309 """Get the items from a section of the config.
312 config: _ProjectConfigParser object containing settings
313 section: name of section to retrieve
316 List of (name, value) tuples for the section
319 return config.items(section)
320 except ConfigParser.NoSectionError as e:
325 def Setup(gitutil, parser, project_name, config_fname=''):
326 """Set up the settings module by reading config files.
329 parser: The parser to update
330 project_name: Name of project that we're working on; we'll look
331 for sections named "project_section" as well.
332 config_fname: Config filename to read ('' for default)
334 # First read the git alias file if available
335 _ReadAliasFile('doc/git-mailrc')
336 config = _ProjectConfigParser(project_name)
337 if config_fname == '':
338 config_fname = '%s/.patman' % os.getenv('HOME')
340 if not os.path.exists(config_fname):
341 print("No config file found ~/.patman\nCreating one...\n")
342 CreatePatmanConfigFile(gitutil, config_fname)
344 config.read(config_fname)
346 for name, value in GetItems(config, 'alias'):
347 alias[name] = value.split(',')
349 _ReadBouncesFile('doc/bounces')
350 for name, value in GetItems(config, 'bounces'):
353 _UpdateDefaults(parser, config)
355 # These are the aliases we understand, indexed by alias. Each member is a list.
359 if __name__ == "__main__":