blob: 7ef0ab0a31fc6a9944bd5230d865cbd82f17c30f [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...
39 >>> import StringIO
40 >>> sample_config = '''
41 ... [alias]
42 ... me: Peter P. <likesspiders@example.com>
43 ... enemies: Evil <evil@example.com>
44 ...
45 ... [sm_alias]
46 ... enemies: Green G. <ugly@example.com>
47 ...
48 ... [sm2_alias]
49 ... enemies: Doc O. <pus@example.com>
50 ...
51 ... [settings]
52 ... am_hero: True
53 ... '''
54
55 # Check to make sure that bogus project gets general alias.
56 >>> config = _ProjectConfigParser("zzz")
57 >>> config.readfp(StringIO.StringIO(sample_config))
58 >>> config.get("alias", "enemies")
59 'Evil <evil@example.com>'
60
61 # Check to make sure that alias gets overridden by project.
62 >>> config = _ProjectConfigParser("sm")
63 >>> config.readfp(StringIO.StringIO(sample_config))
64 >>> config.get("alias", "enemies")
65 'Green G. <ugly@example.com>'
66
67 # Check to make sure that settings get merged with project.
68 >>> config = _ProjectConfigParser("linux")
69 >>> config.readfp(StringIO.StringIO(sample_config))
70 >>> sorted(config.items("settings"))
71 [('am_hero', 'True'), ('process_tags', 'False')]
72
73 # Check to make sure that settings works with unknown project.
74 >>> config = _ProjectConfigParser("unknown")
75 >>> config.readfp(StringIO.StringIO(sample_config))
76 >>> sorted(config.items("settings"))
77 [('am_hero', 'True')]
78 """
79 def __init__(self, project_name):
80 """Construct _ProjectConfigParser.
81
82 In addition to standard SafeConfigParser initialization, this also loads
83 project defaults.
84
85 Args:
86 project_name: The name of the project.
87 """
88 self._project_name = project_name
89 ConfigParser.SafeConfigParser.__init__(self)
90
91 # Update the project settings in the config based on
92 # the _default_settings global.
93 project_settings = "%s_settings" % project_name
94 if not self.has_section(project_settings):
95 self.add_section(project_settings)
96 project_defaults = _default_settings.get(project_name, {})
Paul Burton4466f1a2016-09-27 16:03:54 +010097 for setting_name, setting_value in project_defaults.items():
Doug Anderson31ffd7f2012-12-03 14:43:18 +000098 self.set(project_settings, setting_name, setting_value)
99
100 def get(self, section, option, *args, **kwargs):
101 """Extend SafeConfigParser to try project_section before section.
102
103 Args:
104 See SafeConfigParser.
105 Returns:
106 See SafeConfigParser.
107 """
108 try:
109 return ConfigParser.SafeConfigParser.get(
110 self, "%s_%s" % (self._project_name, section), option,
111 *args, **kwargs
112 )
113 except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
114 return ConfigParser.SafeConfigParser.get(
115 self, section, option, *args, **kwargs
116 )
117
118 def items(self, section, *args, **kwargs):
119 """Extend SafeConfigParser to add project_section to section.
120
121 Args:
122 See SafeConfigParser.
123 Returns:
124 See SafeConfigParser.
125 """
126 project_items = []
127 has_project_section = False
128 top_items = []
129
130 # Get items from the project section
131 try:
132 project_items = ConfigParser.SafeConfigParser.items(
133 self, "%s_%s" % (self._project_name, section), *args, **kwargs
134 )
135 has_project_section = True
136 except ConfigParser.NoSectionError:
137 pass
138
139 # Get top-level items
140 try:
141 top_items = ConfigParser.SafeConfigParser.items(
142 self, section, *args, **kwargs
143 )
144 except ConfigParser.NoSectionError:
145 # If neither section exists raise the error on...
146 if not has_project_section:
147 raise
148
149 item_dict = dict(top_items)
150 item_dict.update(project_items)
151 return item_dict.items()
152
Simon Glass26132882012-01-14 15:12:45 +0000153def ReadGitAliases(fname):
154 """Read a git alias file. This is in the form used by git:
155
156 alias uboot u-boot@lists.denx.de
157 alias wd Wolfgang Denk <wd@denx.de>
158
159 Args:
160 fname: Filename to read
161 """
162 try:
163 fd = open(fname, 'r')
164 except IOError:
Paul Burtonc3931342016-09-27 16:03:50 +0100165 print("Warning: Cannot find alias file '%s'" % fname)
Simon Glass26132882012-01-14 15:12:45 +0000166 return
167
168 re_line = re.compile('alias\s+(\S+)\s+(.*)')
169 for line in fd.readlines():
170 line = line.strip()
171 if not line or line[0] == '#':
172 continue
173
174 m = re_line.match(line)
175 if not m:
Paul Burtonc3931342016-09-27 16:03:50 +0100176 print("Warning: Alias file line '%s' not understood" % line)
Simon Glass26132882012-01-14 15:12:45 +0000177 continue
178
179 list = alias.get(m.group(1), [])
180 for item in m.group(2).split(','):
181 item = item.strip()
182 if item:
183 list.append(item)
184 alias[m.group(1)] = list
185
186 fd.close()
187
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000188def CreatePatmanConfigFile(config_fname):
189 """Creates a config file under $(HOME)/.patman if it can't find one.
190
191 Args:
192 config_fname: Default config filename i.e., $(HOME)/.patman
193
194 Returns:
195 None
196 """
197 name = gitutil.GetDefaultUserName()
198 if name == None:
199 name = raw_input("Enter name: ")
200
201 email = gitutil.GetDefaultUserEmail()
202
203 if email == None:
204 email = raw_input("Enter email: ")
205
206 try:
207 f = open(config_fname, 'w')
208 except IOError:
Paul Burtonc3931342016-09-27 16:03:50 +0100209 print("Couldn't create patman config file\n")
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000210 raise
211
Paul Burtonc3931342016-09-27 16:03:50 +0100212 print("[alias]\nme: %s <%s>" % (name, email), file=f)
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000213 f.close();
214
Doug Anderson3d3077c2012-12-03 14:43:17 +0000215def _UpdateDefaults(parser, config):
216 """Update the given OptionParser defaults based on config.
217
218 We'll walk through all of the settings from the parser
219 For each setting we'll look for a default in the option parser.
220 If it's found we'll update the option parser default.
221
222 The idea here is that the .patman file should be able to update
223 defaults but that command line flags should still have the final
224 say.
225
226 Args:
227 parser: An instance of an OptionParser whose defaults will be
228 updated.
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000229 config: An instance of _ProjectConfigParser that we will query
Doug Anderson3d3077c2012-12-03 14:43:17 +0000230 for settings.
231 """
232 defaults = parser.get_default_values()
233 for name, val in config.items('settings'):
234 if hasattr(defaults, name):
235 default_val = getattr(defaults, name)
236 if isinstance(default_val, bool):
237 val = config.getboolean('settings', name)
238 elif isinstance(default_val, int):
239 val = config.getint('settings', name)
240 parser.set_default(name, val)
241 else:
Paul Burtonc3931342016-09-27 16:03:50 +0100242 print("WARNING: Unknown setting %s" % name)
Doug Anderson3d3077c2012-12-03 14:43:17 +0000243
Simon Glass33382692015-01-29 11:35:17 -0700244def _ReadAliasFile(fname):
245 """Read in the U-Boot git alias file if it exists.
246
247 Args:
248 fname: Filename to read.
249 """
250 if os.path.exists(fname):
251 bad_line = None
252 with open(fname) as fd:
253 linenum = 0
254 for line in fd:
255 linenum += 1
256 line = line.strip()
257 if not line or line.startswith('#'):
258 continue
259 words = line.split(' ', 2)
260 if len(words) < 3 or words[0] != 'alias':
261 if not bad_line:
262 bad_line = "%s:%d:Invalid line '%s'" % (fname, linenum,
263 line)
264 continue
265 alias[words[1]] = [s.strip() for s in words[2].split(',')]
266 if bad_line:
Paul Burtonc3931342016-09-27 16:03:50 +0100267 print(bad_line)
Simon Glass33382692015-01-29 11:35:17 -0700268
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000269def Setup(parser, project_name, config_fname=''):
Simon Glass26132882012-01-14 15:12:45 +0000270 """Set up the settings module by reading config files.
271
272 Args:
Doug Anderson3d3077c2012-12-03 14:43:17 +0000273 parser: The parser to update
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000274 project_name: Name of project that we're working on; we'll look
275 for sections named "project_section" as well.
Simon Glass26132882012-01-14 15:12:45 +0000276 config_fname: Config filename to read ('' for default)
277 """
Simon Glass33382692015-01-29 11:35:17 -0700278 # First read the git alias file if available
279 _ReadAliasFile('doc/git-mailrc')
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000280 config = _ProjectConfigParser(project_name)
Simon Glass26132882012-01-14 15:12:45 +0000281 if config_fname == '':
Vikram Narayananc387d36d2012-05-23 08:58:58 +0000282 config_fname = '%s/.patman' % os.getenv('HOME')
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000283
284 if not os.path.exists(config_fname):
Paul Burtonc3931342016-09-27 16:03:50 +0100285 print("No config file found ~/.patman\nCreating one...\n")
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000286 CreatePatmanConfigFile(config_fname)
287
Doug Anderson3d3077c2012-12-03 14:43:17 +0000288 config.read(config_fname)
Simon Glass26132882012-01-14 15:12:45 +0000289
Doug Anderson3d3077c2012-12-03 14:43:17 +0000290 for name, value in config.items('alias'):
Simon Glass26132882012-01-14 15:12:45 +0000291 alias[name] = value.split(',')
292
Doug Anderson3d3077c2012-12-03 14:43:17 +0000293 _UpdateDefaults(parser, config)
Simon Glass26132882012-01-14 15:12:45 +0000294
295# These are the aliases we understand, indexed by alias. Each member is a list.
296alias = {}
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000297
298if __name__ == "__main__":
299 import doctest
300
301 doctest.testmod()