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",
29 class _ProjectConfigParser(ConfigParser.SafeConfigParser):
30 """ConfigParser that handles projects.
32 There are two main goals of this class:
33 - Load project-specific default settings.
34 - Merge general default settings/aliases with project-specific ones.
36 # Sample config used for tests below...
37 >>> from io import StringIO
38 >>> sample_config = '''
40 ... me: Peter P. <likesspiders@example.com>
41 ... enemies: Evil <evil@example.com>
44 ... enemies: Green G. <ugly@example.com>
47 ... enemies: Doc O. <pus@example.com>
53 # Check to make sure that bogus project gets general alias.
54 >>> config = _ProjectConfigParser("zzz")
55 >>> config.readfp(StringIO(sample_config))
56 >>> str(config.get("alias", "enemies"))
57 'Evil <evil@example.com>'
59 # Check to make sure that alias gets overridden by project.
60 >>> config = _ProjectConfigParser("sm")
61 >>> config.readfp(StringIO(sample_config))
62 >>> str(config.get("alias", "enemies"))
63 'Green G. <ugly@example.com>'
65 # Check to make sure that settings get merged with project.
66 >>> config = _ProjectConfigParser("linux")
67 >>> config.readfp(StringIO(sample_config))
68 >>> sorted((str(a), str(b)) for (a, b) in config.items("settings"))
69 [('am_hero', 'True'), ('process_tags', 'False')]
71 # Check to make sure that settings works with unknown project.
72 >>> config = _ProjectConfigParser("unknown")
73 >>> config.readfp(StringIO(sample_config))
74 >>> sorted((str(a), str(b)) for (a, b) in config.items("settings"))
77 def __init__(self, project_name):
78 """Construct _ProjectConfigParser.
80 In addition to standard SafeConfigParser initialization, this also loads
84 project_name: The name of the project.
86 self._project_name = project_name
87 ConfigParser.SafeConfigParser.__init__(self)
89 # Update the project settings in the config based on
90 # the _default_settings global.
91 project_settings = "%s_settings" % project_name
92 if not self.has_section(project_settings):
93 self.add_section(project_settings)
94 project_defaults = _default_settings.get(project_name, {})
95 for setting_name, setting_value in project_defaults.items():
96 self.set(project_settings, setting_name, setting_value)
98 def get(self, section, option, *args, **kwargs):
99 """Extend SafeConfigParser to try project_section before section.
102 See SafeConfigParser.
104 See SafeConfigParser.
107 val = ConfigParser.SafeConfigParser.get(
108 self, "%s_%s" % (self._project_name, section), option,
111 except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
112 val = ConfigParser.SafeConfigParser.get(
113 self, section, option, *args, **kwargs
117 def items(self, section, *args, **kwargs):
118 """Extend SafeConfigParser to add project_section to section.
121 See SafeConfigParser.
123 See SafeConfigParser.
126 has_project_section = False
129 # Get items from the project section
131 project_items = ConfigParser.SafeConfigParser.items(
132 self, "%s_%s" % (self._project_name, section), *args, **kwargs
134 has_project_section = True
135 except ConfigParser.NoSectionError:
138 # Get top-level items
140 top_items = ConfigParser.SafeConfigParser.items(
141 self, section, *args, **kwargs
143 except ConfigParser.NoSectionError:
144 # If neither section exists raise the error on...
145 if not has_project_section:
148 item_dict = dict(top_items)
149 item_dict.update(project_items)
150 return {(item, val) for item, val in item_dict.items()}
152 def ReadGitAliases(fname):
153 """Read a git alias file. This is in the form used by git:
155 alias uboot u-boot@lists.denx.de
156 alias wd Wolfgang Denk <wd@denx.de>
159 fname: Filename to read
162 fd = open(fname, 'r', encoding='utf-8')
164 print("Warning: Cannot find alias file '%s'" % fname)
167 re_line = re.compile('alias\s+(\S+)\s+(.*)')
168 for line in fd.readlines():
170 if not line or line[0] == '#':
173 m = re_line.match(line)
175 print("Warning: Alias file line '%s' not understood" % line)
178 list = alias.get(m.group(1), [])
179 for item in m.group(2).split(','):
183 alias[m.group(1)] = list
187 def CreatePatmanConfigFile(gitutil, config_fname):
188 """Creates a config file under $(HOME)/.patman if it can't find one.
191 config_fname: Default config filename i.e., $(HOME)/.patman
196 name = gitutil.GetDefaultUserName()
198 name = raw_input("Enter name: ")
200 email = gitutil.GetDefaultUserEmail()
203 email = raw_input("Enter email: ")
206 f = open(config_fname, 'w')
208 print("Couldn't create patman config file\n")
215 nxp = Zhikang Zhang <zhikang.zhang@nxp.com>
216 ''' % (name, email), file=f)
219 def _UpdateDefaults(main_parser, config):
220 """Update the given OptionParser defaults based on config.
222 We'll walk through all of the settings from all parsers.
223 For each setting we'll look for a default in the option parser.
224 If it's found we'll update the option parser default.
226 The idea here is that the .patman file should be able to update
227 defaults but that command line flags should still have the final
231 parser: An instance of an ArgumentParser whose defaults will be
233 config: An instance of _ProjectConfigParser that we will query
236 # Find all the parsers and subparsers
237 parsers = [main_parser]
238 parsers += [subparser for action in main_parser._actions
239 if isinstance(action, argparse._SubParsersAction)
240 for _, subparser in action.choices.items()]
242 # Collect the defaults from each parser
244 for parser in parsers:
245 pdefs = parser.parse_known_args()[0]
246 defaults.update(vars(pdefs))
248 # Go through the settings and collect defaults
249 for name, val in config.items('settings'):
251 default_val = defaults[name]
252 if isinstance(default_val, bool):
253 val = config.getboolean('settings', name)
254 elif isinstance(default_val, int):
255 val = config.getint('settings', name)
256 elif isinstance(default_val, str):
257 val = config.get('settings', name)
260 print("WARNING: Unknown setting %s" % name)
262 # Set all the defaults (this propagates through all subparsers)
263 main_parser.set_defaults(**defaults)
265 def _ReadAliasFile(fname):
266 """Read in the U-Boot git alias file if it exists.
269 fname: Filename to read.
271 if os.path.exists(fname):
273 with open(fname, encoding='utf-8') as fd:
278 if not line or line.startswith('#'):
280 words = line.split(None, 2)
281 if len(words) < 3 or words[0] != 'alias':
283 bad_line = "%s:%d:Invalid line '%s'" % (fname, linenum,
286 alias[words[1]] = [s.strip() for s in words[2].split(',')]
290 def _ReadBouncesFile(fname):
291 """Read in the bounces file if it exists
294 fname: Filename to read.
296 if os.path.exists(fname):
297 with open(fname) as fd:
299 if line.startswith('#'):
301 bounces.add(line.strip())
303 def GetItems(config, section):
304 """Get the items from a section of the config.
307 config: _ProjectConfigParser object containing settings
308 section: name of section to retrieve
311 List of (name, value) tuples for the section
314 return config.items(section)
315 except ConfigParser.NoSectionError as e:
320 def Setup(gitutil, parser, project_name, config_fname=''):
321 """Set up the settings module by reading config files.
324 parser: The parser to update
325 project_name: Name of project that we're working on; we'll look
326 for sections named "project_section" as well.
327 config_fname: Config filename to read ('' for default)
329 # First read the git alias file if available
330 _ReadAliasFile('doc/git-mailrc')
331 config = _ProjectConfigParser(project_name)
332 if config_fname == '':
333 config_fname = '%s/.patman' % os.getenv('HOME')
335 if not os.path.exists(config_fname):
336 print("No config file found ~/.patman\nCreating one...\n")
337 CreatePatmanConfigFile(gitutil, config_fname)
339 config.read(config_fname)
341 for name, value in GetItems(config, 'alias'):
342 alias[name] = value.split(',')
344 _ReadBouncesFile('doc/bounces')
345 for name, value in GetItems(config, 'bounces'):
348 _UpdateDefaults(parser, config)
350 # These are the aliases we understand, indexed by alias. Each member is a list.
354 if __name__ == "__main__":