blob: 94ea5b5a1b0c26993833a187873a9075800a23a7 [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")
61 'Evil <evil@example.com>'
62
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")
67 'Green G. <ugly@example.com>'
68
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"))
73 [('am_hero', 'True'), ('process_tags', 'False')]
74
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"))
79 [('am_hero', 'True')]
80 """
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
102 def get(self, section, option, *args, **kwargs):
103 """Extend SafeConfigParser to try project_section before section.
104
105 Args:
106 See SafeConfigParser.
107 Returns:
108 See SafeConfigParser.
109 """
110 try:
111 return ConfigParser.SafeConfigParser.get(
112 self, "%s_%s" % (self._project_name, section), option,
113 *args, **kwargs
114 )
115 except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
116 return ConfigParser.SafeConfigParser.get(
117 self, section, option, *args, **kwargs
118 )
119
120 def items(self, section, *args, **kwargs):
121 """Extend SafeConfigParser to add project_section to section.
122
123 Args:
124 See SafeConfigParser.
125 Returns:
126 See SafeConfigParser.
127 """
128 project_items = []
129 has_project_section = False
130 top_items = []
131
132 # Get items from the project section
133 try:
134 project_items = ConfigParser.SafeConfigParser.items(
135 self, "%s_%s" % (self._project_name, section), *args, **kwargs
136 )
137 has_project_section = True
138 except ConfigParser.NoSectionError:
139 pass
140
141 # Get top-level items
142 try:
143 top_items = ConfigParser.SafeConfigParser.items(
144 self, section, *args, **kwargs
145 )
146 except ConfigParser.NoSectionError:
147 # If neither section exists raise the error on...
148 if not has_project_section:
149 raise
150
151 item_dict = dict(top_items)
152 item_dict.update(project_items)
153 return item_dict.items()
154
Simon Glass0d24de92012-01-14 15:12:45 +0000155def ReadGitAliases(fname):
156 """Read a git alias file. This is in the form used by git:
157
158 alias uboot u-boot@lists.denx.de
159 alias wd Wolfgang Denk <wd@denx.de>
160
161 Args:
162 fname: Filename to read
163 """
164 try:
165 fd = open(fname, 'r')
166 except IOError:
Paul Burtona920a172016-09-27 16:03:50 +0100167 print("Warning: Cannot find alias file '%s'" % fname)
Simon Glass0d24de92012-01-14 15:12:45 +0000168 return
169
170 re_line = re.compile('alias\s+(\S+)\s+(.*)')
171 for line in fd.readlines():
172 line = line.strip()
173 if not line or line[0] == '#':
174 continue
175
176 m = re_line.match(line)
177 if not m:
Paul Burtona920a172016-09-27 16:03:50 +0100178 print("Warning: Alias file line '%s' not understood" % line)
Simon Glass0d24de92012-01-14 15:12:45 +0000179 continue
180
181 list = alias.get(m.group(1), [])
182 for item in m.group(2).split(','):
183 item = item.strip()
184 if item:
185 list.append(item)
186 alias[m.group(1)] = list
187
188 fd.close()
189
Vikram Narayanan87d65552012-05-23 09:01:06 +0000190def CreatePatmanConfigFile(config_fname):
191 """Creates a config file under $(HOME)/.patman if it can't find one.
192
193 Args:
194 config_fname: Default config filename i.e., $(HOME)/.patman
195
196 Returns:
197 None
198 """
199 name = gitutil.GetDefaultUserName()
200 if name == None:
201 name = raw_input("Enter name: ")
202
203 email = gitutil.GetDefaultUserEmail()
204
205 if email == None:
206 email = raw_input("Enter email: ")
207
208 try:
209 f = open(config_fname, 'w')
210 except IOError:
Paul Burtona920a172016-09-27 16:03:50 +0100211 print("Couldn't create patman config file\n")
Vikram Narayanan87d65552012-05-23 09:01:06 +0000212 raise
213
Simon Glassad893142017-09-12 20:30:28 -0600214 print('''[alias]
215me: %s <%s>
216
217[bounces]
218nxp = Zhikang Zhang <zhikang.zhang@nxp.com>
219''' % (name, email), file=f)
Vikram Narayanan87d65552012-05-23 09:01:06 +0000220 f.close();
221
Doug Anderson8568bae2012-12-03 14:43:17 +0000222def _UpdateDefaults(parser, config):
223 """Update the given OptionParser defaults based on config.
224
225 We'll walk through all of the settings from the parser
226 For each setting we'll look for a default in the option parser.
227 If it's found we'll update the option parser default.
228
229 The idea here is that the .patman file should be able to update
230 defaults but that command line flags should still have the final
231 say.
232
233 Args:
234 parser: An instance of an OptionParser whose defaults will be
235 updated.
Doug Andersona1dcee82012-12-03 14:43:18 +0000236 config: An instance of _ProjectConfigParser that we will query
Doug Anderson8568bae2012-12-03 14:43:17 +0000237 for settings.
238 """
239 defaults = parser.get_default_values()
240 for name, val in config.items('settings'):
241 if hasattr(defaults, name):
242 default_val = getattr(defaults, name)
243 if isinstance(default_val, bool):
244 val = config.getboolean('settings', name)
245 elif isinstance(default_val, int):
246 val = config.getint('settings', name)
247 parser.set_default(name, val)
248 else:
Paul Burtona920a172016-09-27 16:03:50 +0100249 print("WARNING: Unknown setting %s" % name)
Doug Anderson8568bae2012-12-03 14:43:17 +0000250
Simon Glass8895b3e2015-01-29 11:35:17 -0700251def _ReadAliasFile(fname):
252 """Read in the U-Boot git alias file if it exists.
253
254 Args:
255 fname: Filename to read.
256 """
257 if os.path.exists(fname):
258 bad_line = None
259 with open(fname) as fd:
260 linenum = 0
261 for line in fd:
262 linenum += 1
263 line = line.strip()
264 if not line or line.startswith('#'):
265 continue
266 words = line.split(' ', 2)
267 if len(words) < 3 or words[0] != 'alias':
268 if not bad_line:
269 bad_line = "%s:%d:Invalid line '%s'" % (fname, linenum,
270 line)
271 continue
272 alias[words[1]] = [s.strip() for s in words[2].split(',')]
273 if bad_line:
Paul Burtona920a172016-09-27 16:03:50 +0100274 print(bad_line)
Simon Glass8895b3e2015-01-29 11:35:17 -0700275
Chris Packhame11aa602017-09-01 20:57:53 +1200276def _ReadBouncesFile(fname):
277 """Read in the bounces file if it exists
278
279 Args:
280 fname: Filename to read.
281 """
282 if os.path.exists(fname):
283 with open(fname) as fd:
284 for line in fd:
285 if line.startswith('#'):
286 continue
287 bounces.add(line.strip())
288
Simon Glassad893142017-09-12 20:30:28 -0600289def GetItems(config, section):
290 """Get the items from a section of the config.
291
292 Args:
293 config: _ProjectConfigParser object containing settings
294 section: name of section to retrieve
295
296 Returns:
297 List of (name, value) tuples for the section
298 """
299 try:
300 return config.items(section)
301 except ConfigParser.NoSectionError as e:
302 return []
303 except:
304 raise
305
Doug Andersona1dcee82012-12-03 14:43:18 +0000306def Setup(parser, project_name, config_fname=''):
Simon Glass0d24de92012-01-14 15:12:45 +0000307 """Set up the settings module by reading config files.
308
309 Args:
Doug Anderson8568bae2012-12-03 14:43:17 +0000310 parser: The parser to update
Doug Andersona1dcee82012-12-03 14:43:18 +0000311 project_name: Name of project that we're working on; we'll look
312 for sections named "project_section" as well.
Simon Glass0d24de92012-01-14 15:12:45 +0000313 config_fname: Config filename to read ('' for default)
314 """
Simon Glass8895b3e2015-01-29 11:35:17 -0700315 # First read the git alias file if available
316 _ReadAliasFile('doc/git-mailrc')
Doug Andersona1dcee82012-12-03 14:43:18 +0000317 config = _ProjectConfigParser(project_name)
Simon Glass0d24de92012-01-14 15:12:45 +0000318 if config_fname == '':
Vikram Narayanan2b36c752012-05-23 08:58:58 +0000319 config_fname = '%s/.patman' % os.getenv('HOME')
Vikram Narayanan87d65552012-05-23 09:01:06 +0000320
321 if not os.path.exists(config_fname):
Paul Burtona920a172016-09-27 16:03:50 +0100322 print("No config file found ~/.patman\nCreating one...\n")
Vikram Narayanan87d65552012-05-23 09:01:06 +0000323 CreatePatmanConfigFile(config_fname)
324
Doug Anderson8568bae2012-12-03 14:43:17 +0000325 config.read(config_fname)
Simon Glass0d24de92012-01-14 15:12:45 +0000326
Simon Glassad893142017-09-12 20:30:28 -0600327 for name, value in GetItems(config, 'alias'):
Simon Glass0d24de92012-01-14 15:12:45 +0000328 alias[name] = value.split(',')
329
Chris Packhame11aa602017-09-01 20:57:53 +1200330 _ReadBouncesFile('doc/bounces')
Simon Glassad893142017-09-12 20:30:28 -0600331 for name, value in GetItems(config, 'bounces'):
Chris Packhame11aa602017-09-01 20:57:53 +1200332 bounces.add(value)
333
Doug Anderson8568bae2012-12-03 14:43:17 +0000334 _UpdateDefaults(parser, config)
Simon Glass0d24de92012-01-14 15:12:45 +0000335
336# These are the aliases we understand, indexed by alias. Each member is a list.
337alias = {}
Chris Packhame11aa602017-09-01 20:57:53 +1200338bounces = set()
Doug Andersona1dcee82012-12-03 14:43:18 +0000339
340if __name__ == "__main__":
341 import doctest
342
343 doctest.testmod()