blob: 92379b72e76474173c3b4dd2224dcb474363a914 [file] [log] [blame]
Simon Glass26132882012-01-14 15:12:45 +00001# Copyright (c) 2011 The Chromium OS Authors.
2#
Wolfgang Denkd79de1d2013-07-08 09:37:19 +02003# SPDX-License-Identifier: GPL-2.0+
Simon Glass26132882012-01-14 15:12:45 +00004#
5
Paul Burtonc3931342016-09-27 16:03:50 +01006from __future__ import print_function
7
Paul Burton352f1102016-09-27 16:03:52 +01008try:
9 import configparser as ConfigParser
10except:
11 import ConfigParser
12
Simon Glass26132882012-01-14 15:12:45 +000013import os
14import re
15
16import command
Vikram Narayanan12fb29a2012-05-23 09:01:06 +000017import gitutil
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))
Doug Anderson31ffd7f2012-12-03 14:43:18 +000061 >>> config.get("alias", "enemies")
62 'Evil <evil@example.com>'
63
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))
Doug Anderson31ffd7f2012-12-03 14:43:18 +000067 >>> config.get("alias", "enemies")
68 'Green G. <ugly@example.com>'
69
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))
Doug Anderson31ffd7f2012-12-03 14:43:18 +000073 >>> sorted(config.items("settings"))
74 [('am_hero', 'True'), ('process_tags', 'False')]
75
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))
Doug Anderson31ffd7f2012-12-03 14:43:18 +000079 >>> sorted(config.items("settings"))
80 [('am_hero', 'True')]
81 """
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:
112 return ConfigParser.SafeConfigParser.get(
113 self, "%s_%s" % (self._project_name, section), option,
114 *args, **kwargs
115 )
116 except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
117 return ConfigParser.SafeConfigParser.get(
118 self, section, option, *args, **kwargs
119 )
120
121 def items(self, section, *args, **kwargs):
122 """Extend SafeConfigParser to add project_section to section.
123
124 Args:
125 See SafeConfigParser.
126 Returns:
127 See SafeConfigParser.
128 """
129 project_items = []
130 has_project_section = False
131 top_items = []
132
133 # Get items from the project section
134 try:
135 project_items = ConfigParser.SafeConfigParser.items(
136 self, "%s_%s" % (self._project_name, section), *args, **kwargs
137 )
138 has_project_section = True
139 except ConfigParser.NoSectionError:
140 pass
141
142 # Get top-level items
143 try:
144 top_items = ConfigParser.SafeConfigParser.items(
145 self, section, *args, **kwargs
146 )
147 except ConfigParser.NoSectionError:
148 # If neither section exists raise the error on...
149 if not has_project_section:
150 raise
151
152 item_dict = dict(top_items)
153 item_dict.update(project_items)
154 return item_dict.items()
155
Simon Glass26132882012-01-14 15:12:45 +0000156def ReadGitAliases(fname):
157 """Read a git alias file. This is in the form used by git:
158
159 alias uboot u-boot@lists.denx.de
160 alias wd Wolfgang Denk <wd@denx.de>
161
162 Args:
163 fname: Filename to read
164 """
165 try:
166 fd = open(fname, 'r')
167 except IOError:
Paul Burtonc3931342016-09-27 16:03:50 +0100168 print("Warning: Cannot find alias file '%s'" % fname)
Simon Glass26132882012-01-14 15:12:45 +0000169 return
170
171 re_line = re.compile('alias\s+(\S+)\s+(.*)')
172 for line in fd.readlines():
173 line = line.strip()
174 if not line or line[0] == '#':
175 continue
176
177 m = re_line.match(line)
178 if not m:
Paul Burtonc3931342016-09-27 16:03:50 +0100179 print("Warning: Alias file line '%s' not understood" % line)
Simon Glass26132882012-01-14 15:12:45 +0000180 continue
181
182 list = alias.get(m.group(1), [])
183 for item in m.group(2).split(','):
184 item = item.strip()
185 if item:
186 list.append(item)
187 alias[m.group(1)] = list
188
189 fd.close()
190
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000191def CreatePatmanConfigFile(config_fname):
192 """Creates a config file under $(HOME)/.patman if it can't find one.
193
194 Args:
195 config_fname: Default config filename i.e., $(HOME)/.patman
196
197 Returns:
198 None
199 """
200 name = gitutil.GetDefaultUserName()
201 if name == None:
202 name = raw_input("Enter name: ")
203
204 email = gitutil.GetDefaultUserEmail()
205
206 if email == None:
207 email = raw_input("Enter email: ")
208
209 try:
210 f = open(config_fname, 'w')
211 except IOError:
Paul Burtonc3931342016-09-27 16:03:50 +0100212 print("Couldn't create patman config file\n")
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000213 raise
214
Simon Glass52a08642017-09-12 20:30:28 -0600215 print('''[alias]
216me: %s <%s>
217
218[bounces]
219nxp = Zhikang Zhang <zhikang.zhang@nxp.com>
220''' % (name, email), file=f)
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000221 f.close();
222
Doug Anderson3d3077c2012-12-03 14:43:17 +0000223def _UpdateDefaults(parser, config):
224 """Update the given OptionParser defaults based on config.
225
226 We'll walk through all of the settings from the parser
227 For each setting we'll look for a default in the option parser.
228 If it's found we'll update the option parser default.
229
230 The idea here is that the .patman file should be able to update
231 defaults but that command line flags should still have the final
232 say.
233
234 Args:
235 parser: An instance of an OptionParser whose defaults will be
236 updated.
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000237 config: An instance of _ProjectConfigParser that we will query
Doug Anderson3d3077c2012-12-03 14:43:17 +0000238 for settings.
239 """
240 defaults = parser.get_default_values()
241 for name, val in config.items('settings'):
242 if hasattr(defaults, name):
243 default_val = getattr(defaults, name)
244 if isinstance(default_val, bool):
245 val = config.getboolean('settings', name)
246 elif isinstance(default_val, int):
247 val = config.getint('settings', name)
248 parser.set_default(name, val)
249 else:
Paul Burtonc3931342016-09-27 16:03:50 +0100250 print("WARNING: Unknown setting %s" % name)
Doug Anderson3d3077c2012-12-03 14:43:17 +0000251
Simon Glass33382692015-01-29 11:35:17 -0700252def _ReadAliasFile(fname):
253 """Read in the U-Boot git alias file if it exists.
254
255 Args:
256 fname: Filename to read.
257 """
258 if os.path.exists(fname):
259 bad_line = None
260 with open(fname) as fd:
261 linenum = 0
262 for line in fd:
263 linenum += 1
264 line = line.strip()
265 if not line or line.startswith('#'):
266 continue
267 words = line.split(' ', 2)
268 if len(words) < 3 or words[0] != 'alias':
269 if not bad_line:
270 bad_line = "%s:%d:Invalid line '%s'" % (fname, linenum,
271 line)
272 continue
273 alias[words[1]] = [s.strip() for s in words[2].split(',')]
274 if bad_line:
Paul Burtonc3931342016-09-27 16:03:50 +0100275 print(bad_line)
Simon Glass33382692015-01-29 11:35:17 -0700276
Chris Packhame8d2a122017-09-01 20:57:53 +1200277def _ReadBouncesFile(fname):
278 """Read in the bounces file if it exists
279
280 Args:
281 fname: Filename to read.
282 """
283 if os.path.exists(fname):
284 with open(fname) as fd:
285 for line in fd:
286 if line.startswith('#'):
287 continue
288 bounces.add(line.strip())
289
Simon Glass52a08642017-09-12 20:30:28 -0600290def GetItems(config, section):
291 """Get the items from a section of the config.
292
293 Args:
294 config: _ProjectConfigParser object containing settings
295 section: name of section to retrieve
296
297 Returns:
298 List of (name, value) tuples for the section
299 """
300 try:
301 return config.items(section)
302 except ConfigParser.NoSectionError as e:
303 return []
304 except:
305 raise
306
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000307def Setup(parser, project_name, config_fname=''):
Simon Glass26132882012-01-14 15:12:45 +0000308 """Set up the settings module by reading config files.
309
310 Args:
Doug Anderson3d3077c2012-12-03 14:43:17 +0000311 parser: The parser to update
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000312 project_name: Name of project that we're working on; we'll look
313 for sections named "project_section" as well.
Simon Glass26132882012-01-14 15:12:45 +0000314 config_fname: Config filename to read ('' for default)
315 """
Simon Glass33382692015-01-29 11:35:17 -0700316 # First read the git alias file if available
317 _ReadAliasFile('doc/git-mailrc')
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000318 config = _ProjectConfigParser(project_name)
Simon Glass26132882012-01-14 15:12:45 +0000319 if config_fname == '':
Vikram Narayananc387d36d2012-05-23 08:58:58 +0000320 config_fname = '%s/.patman' % os.getenv('HOME')
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000321
322 if not os.path.exists(config_fname):
Paul Burtonc3931342016-09-27 16:03:50 +0100323 print("No config file found ~/.patman\nCreating one...\n")
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000324 CreatePatmanConfigFile(config_fname)
325
Doug Anderson3d3077c2012-12-03 14:43:17 +0000326 config.read(config_fname)
Simon Glass26132882012-01-14 15:12:45 +0000327
Simon Glass52a08642017-09-12 20:30:28 -0600328 for name, value in GetItems(config, 'alias'):
Simon Glass26132882012-01-14 15:12:45 +0000329 alias[name] = value.split(',')
330
Chris Packhame8d2a122017-09-01 20:57:53 +1200331 _ReadBouncesFile('doc/bounces')
Simon Glass52a08642017-09-12 20:30:28 -0600332 for name, value in GetItems(config, 'bounces'):
Chris Packhame8d2a122017-09-01 20:57:53 +1200333 bounces.add(value)
334
Doug Anderson3d3077c2012-12-03 14:43:17 +0000335 _UpdateDefaults(parser, config)
Simon Glass26132882012-01-14 15:12:45 +0000336
337# These are the aliases we understand, indexed by alias. Each member is a list.
338alias = {}
Chris Packhame8d2a122017-09-01 20:57:53 +1200339bounces = set()
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000340
341if __name__ == "__main__":
342 import doctest
343
344 doctest.testmod()