blob: 94ea5b5a1b0c26993833a187873a9075800a23a7 [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 Glass26132882012-01-14 15:12:45 +000017
Doug Anderson31ffd7f2012-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 Burton44fc9ba2016-09-27 16:03:55 +010038 >>> try:
39 ... from StringIO import StringIO
40 ... except ImportError:
41 ... from io import StringIO
Doug Anderson31ffd7f2012-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 Burton44fc9ba2016-09-27 16:03:55 +010059 >>> config.readfp(StringIO(sample_config))
Doug Anderson31ffd7f2012-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 Burton44fc9ba2016-09-27 16:03:55 +010065 >>> config.readfp(StringIO(sample_config))
Doug Anderson31ffd7f2012-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 Burton44fc9ba2016-09-27 16:03:55 +010071 >>> config.readfp(StringIO(sample_config))
Doug Anderson31ffd7f2012-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 Burton44fc9ba2016-09-27 16:03:55 +010077 >>> config.readfp(StringIO(sample_config))
Doug Anderson31ffd7f2012-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 Burton4466f1a2016-09-27 16:03:54 +010099 for setting_name, setting_value in project_defaults.items():
Doug Anderson31ffd7f2012-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 Glass26132882012-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 Burtonc3931342016-09-27 16:03:50 +0100167 print("Warning: Cannot find alias file '%s'" % fname)
Simon Glass26132882012-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 Burtonc3931342016-09-27 16:03:50 +0100178 print("Warning: Alias file line '%s' not understood" % line)
Simon Glass26132882012-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 Narayanan12fb29a2012-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 Burtonc3931342016-09-27 16:03:50 +0100211 print("Couldn't create patman config file\n")
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000212 raise
213
Simon Glass52a08642017-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 Narayanan12fb29a2012-05-23 09:01:06 +0000220 f.close();
221
Doug Anderson3d3077c2012-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 Anderson31ffd7f2012-12-03 14:43:18 +0000236 config: An instance of _ProjectConfigParser that we will query
Doug Anderson3d3077c2012-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 Burtonc3931342016-09-27 16:03:50 +0100249 print("WARNING: Unknown setting %s" % name)
Doug Anderson3d3077c2012-12-03 14:43:17 +0000250
Simon Glass33382692015-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 Burtonc3931342016-09-27 16:03:50 +0100274 print(bad_line)
Simon Glass33382692015-01-29 11:35:17 -0700275
Chris Packhame8d2a122017-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 Glass52a08642017-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 Anderson31ffd7f2012-12-03 14:43:18 +0000306def Setup(parser, project_name, config_fname=''):
Simon Glass26132882012-01-14 15:12:45 +0000307 """Set up the settings module by reading config files.
308
309 Args:
Doug Anderson3d3077c2012-12-03 14:43:17 +0000310 parser: The parser to update
Doug Anderson31ffd7f2012-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 Glass26132882012-01-14 15:12:45 +0000313 config_fname: Config filename to read ('' for default)
314 """
Simon Glass33382692015-01-29 11:35:17 -0700315 # First read the git alias file if available
316 _ReadAliasFile('doc/git-mailrc')
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000317 config = _ProjectConfigParser(project_name)
Simon Glass26132882012-01-14 15:12:45 +0000318 if config_fname == '':
Vikram Narayananc387d36d2012-05-23 08:58:58 +0000319 config_fname = '%s/.patman' % os.getenv('HOME')
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000320
321 if not os.path.exists(config_fname):
Paul Burtonc3931342016-09-27 16:03:50 +0100322 print("No config file found ~/.patman\nCreating one...\n")
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000323 CreatePatmanConfigFile(config_fname)
324
Doug Anderson3d3077c2012-12-03 14:43:17 +0000325 config.read(config_fname)
Simon Glass26132882012-01-14 15:12:45 +0000326
Simon Glass52a08642017-09-12 20:30:28 -0600327 for name, value in GetItems(config, 'alias'):
Simon Glass26132882012-01-14 15:12:45 +0000328 alias[name] = value.split(',')
329
Chris Packhame8d2a122017-09-01 20:57:53 +1200330 _ReadBouncesFile('doc/bounces')
Simon Glass52a08642017-09-12 20:30:28 -0600331 for name, value in GetItems(config, 'bounces'):
Chris Packhame8d2a122017-09-01 20:57:53 +1200332 bounces.add(value)
333
Doug Anderson3d3077c2012-12-03 14:43:17 +0000334 _UpdateDefaults(parser, config)
Simon Glass26132882012-01-14 15:12:45 +0000335
336# These are the aliases we understand, indexed by alias. Each member is a list.
337alias = {}
Chris Packhame8d2a122017-09-01 20:57:53 +1200338bounces = set()
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000339
340if __name__ == "__main__":
341 import doctest
342
343 doctest.testmod()