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