blob: ea2bc74f759b2aa3a9d931a4444393a33546da5e [file] [log] [blame]
Tom Rini83d290c2018-05-06 17:58:06 -04001# SPDX-License-Identifier: GPL-2.0+
Simon Glass0d24de92012-01-14 15:12:45 +00002# Copyright (c) 2011 The Chromium OS Authors.
3#
Simon Glass0d24de92012-01-14 15:12:45 +00004
Paul Burtona920a172016-09-27 16:03:50 +01005from __future__ import print_function
6
Paul Burton2ce7b212016-09-27 16:03:52 +01007try:
8 import configparser as ConfigParser
9except:
10 import ConfigParser
11
Simon Glass0d24de92012-01-14 15:12:45 +000012import os
13import re
14
15import command
Vikram Narayanan87d65552012-05-23 09:01:06 +000016import gitutil
Simon Glass0d24de92012-01-14 15:12:45 +000017
Doug Andersona1dcee82012-12-03 14:43:18 +000018"""Default settings per-project.
19
20These are used by _ProjectConfigParser. Settings names should match
21the "dest" of the option parser from patman.py.
22"""
23_default_settings = {
24 "u-boot": {},
25 "linux": {
26 "process_tags": "False",
27 }
28}
29
30class _ProjectConfigParser(ConfigParser.SafeConfigParser):
31 """ConfigParser that handles projects.
32
33 There are two main goals of this class:
34 - Load project-specific default settings.
35 - Merge general default settings/aliases with project-specific ones.
36
37 # Sample config used for tests below...
Paul Burtonf5d44b92016-09-27 16:03:55 +010038 >>> try:
39 ... from StringIO import StringIO
40 ... except ImportError:
41 ... from io import StringIO
Doug Andersona1dcee82012-12-03 14:43:18 +000042 >>> sample_config = '''
43 ... [alias]
44 ... me: Peter P. <likesspiders@example.com>
45 ... enemies: Evil <evil@example.com>
46 ...
47 ... [sm_alias]
48 ... enemies: Green G. <ugly@example.com>
49 ...
50 ... [sm2_alias]
51 ... enemies: Doc O. <pus@example.com>
52 ...
53 ... [settings]
54 ... am_hero: True
55 ... '''
56
57 # Check to make sure that bogus project gets general alias.
58 >>> config = _ProjectConfigParser("zzz")
Paul Burtonf5d44b92016-09-27 16:03:55 +010059 >>> config.readfp(StringIO(sample_config))
Doug Andersona1dcee82012-12-03 14:43:18 +000060 >>> config.get("alias", "enemies")
Simon Glassec9e0f42018-10-01 21:12:33 -060061 u'Evil <evil@example.com>'
Doug Andersona1dcee82012-12-03 14:43:18 +000062
63 # Check to make sure that alias gets overridden by project.
64 >>> config = _ProjectConfigParser("sm")
Paul Burtonf5d44b92016-09-27 16:03:55 +010065 >>> config.readfp(StringIO(sample_config))
Doug Andersona1dcee82012-12-03 14:43:18 +000066 >>> config.get("alias", "enemies")
Simon Glassec9e0f42018-10-01 21:12:33 -060067 u'Green G. <ugly@example.com>'
Doug Andersona1dcee82012-12-03 14:43:18 +000068
69 # Check to make sure that settings get merged with project.
70 >>> config = _ProjectConfigParser("linux")
Paul Burtonf5d44b92016-09-27 16:03:55 +010071 >>> config.readfp(StringIO(sample_config))
Doug Andersona1dcee82012-12-03 14:43:18 +000072 >>> sorted(config.items("settings"))
Simon Glassec9e0f42018-10-01 21:12:33 -060073 [(u'am_hero', u'True'), (u'process_tags', u'False')]
Doug Andersona1dcee82012-12-03 14:43:18 +000074
75 # Check to make sure that settings works with unknown project.
76 >>> config = _ProjectConfigParser("unknown")
Paul Burtonf5d44b92016-09-27 16:03:55 +010077 >>> config.readfp(StringIO(sample_config))
Doug Andersona1dcee82012-12-03 14:43:18 +000078 >>> sorted(config.items("settings"))
Simon Glassec9e0f42018-10-01 21:12:33 -060079 [(u'am_hero', u'True')]
Doug Andersona1dcee82012-12-03 14:43:18 +000080 """
81 def __init__(self, project_name):
82 """Construct _ProjectConfigParser.
83
84 In addition to standard SafeConfigParser initialization, this also loads
85 project defaults.
86
87 Args:
88 project_name: The name of the project.
89 """
90 self._project_name = project_name
91 ConfigParser.SafeConfigParser.__init__(self)
92
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, {})
Paul Burtonc9eac382016-09-27 16:03:54 +010099 for setting_name, setting_value in project_defaults.items():
Doug Andersona1dcee82012-12-03 14:43:18 +0000100 self.set(project_settings, setting_name, setting_value)
101
Simon Glassec9e0f42018-10-01 21:12:33 -0600102 def _to_unicode(self, val):
103 """Make sure a value is of type 'unicode'
104
105 Args:
106 val: string or unicode object
107
108 Returns:
109 unicode version of val
110 """
111 return val if isinstance(val, unicode) else val.decode('utf-8')
112
Doug Andersona1dcee82012-12-03 14:43:18 +0000113 def get(self, section, option, *args, **kwargs):
114 """Extend SafeConfigParser to try project_section before section.
115
116 Args:
117 See SafeConfigParser.
118 Returns:
119 See SafeConfigParser.
120 """
121 try:
Simon Glassec9e0f42018-10-01 21:12:33 -0600122 val = ConfigParser.SafeConfigParser.get(
Doug Andersona1dcee82012-12-03 14:43:18 +0000123 self, "%s_%s" % (self._project_name, section), option,
124 *args, **kwargs
125 )
126 except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
Simon Glassec9e0f42018-10-01 21:12:33 -0600127 val = ConfigParser.SafeConfigParser.get(
Doug Andersona1dcee82012-12-03 14:43:18 +0000128 self, section, option, *args, **kwargs
129 )
Simon Glassec9e0f42018-10-01 21:12:33 -0600130 return self._to_unicode(val)
Doug Andersona1dcee82012-12-03 14:43:18 +0000131
132 def items(self, section, *args, **kwargs):
133 """Extend SafeConfigParser to add project_section to section.
134
135 Args:
136 See SafeConfigParser.
137 Returns:
138 See SafeConfigParser.
139 """
140 project_items = []
141 has_project_section = False
142 top_items = []
143
144 # Get items from the project section
145 try:
146 project_items = ConfigParser.SafeConfigParser.items(
147 self, "%s_%s" % (self._project_name, section), *args, **kwargs
148 )
149 has_project_section = True
150 except ConfigParser.NoSectionError:
151 pass
152
153 # Get top-level items
154 try:
155 top_items = ConfigParser.SafeConfigParser.items(
156 self, section, *args, **kwargs
157 )
158 except ConfigParser.NoSectionError:
159 # If neither section exists raise the error on...
160 if not has_project_section:
161 raise
162
163 item_dict = dict(top_items)
164 item_dict.update(project_items)
Simon Glassec9e0f42018-10-01 21:12:33 -0600165 return {(self._to_unicode(item), self._to_unicode(val))
166 for item, val in item_dict.iteritems()}
Doug Andersona1dcee82012-12-03 14:43:18 +0000167
Simon Glass0d24de92012-01-14 15:12:45 +0000168def ReadGitAliases(fname):
169 """Read a git alias file. This is in the form used by git:
170
171 alias uboot u-boot@lists.denx.de
172 alias wd Wolfgang Denk <wd@denx.de>
173
174 Args:
175 fname: Filename to read
176 """
177 try:
178 fd = open(fname, 'r')
179 except IOError:
Paul Burtona920a172016-09-27 16:03:50 +0100180 print("Warning: Cannot find alias file '%s'" % fname)
Simon Glass0d24de92012-01-14 15:12:45 +0000181 return
182
183 re_line = re.compile('alias\s+(\S+)\s+(.*)')
184 for line in fd.readlines():
185 line = line.strip()
186 if not line or line[0] == '#':
187 continue
188
189 m = re_line.match(line)
190 if not m:
Paul Burtona920a172016-09-27 16:03:50 +0100191 print("Warning: Alias file line '%s' not understood" % line)
Simon Glass0d24de92012-01-14 15:12:45 +0000192 continue
193
194 list = alias.get(m.group(1), [])
195 for item in m.group(2).split(','):
196 item = item.strip()
197 if item:
198 list.append(item)
199 alias[m.group(1)] = list
200
201 fd.close()
202
Vikram Narayanan87d65552012-05-23 09:01:06 +0000203def CreatePatmanConfigFile(config_fname):
204 """Creates a config file under $(HOME)/.patman if it can't find one.
205
206 Args:
207 config_fname: Default config filename i.e., $(HOME)/.patman
208
209 Returns:
210 None
211 """
212 name = gitutil.GetDefaultUserName()
213 if name == None:
214 name = raw_input("Enter name: ")
215
216 email = gitutil.GetDefaultUserEmail()
217
218 if email == None:
219 email = raw_input("Enter email: ")
220
221 try:
222 f = open(config_fname, 'w')
223 except IOError:
Paul Burtona920a172016-09-27 16:03:50 +0100224 print("Couldn't create patman config file\n")
Vikram Narayanan87d65552012-05-23 09:01:06 +0000225 raise
226
Simon Glassad893142017-09-12 20:30:28 -0600227 print('''[alias]
228me: %s <%s>
229
230[bounces]
231nxp = Zhikang Zhang <zhikang.zhang@nxp.com>
232''' % (name, email), file=f)
Vikram Narayanan87d65552012-05-23 09:01:06 +0000233 f.close();
234
Doug Anderson8568bae2012-12-03 14:43:17 +0000235def _UpdateDefaults(parser, config):
236 """Update the given OptionParser defaults based on config.
237
238 We'll walk through all of the settings from the parser
239 For each setting we'll look for a default in the option parser.
240 If it's found we'll update the option parser default.
241
242 The idea here is that the .patman file should be able to update
243 defaults but that command line flags should still have the final
244 say.
245
246 Args:
247 parser: An instance of an OptionParser whose defaults will be
248 updated.
Doug Andersona1dcee82012-12-03 14:43:18 +0000249 config: An instance of _ProjectConfigParser that we will query
Doug Anderson8568bae2012-12-03 14:43:17 +0000250 for settings.
251 """
252 defaults = parser.get_default_values()
253 for name, val in config.items('settings'):
254 if hasattr(defaults, name):
255 default_val = getattr(defaults, name)
256 if isinstance(default_val, bool):
257 val = config.getboolean('settings', name)
258 elif isinstance(default_val, int):
259 val = config.getint('settings', name)
260 parser.set_default(name, val)
261 else:
Paul Burtona920a172016-09-27 16:03:50 +0100262 print("WARNING: Unknown setting %s" % name)
Doug Anderson8568bae2012-12-03 14:43:17 +0000263
Simon Glass8895b3e2015-01-29 11:35:17 -0700264def _ReadAliasFile(fname):
265 """Read in the U-Boot git alias file if it exists.
266
267 Args:
268 fname: Filename to read.
269 """
270 if os.path.exists(fname):
271 bad_line = None
272 with open(fname) as fd:
273 linenum = 0
274 for line in fd:
275 linenum += 1
276 line = line.strip()
277 if not line or line.startswith('#'):
278 continue
Adam Sampsonb8a48fb2018-06-27 14:38:58 +0100279 words = line.split(None, 2)
Simon Glass8895b3e2015-01-29 11:35:17 -0700280 if len(words) < 3 or words[0] != 'alias':
281 if not bad_line:
282 bad_line = "%s:%d:Invalid line '%s'" % (fname, linenum,
283 line)
284 continue
285 alias[words[1]] = [s.strip() for s in words[2].split(',')]
286 if bad_line:
Paul Burtona920a172016-09-27 16:03:50 +0100287 print(bad_line)
Simon Glass8895b3e2015-01-29 11:35:17 -0700288
Chris Packhame11aa602017-09-01 20:57:53 +1200289def _ReadBouncesFile(fname):
290 """Read in the bounces file if it exists
291
292 Args:
293 fname: Filename to read.
294 """
295 if os.path.exists(fname):
296 with open(fname) as fd:
297 for line in fd:
298 if line.startswith('#'):
299 continue
300 bounces.add(line.strip())
301
Simon Glassad893142017-09-12 20:30:28 -0600302def GetItems(config, section):
303 """Get the items from a section of the config.
304
305 Args:
306 config: _ProjectConfigParser object containing settings
307 section: name of section to retrieve
308
309 Returns:
310 List of (name, value) tuples for the section
311 """
312 try:
313 return config.items(section)
314 except ConfigParser.NoSectionError as e:
315 return []
316 except:
317 raise
318
Doug Andersona1dcee82012-12-03 14:43:18 +0000319def Setup(parser, project_name, config_fname=''):
Simon Glass0d24de92012-01-14 15:12:45 +0000320 """Set up the settings module by reading config files.
321
322 Args:
Doug Anderson8568bae2012-12-03 14:43:17 +0000323 parser: The parser to update
Doug Andersona1dcee82012-12-03 14:43:18 +0000324 project_name: Name of project that we're working on; we'll look
325 for sections named "project_section" as well.
Simon Glass0d24de92012-01-14 15:12:45 +0000326 config_fname: Config filename to read ('' for default)
327 """
Simon Glass8895b3e2015-01-29 11:35:17 -0700328 # First read the git alias file if available
329 _ReadAliasFile('doc/git-mailrc')
Doug Andersona1dcee82012-12-03 14:43:18 +0000330 config = _ProjectConfigParser(project_name)
Simon Glass0d24de92012-01-14 15:12:45 +0000331 if config_fname == '':
Vikram Narayanan2b36c752012-05-23 08:58:58 +0000332 config_fname = '%s/.patman' % os.getenv('HOME')
Vikram Narayanan87d65552012-05-23 09:01:06 +0000333
334 if not os.path.exists(config_fname):
Paul Burtona920a172016-09-27 16:03:50 +0100335 print("No config file found ~/.patman\nCreating one...\n")
Vikram Narayanan87d65552012-05-23 09:01:06 +0000336 CreatePatmanConfigFile(config_fname)
337
Doug Anderson8568bae2012-12-03 14:43:17 +0000338 config.read(config_fname)
Simon Glass0d24de92012-01-14 15:12:45 +0000339
Simon Glassad893142017-09-12 20:30:28 -0600340 for name, value in GetItems(config, 'alias'):
Simon Glass0d24de92012-01-14 15:12:45 +0000341 alias[name] = value.split(',')
342
Chris Packhame11aa602017-09-01 20:57:53 +1200343 _ReadBouncesFile('doc/bounces')
Simon Glassad893142017-09-12 20:30:28 -0600344 for name, value in GetItems(config, 'bounces'):
Chris Packhame11aa602017-09-01 20:57:53 +1200345 bounces.add(value)
346
Doug Anderson8568bae2012-12-03 14:43:17 +0000347 _UpdateDefaults(parser, config)
Simon Glass0d24de92012-01-14 15:12:45 +0000348
349# These are the aliases we understand, indexed by alias. Each member is a list.
350alias = {}
Chris Packhame11aa602017-09-01 20:57:53 +1200351bounces = set()
Doug Andersona1dcee82012-12-03 14:43:18 +0000352
353if __name__ == "__main__":
354 import doctest
355
356 doctest.testmod()