blob: d735ff9ba3c663be9510019724b247dd8c11d330 [file] [log] [blame]
Simon Glass0d24de92012-01-14 15:12:45 +00001# Copyright (c) 2011 The Chromium OS Authors.
2#
Wolfgang Denk1a459662013-07-08 09:37:19 +02003# SPDX-License-Identifier: GPL-2.0+
Simon Glass0d24de92012-01-14 15:12:45 +00004#
5
Paul Burtona920a172016-09-27 16:03:50 +01006from __future__ import print_function
7
Paul Burton2ce7b212016-09-27 16:03:52 +01008try:
9 import configparser as ConfigParser
10except:
11 import ConfigParser
12
Simon Glass0d24de92012-01-14 15:12:45 +000013import os
14import re
15
16import command
Vikram Narayanan87d65552012-05-23 09:01:06 +000017import gitutil
Simon Glass0d24de92012-01-14 15:12:45 +000018
Doug Andersona1dcee82012-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 Burtonf5d44b92016-09-27 16:03:55 +010039 >>> try:
40 ... from StringIO import StringIO
41 ... except ImportError:
42 ... from io import StringIO
Doug Andersona1dcee82012-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 Burtonf5d44b92016-09-27 16:03:55 +010060 >>> config.readfp(StringIO(sample_config))
Doug Andersona1dcee82012-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 Burtonf5d44b92016-09-27 16:03:55 +010066 >>> config.readfp(StringIO(sample_config))
Doug Andersona1dcee82012-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 Burtonf5d44b92016-09-27 16:03:55 +010072 >>> config.readfp(StringIO(sample_config))
Doug Andersona1dcee82012-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 Burtonf5d44b92016-09-27 16:03:55 +010078 >>> config.readfp(StringIO(sample_config))
Doug Andersona1dcee82012-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 Burtonc9eac382016-09-27 16:03:54 +0100100 for setting_name, setting_value in project_defaults.items():
Doug Andersona1dcee82012-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 Glass0d24de92012-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 Burtona920a172016-09-27 16:03:50 +0100168 print("Warning: Cannot find alias file '%s'" % fname)
Simon Glass0d24de92012-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 Burtona920a172016-09-27 16:03:50 +0100179 print("Warning: Alias file line '%s' not understood" % line)
Simon Glass0d24de92012-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 Narayanan87d65552012-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 Burtona920a172016-09-27 16:03:50 +0100212 print("Couldn't create patman config file\n")
Vikram Narayanan87d65552012-05-23 09:01:06 +0000213 raise
214
Paul Burtona920a172016-09-27 16:03:50 +0100215 print("[alias]\nme: %s <%s>" % (name, email), file=f)
Vikram Narayanan87d65552012-05-23 09:01:06 +0000216 f.close();
217
Doug Anderson8568bae2012-12-03 14:43:17 +0000218def _UpdateDefaults(parser, config):
219 """Update the given OptionParser defaults based on config.
220
221 We'll walk through all of the settings from the parser
222 For each setting we'll look for a default in the option parser.
223 If it's found we'll update the option parser default.
224
225 The idea here is that the .patman file should be able to update
226 defaults but that command line flags should still have the final
227 say.
228
229 Args:
230 parser: An instance of an OptionParser whose defaults will be
231 updated.
Doug Andersona1dcee82012-12-03 14:43:18 +0000232 config: An instance of _ProjectConfigParser that we will query
Doug Anderson8568bae2012-12-03 14:43:17 +0000233 for settings.
234 """
235 defaults = parser.get_default_values()
236 for name, val in config.items('settings'):
237 if hasattr(defaults, name):
238 default_val = getattr(defaults, name)
239 if isinstance(default_val, bool):
240 val = config.getboolean('settings', name)
241 elif isinstance(default_val, int):
242 val = config.getint('settings', name)
243 parser.set_default(name, val)
244 else:
Paul Burtona920a172016-09-27 16:03:50 +0100245 print("WARNING: Unknown setting %s" % name)
Doug Anderson8568bae2012-12-03 14:43:17 +0000246
Simon Glass8895b3e2015-01-29 11:35:17 -0700247def _ReadAliasFile(fname):
248 """Read in the U-Boot git alias file if it exists.
249
250 Args:
251 fname: Filename to read.
252 """
253 if os.path.exists(fname):
254 bad_line = None
255 with open(fname) as fd:
256 linenum = 0
257 for line in fd:
258 linenum += 1
259 line = line.strip()
260 if not line or line.startswith('#'):
261 continue
262 words = line.split(' ', 2)
263 if len(words) < 3 or words[0] != 'alias':
264 if not bad_line:
265 bad_line = "%s:%d:Invalid line '%s'" % (fname, linenum,
266 line)
267 continue
268 alias[words[1]] = [s.strip() for s in words[2].split(',')]
269 if bad_line:
Paul Burtona920a172016-09-27 16:03:50 +0100270 print(bad_line)
Simon Glass8895b3e2015-01-29 11:35:17 -0700271
Chris Packhame11aa602017-09-01 20:57:53 +1200272def _ReadBouncesFile(fname):
273 """Read in the bounces file if it exists
274
275 Args:
276 fname: Filename to read.
277 """
278 if os.path.exists(fname):
279 with open(fname) as fd:
280 for line in fd:
281 if line.startswith('#'):
282 continue
283 bounces.add(line.strip())
284
Doug Andersona1dcee82012-12-03 14:43:18 +0000285def Setup(parser, project_name, config_fname=''):
Simon Glass0d24de92012-01-14 15:12:45 +0000286 """Set up the settings module by reading config files.
287
288 Args:
Doug Anderson8568bae2012-12-03 14:43:17 +0000289 parser: The parser to update
Doug Andersona1dcee82012-12-03 14:43:18 +0000290 project_name: Name of project that we're working on; we'll look
291 for sections named "project_section" as well.
Simon Glass0d24de92012-01-14 15:12:45 +0000292 config_fname: Config filename to read ('' for default)
293 """
Simon Glass8895b3e2015-01-29 11:35:17 -0700294 # First read the git alias file if available
295 _ReadAliasFile('doc/git-mailrc')
Doug Andersona1dcee82012-12-03 14:43:18 +0000296 config = _ProjectConfigParser(project_name)
Simon Glass0d24de92012-01-14 15:12:45 +0000297 if config_fname == '':
Vikram Narayanan2b36c752012-05-23 08:58:58 +0000298 config_fname = '%s/.patman' % os.getenv('HOME')
Vikram Narayanan87d65552012-05-23 09:01:06 +0000299
300 if not os.path.exists(config_fname):
Paul Burtona920a172016-09-27 16:03:50 +0100301 print("No config file found ~/.patman\nCreating one...\n")
Vikram Narayanan87d65552012-05-23 09:01:06 +0000302 CreatePatmanConfigFile(config_fname)
303
Doug Anderson8568bae2012-12-03 14:43:17 +0000304 config.read(config_fname)
Simon Glass0d24de92012-01-14 15:12:45 +0000305
Doug Anderson8568bae2012-12-03 14:43:17 +0000306 for name, value in config.items('alias'):
Simon Glass0d24de92012-01-14 15:12:45 +0000307 alias[name] = value.split(',')
308
Chris Packhame11aa602017-09-01 20:57:53 +1200309 _ReadBouncesFile('doc/bounces')
310 for name, value in config.items('bounces'):
311 bounces.add(value)
312
Doug Anderson8568bae2012-12-03 14:43:17 +0000313 _UpdateDefaults(parser, config)
Simon Glass0d24de92012-01-14 15:12:45 +0000314
315# These are the aliases we understand, indexed by alias. Each member is a list.
316alias = {}
Chris Packhame11aa602017-09-01 20:57:53 +1200317bounces = set()
Doug Andersona1dcee82012-12-03 14:43:18 +0000318
319if __name__ == "__main__":
320 import doctest
321
322 doctest.testmod()