Merge https://gitlab.denx.de/u-boot/custodians/u-boot-marvell
[oweals/u-boot.git] / tools / patman / settings.py
1 # SPDX-License-Identifier: GPL-2.0+
2 # Copyright (c) 2011 The Chromium OS Authors.
3 #
4
5 from __future__ import print_function
6
7 try:
8     import configparser as ConfigParser
9 except:
10     import ConfigParser
11
12 import os
13 import re
14
15 import command
16 import gitutil
17
18 """Default settings per-project.
19
20 These are used by _ProjectConfigParser.  Settings names should match
21 the "dest" of the option parser from patman.py.
22 """
23 _default_settings = {
24     "u-boot": {},
25     "linux": {
26         "process_tags": "False",
27     }
28 }
29
30 class _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...
38     >>> try:
39     ...     from StringIO import StringIO
40     ... except ImportError:
41     ...     from io import StringIO
42     >>> 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")
59     >>> config.readfp(StringIO(sample_config))
60     >>> config.get("alias", "enemies")
61     u'Evil <evil@example.com>'
62
63     # Check to make sure that alias gets overridden by project.
64     >>> config = _ProjectConfigParser("sm")
65     >>> config.readfp(StringIO(sample_config))
66     >>> config.get("alias", "enemies")
67     u'Green G. <ugly@example.com>'
68
69     # Check to make sure that settings get merged with project.
70     >>> config = _ProjectConfigParser("linux")
71     >>> config.readfp(StringIO(sample_config))
72     >>> sorted(config.items("settings"))
73     [(u'am_hero', u'True'), (u'process_tags', u'False')]
74
75     # Check to make sure that settings works with unknown project.
76     >>> config = _ProjectConfigParser("unknown")
77     >>> config.readfp(StringIO(sample_config))
78     >>> sorted(config.items("settings"))
79     [(u'am_hero', u'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, {})
99         for setting_name, setting_value in project_defaults.items():
100             self.set(project_settings, setting_name, setting_value)
101
102     def _to_unicode(self, val):
103         """Make sure a value is of type 'unicode'
104
105         Args:
106             val: string or unicode object
107
108         Returns:
109             unicode version of val
110         """
111         return val if isinstance(val, unicode) else val.decode('utf-8')
112
113     def get(self, section, option, *args, **kwargs):
114         """Extend SafeConfigParser to try project_section before section.
115
116         Args:
117             See SafeConfigParser.
118         Returns:
119             See SafeConfigParser.
120         """
121         try:
122             val = ConfigParser.SafeConfigParser.get(
123                 self, "%s_%s" % (self._project_name, section), option,
124                 *args, **kwargs
125             )
126         except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
127             val = ConfigParser.SafeConfigParser.get(
128                 self, section, option, *args, **kwargs
129             )
130         return self._to_unicode(val)
131
132     def items(self, section, *args, **kwargs):
133         """Extend SafeConfigParser to add project_section to section.
134
135         Args:
136             See SafeConfigParser.
137         Returns:
138             See SafeConfigParser.
139         """
140         project_items = []
141         has_project_section = False
142         top_items = []
143
144         # Get items from the project section
145         try:
146             project_items = ConfigParser.SafeConfigParser.items(
147                 self, "%s_%s" % (self._project_name, section), *args, **kwargs
148             )
149             has_project_section = True
150         except ConfigParser.NoSectionError:
151             pass
152
153         # Get top-level items
154         try:
155             top_items = ConfigParser.SafeConfigParser.items(
156                 self, section, *args, **kwargs
157             )
158         except ConfigParser.NoSectionError:
159             # If neither section exists raise the error on...
160             if not has_project_section:
161                 raise
162
163         item_dict = dict(top_items)
164         item_dict.update(project_items)
165         return {(self._to_unicode(item), self._to_unicode(val))
166                 for item, val in item_dict.iteritems()}
167
168 def ReadGitAliases(fname):
169     """Read a git alias file. This is in the form used by git:
170
171     alias uboot  u-boot@lists.denx.de
172     alias wd     Wolfgang Denk <wd@denx.de>
173
174     Args:
175         fname: Filename to read
176     """
177     try:
178         fd = open(fname, 'r')
179     except IOError:
180         print("Warning: Cannot find alias file '%s'" % fname)
181         return
182
183     re_line = re.compile('alias\s+(\S+)\s+(.*)')
184     for line in fd.readlines():
185         line = line.strip()
186         if not line or line[0] == '#':
187             continue
188
189         m = re_line.match(line)
190         if not m:
191             print("Warning: Alias file line '%s' not understood" % line)
192             continue
193
194         list = alias.get(m.group(1), [])
195         for item in m.group(2).split(','):
196             item = item.strip()
197             if item:
198                 list.append(item)
199         alias[m.group(1)] = list
200
201     fd.close()
202
203 def CreatePatmanConfigFile(config_fname):
204     """Creates a config file under $(HOME)/.patman if it can't find one.
205
206     Args:
207         config_fname: Default config filename i.e., $(HOME)/.patman
208
209     Returns:
210         None
211     """
212     name = gitutil.GetDefaultUserName()
213     if name == None:
214         name = raw_input("Enter name: ")
215
216     email = gitutil.GetDefaultUserEmail()
217
218     if email == None:
219         email = raw_input("Enter email: ")
220
221     try:
222         f = open(config_fname, 'w')
223     except IOError:
224         print("Couldn't create patman config file\n")
225         raise
226
227     print('''[alias]
228 me: %s <%s>
229
230 [bounces]
231 nxp = Zhikang Zhang <zhikang.zhang@nxp.com>
232 ''' % (name, email), file=f)
233     f.close();
234
235 def _UpdateDefaults(parser, config):
236     """Update the given OptionParser defaults based on config.
237
238     We'll walk through all of the settings from the parser
239     For each setting we'll look for a default in the option parser.
240     If it's found we'll update the option parser default.
241
242     The idea here is that the .patman file should be able to update
243     defaults but that command line flags should still have the final
244     say.
245
246     Args:
247         parser: An instance of an OptionParser whose defaults will be
248             updated.
249         config: An instance of _ProjectConfigParser that we will query
250             for settings.
251     """
252     defaults = parser.get_default_values()
253     for name, val in config.items('settings'):
254         if hasattr(defaults, name):
255             default_val = getattr(defaults, name)
256             if isinstance(default_val, bool):
257                 val = config.getboolean('settings', name)
258             elif isinstance(default_val, int):
259                 val = config.getint('settings', name)
260             parser.set_default(name, val)
261         else:
262             print("WARNING: Unknown setting %s" % name)
263
264 def _ReadAliasFile(fname):
265     """Read in the U-Boot git alias file if it exists.
266
267     Args:
268         fname: Filename to read.
269     """
270     if os.path.exists(fname):
271         bad_line = None
272         with open(fname) as fd:
273             linenum = 0
274             for line in fd:
275                 linenum += 1
276                 line = line.strip()
277                 if not line or line.startswith('#'):
278                     continue
279                 words = line.split(None, 2)
280                 if len(words) < 3 or words[0] != 'alias':
281                     if not bad_line:
282                         bad_line = "%s:%d:Invalid line '%s'" % (fname, linenum,
283                                                                 line)
284                     continue
285                 alias[words[1]] = [s.strip() for s in words[2].split(',')]
286         if bad_line:
287             print(bad_line)
288
289 def _ReadBouncesFile(fname):
290     """Read in the bounces file if it exists
291
292     Args:
293         fname: Filename to read.
294     """
295     if os.path.exists(fname):
296         with open(fname) as fd:
297             for line in fd:
298                 if line.startswith('#'):
299                     continue
300                 bounces.add(line.strip())
301
302 def GetItems(config, section):
303     """Get the items from a section of the config.
304
305     Args:
306         config: _ProjectConfigParser object containing settings
307         section: name of section to retrieve
308
309     Returns:
310         List of (name, value) tuples for the section
311     """
312     try:
313         return config.items(section)
314     except ConfigParser.NoSectionError as e:
315         return []
316     except:
317         raise
318
319 def Setup(parser, project_name, config_fname=''):
320     """Set up the settings module by reading config files.
321
322     Args:
323         parser:         The parser to update
324         project_name:   Name of project that we're working on; we'll look
325             for sections named "project_section" as well.
326         config_fname:   Config filename to read ('' for default)
327     """
328     # First read the git alias file if available
329     _ReadAliasFile('doc/git-mailrc')
330     config = _ProjectConfigParser(project_name)
331     if config_fname == '':
332         config_fname = '%s/.patman' % os.getenv('HOME')
333
334     if not os.path.exists(config_fname):
335         print("No config file found ~/.patman\nCreating one...\n")
336         CreatePatmanConfigFile(config_fname)
337
338     config.read(config_fname)
339
340     for name, value in GetItems(config, 'alias'):
341         alias[name] = value.split(',')
342
343     _ReadBouncesFile('doc/bounces')
344     for name, value in GetItems(config, 'bounces'):
345         bounces.add(value)
346
347     _UpdateDefaults(parser, config)
348
349 # These are the aliases we understand, indexed by alias. Each member is a list.
350 alias = {}
351 bounces = set()
352
353 if __name__ == "__main__":
354     import doctest
355
356     doctest.testmod()