blob: 5dc83a850023c779d6ce76ce96aaea9df3056646 [file] [log] [blame]
Tom Rini10e47792018-05-06 17:58:06 -04001# SPDX-License-Identifier: GPL-2.0+
Simon Glass26132882012-01-14 15:12:45 +00002# Copyright (c) 2011 The Chromium OS Authors.
3#
Simon Glass26132882012-01-14 15:12:45 +00004
Paul Burtonc3931342016-09-27 16:03:50 +01005from __future__ import print_function
6
Paul Burton352f1102016-09-27 16:03:52 +01007try:
8 import configparser as ConfigParser
9except:
10 import ConfigParser
11
Simon Glass26132882012-01-14 15:12:45 +000012import os
13import re
14
15import command
Vikram Narayanan12fb29a2012-05-23 09:01:06 +000016import gitutil
Simon Glass8bb7a7a2019-05-14 15:53:50 -060017import tools
Simon Glass26132882012-01-14 15:12:45 +000018
Doug Anderson31ffd7f2012-12-03 14:43:18 +000019"""Default settings per-project.
20
21These are used by _ProjectConfigParser. Settings names should match
22the "dest" of the option parser from patman.py.
23"""
24_default_settings = {
25 "u-boot": {},
26 "linux": {
27 "process_tags": "False",
28 }
29}
30
31class _ProjectConfigParser(ConfigParser.SafeConfigParser):
32 """ConfigParser that handles projects.
33
34 There are two main goals of this class:
35 - Load project-specific default settings.
36 - Merge general default settings/aliases with project-specific ones.
37
38 # Sample config used for tests below...
Paul Burton44fc9ba2016-09-27 16:03:55 +010039 >>> try:
40 ... from StringIO import StringIO
41 ... except ImportError:
42 ... from io import StringIO
Doug Anderson31ffd7f2012-12-03 14:43:18 +000043 >>> sample_config = '''
44 ... [alias]
45 ... me: Peter P. <likesspiders@example.com>
46 ... enemies: Evil <evil@example.com>
47 ...
48 ... [sm_alias]
49 ... enemies: Green G. <ugly@example.com>
50 ...
51 ... [sm2_alias]
52 ... enemies: Doc O. <pus@example.com>
53 ...
54 ... [settings]
55 ... am_hero: True
56 ... '''
57
58 # Check to make sure that bogus project gets general alias.
59 >>> config = _ProjectConfigParser("zzz")
Paul Burton44fc9ba2016-09-27 16:03:55 +010060 >>> config.readfp(StringIO(sample_config))
Simon Glassaa82db62019-05-14 15:53:52 -060061 >>> str(config.get("alias", "enemies"))
62 'Evil <evil@example.com>'
Doug Anderson31ffd7f2012-12-03 14:43:18 +000063
64 # Check to make sure that alias gets overridden by project.
65 >>> config = _ProjectConfigParser("sm")
Paul Burton44fc9ba2016-09-27 16:03:55 +010066 >>> config.readfp(StringIO(sample_config))
Simon Glassaa82db62019-05-14 15:53:52 -060067 >>> str(config.get("alias", "enemies"))
68 'Green G. <ugly@example.com>'
Doug Anderson31ffd7f2012-12-03 14:43:18 +000069
70 # Check to make sure that settings get merged with project.
71 >>> config = _ProjectConfigParser("linux")
Paul Burton44fc9ba2016-09-27 16:03:55 +010072 >>> config.readfp(StringIO(sample_config))
Simon Glassaa82db62019-05-14 15:53:52 -060073 >>> sorted((str(a), str(b)) for (a, b) in config.items("settings"))
74 [('am_hero', 'True'), ('process_tags', 'False')]
Doug Anderson31ffd7f2012-12-03 14:43:18 +000075
76 # Check to make sure that settings works with unknown project.
77 >>> config = _ProjectConfigParser("unknown")
Paul Burton44fc9ba2016-09-27 16:03:55 +010078 >>> config.readfp(StringIO(sample_config))
Simon Glassaa82db62019-05-14 15:53:52 -060079 >>> sorted((str(a), str(b)) for (a, b) in config.items("settings"))
80 [('am_hero', 'True')]
Doug Anderson31ffd7f2012-12-03 14:43:18 +000081 """
82 def __init__(self, project_name):
83 """Construct _ProjectConfigParser.
84
85 In addition to standard SafeConfigParser initialization, this also loads
86 project defaults.
87
88 Args:
89 project_name: The name of the project.
90 """
91 self._project_name = project_name
92 ConfigParser.SafeConfigParser.__init__(self)
93
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, {})
Paul Burton4466f1a2016-09-27 16:03:54 +0100100 for setting_name, setting_value in project_defaults.items():
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000101 self.set(project_settings, setting_name, setting_value)
102
103 def get(self, section, option, *args, **kwargs):
104 """Extend SafeConfigParser to try project_section before section.
105
106 Args:
107 See SafeConfigParser.
108 Returns:
109 See SafeConfigParser.
110 """
111 try:
Simon Glass26f8b202018-10-01 21:12:33 -0600112 val = ConfigParser.SafeConfigParser.get(
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000113 self, "%s_%s" % (self._project_name, section), option,
114 *args, **kwargs
115 )
116 except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
Simon Glass26f8b202018-10-01 21:12:33 -0600117 val = ConfigParser.SafeConfigParser.get(
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000118 self, section, option, *args, **kwargs
119 )
Simon Glass8bb7a7a2019-05-14 15:53:50 -0600120 return tools.ToUnicode(val)
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000121
122 def items(self, section, *args, **kwargs):
123 """Extend SafeConfigParser to add project_section to section.
124
125 Args:
126 See SafeConfigParser.
127 Returns:
128 See SafeConfigParser.
129 """
130 project_items = []
131 has_project_section = False
132 top_items = []
133
134 # Get items from the project section
135 try:
136 project_items = ConfigParser.SafeConfigParser.items(
137 self, "%s_%s" % (self._project_name, section), *args, **kwargs
138 )
139 has_project_section = True
140 except ConfigParser.NoSectionError:
141 pass
142
143 # Get top-level items
144 try:
145 top_items = ConfigParser.SafeConfigParser.items(
146 self, section, *args, **kwargs
147 )
148 except ConfigParser.NoSectionError:
149 # If neither section exists raise the error on...
150 if not has_project_section:
151 raise
152
153 item_dict = dict(top_items)
154 item_dict.update(project_items)
Simon Glass8bb7a7a2019-05-14 15:53:50 -0600155 return {(tools.ToUnicode(item), tools.ToUnicode(val))
Simon Glass10017372019-05-14 15:53:40 -0600156 for item, val in item_dict.items()}
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000157
Simon Glass26132882012-01-14 15:12:45 +0000158def ReadGitAliases(fname):
159 """Read a git alias file. This is in the form used by git:
160
161 alias uboot u-boot@lists.denx.de
162 alias wd Wolfgang Denk <wd@denx.de>
163
164 Args:
165 fname: Filename to read
166 """
167 try:
Simon Glassf544a2d2019-10-31 07:42:51 -0600168 fd = open(fname, 'r', encoding='utf-8')
Simon Glass26132882012-01-14 15:12:45 +0000169 except IOError:
Paul Burtonc3931342016-09-27 16:03:50 +0100170 print("Warning: Cannot find alias file '%s'" % fname)
Simon Glass26132882012-01-14 15:12:45 +0000171 return
172
173 re_line = re.compile('alias\s+(\S+)\s+(.*)')
174 for line in fd.readlines():
175 line = line.strip()
176 if not line or line[0] == '#':
177 continue
178
179 m = re_line.match(line)
180 if not m:
Paul Burtonc3931342016-09-27 16:03:50 +0100181 print("Warning: Alias file line '%s' not understood" % line)
Simon Glass26132882012-01-14 15:12:45 +0000182 continue
183
184 list = alias.get(m.group(1), [])
185 for item in m.group(2).split(','):
186 item = item.strip()
187 if item:
188 list.append(item)
189 alias[m.group(1)] = list
190
191 fd.close()
192
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000193def CreatePatmanConfigFile(config_fname):
194 """Creates a config file under $(HOME)/.patman if it can't find one.
195
196 Args:
197 config_fname: Default config filename i.e., $(HOME)/.patman
198
199 Returns:
200 None
201 """
202 name = gitutil.GetDefaultUserName()
203 if name == None:
204 name = raw_input("Enter name: ")
205
206 email = gitutil.GetDefaultUserEmail()
207
208 if email == None:
209 email = raw_input("Enter email: ")
210
211 try:
212 f = open(config_fname, 'w')
213 except IOError:
Paul Burtonc3931342016-09-27 16:03:50 +0100214 print("Couldn't create patman config file\n")
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000215 raise
216
Simon Glass52a08642017-09-12 20:30:28 -0600217 print('''[alias]
218me: %s <%s>
219
220[bounces]
221nxp = Zhikang Zhang <zhikang.zhang@nxp.com>
222''' % (name, email), file=f)
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000223 f.close();
224
Doug Anderson3d3077c2012-12-03 14:43:17 +0000225def _UpdateDefaults(parser, config):
226 """Update the given OptionParser defaults based on config.
227
228 We'll walk through all of the settings from the parser
229 For each setting we'll look for a default in the option parser.
230 If it's found we'll update the option parser default.
231
232 The idea here is that the .patman file should be able to update
233 defaults but that command line flags should still have the final
234 say.
235
236 Args:
237 parser: An instance of an OptionParser whose defaults will be
238 updated.
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000239 config: An instance of _ProjectConfigParser that we will query
Doug Anderson3d3077c2012-12-03 14:43:17 +0000240 for settings.
241 """
242 defaults = parser.get_default_values()
243 for name, val in config.items('settings'):
244 if hasattr(defaults, name):
245 default_val = getattr(defaults, name)
246 if isinstance(default_val, bool):
247 val = config.getboolean('settings', name)
248 elif isinstance(default_val, int):
249 val = config.getint('settings', name)
250 parser.set_default(name, val)
251 else:
Paul Burtonc3931342016-09-27 16:03:50 +0100252 print("WARNING: Unknown setting %s" % name)
Doug Anderson3d3077c2012-12-03 14:43:17 +0000253
Simon Glass33382692015-01-29 11:35:17 -0700254def _ReadAliasFile(fname):
255 """Read in the U-Boot git alias file if it exists.
256
257 Args:
258 fname: Filename to read.
259 """
260 if os.path.exists(fname):
261 bad_line = None
Simon Glassf544a2d2019-10-31 07:42:51 -0600262 with open(fname, encoding='utf-8') as fd:
Simon Glass33382692015-01-29 11:35:17 -0700263 linenum = 0
264 for line in fd:
265 linenum += 1
266 line = line.strip()
267 if not line or line.startswith('#'):
268 continue
Adam Sampson02651322018-06-27 14:38:58 +0100269 words = line.split(None, 2)
Simon Glass33382692015-01-29 11:35:17 -0700270 if len(words) < 3 or words[0] != 'alias':
271 if not bad_line:
272 bad_line = "%s:%d:Invalid line '%s'" % (fname, linenum,
273 line)
274 continue
275 alias[words[1]] = [s.strip() for s in words[2].split(',')]
276 if bad_line:
Paul Burtonc3931342016-09-27 16:03:50 +0100277 print(bad_line)
Simon Glass33382692015-01-29 11:35:17 -0700278
Chris Packhame8d2a122017-09-01 20:57:53 +1200279def _ReadBouncesFile(fname):
280 """Read in the bounces file if it exists
281
282 Args:
283 fname: Filename to read.
284 """
285 if os.path.exists(fname):
286 with open(fname) as fd:
287 for line in fd:
288 if line.startswith('#'):
289 continue
290 bounces.add(line.strip())
291
Simon Glass52a08642017-09-12 20:30:28 -0600292def GetItems(config, section):
293 """Get the items from a section of the config.
294
295 Args:
296 config: _ProjectConfigParser object containing settings
297 section: name of section to retrieve
298
299 Returns:
300 List of (name, value) tuples for the section
301 """
302 try:
303 return config.items(section)
304 except ConfigParser.NoSectionError as e:
305 return []
306 except:
307 raise
308
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000309def Setup(parser, project_name, config_fname=''):
Simon Glass26132882012-01-14 15:12:45 +0000310 """Set up the settings module by reading config files.
311
312 Args:
Doug Anderson3d3077c2012-12-03 14:43:17 +0000313 parser: The parser to update
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000314 project_name: Name of project that we're working on; we'll look
315 for sections named "project_section" as well.
Simon Glass26132882012-01-14 15:12:45 +0000316 config_fname: Config filename to read ('' for default)
317 """
Simon Glass33382692015-01-29 11:35:17 -0700318 # First read the git alias file if available
319 _ReadAliasFile('doc/git-mailrc')
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000320 config = _ProjectConfigParser(project_name)
Simon Glass26132882012-01-14 15:12:45 +0000321 if config_fname == '':
Vikram Narayananc387d36d2012-05-23 08:58:58 +0000322 config_fname = '%s/.patman' % os.getenv('HOME')
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000323
324 if not os.path.exists(config_fname):
Paul Burtonc3931342016-09-27 16:03:50 +0100325 print("No config file found ~/.patman\nCreating one...\n")
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000326 CreatePatmanConfigFile(config_fname)
327
Doug Anderson3d3077c2012-12-03 14:43:17 +0000328 config.read(config_fname)
Simon Glass26132882012-01-14 15:12:45 +0000329
Simon Glass52a08642017-09-12 20:30:28 -0600330 for name, value in GetItems(config, 'alias'):
Simon Glass26132882012-01-14 15:12:45 +0000331 alias[name] = value.split(',')
332
Chris Packhame8d2a122017-09-01 20:57:53 +1200333 _ReadBouncesFile('doc/bounces')
Simon Glass52a08642017-09-12 20:30:28 -0600334 for name, value in GetItems(config, 'bounces'):
Chris Packhame8d2a122017-09-01 20:57:53 +1200335 bounces.add(value)
336
Doug Anderson3d3077c2012-12-03 14:43:17 +0000337 _UpdateDefaults(parser, config)
Simon Glass26132882012-01-14 15:12:45 +0000338
339# These are the aliases we understand, indexed by alias. Each member is a list.
340alias = {}
Chris Packhame8d2a122017-09-01 20:57:53 +1200341bounces = set()
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000342
343if __name__ == "__main__":
344 import doctest
345
346 doctest.testmod()