patman: Drop references to __future__
[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 try:
6     import configparser as ConfigParser
7 except:
8     import ConfigParser
9
10 import os
11 import re
12
13 import command
14 import gitutil
15 import tools
16
17 """Default settings per-project.
18
19 These are used by _ProjectConfigParser.  Settings names should match
20 the "dest" of the option parser from patman.py.
21 """
22 _default_settings = {
23     "u-boot": {},
24     "linux": {
25         "process_tags": "False",
26     }
27 }
28
29 class _ProjectConfigParser(ConfigParser.SafeConfigParser):
30     """ConfigParser that handles projects.
31
32     There are two main goals of this class:
33     - Load project-specific default settings.
34     - Merge general default settings/aliases with project-specific ones.
35
36     # Sample config used for tests below...
37     >>> try:
38     ...     from StringIO import StringIO
39     ... except ImportError:
40     ...     from io import StringIO
41     >>> sample_config = '''
42     ... [alias]
43     ... me: Peter P. <likesspiders@example.com>
44     ... enemies: Evil <evil@example.com>
45     ...
46     ... [sm_alias]
47     ... enemies: Green G. <ugly@example.com>
48     ...
49     ... [sm2_alias]
50     ... enemies: Doc O. <pus@example.com>
51     ...
52     ... [settings]
53     ... am_hero: True
54     ... '''
55
56     # Check to make sure that bogus project gets general alias.
57     >>> config = _ProjectConfigParser("zzz")
58     >>> config.readfp(StringIO(sample_config))
59     >>> str(config.get("alias", "enemies"))
60     'Evil <evil@example.com>'
61
62     # Check to make sure that alias gets overridden by project.
63     >>> config = _ProjectConfigParser("sm")
64     >>> config.readfp(StringIO(sample_config))
65     >>> str(config.get("alias", "enemies"))
66     'Green G. <ugly@example.com>'
67
68     # Check to make sure that settings get merged with project.
69     >>> config = _ProjectConfigParser("linux")
70     >>> config.readfp(StringIO(sample_config))
71     >>> sorted((str(a), str(b)) for (a, b) in config.items("settings"))
72     [('am_hero', 'True'), ('process_tags', 'False')]
73
74     # Check to make sure that settings works with unknown project.
75     >>> config = _ProjectConfigParser("unknown")
76     >>> config.readfp(StringIO(sample_config))
77     >>> sorted((str(a), str(b)) for (a, b) in config.items("settings"))
78     [('am_hero', 'True')]
79     """
80     def __init__(self, project_name):
81         """Construct _ProjectConfigParser.
82
83         In addition to standard SafeConfigParser initialization, this also loads
84         project defaults.
85
86         Args:
87             project_name: The name of the project.
88         """
89         self._project_name = project_name
90         ConfigParser.SafeConfigParser.__init__(self)
91
92         # Update the project settings in the config based on
93         # the _default_settings global.
94         project_settings = "%s_settings" % project_name
95         if not self.has_section(project_settings):
96             self.add_section(project_settings)
97         project_defaults = _default_settings.get(project_name, {})
98         for setting_name, setting_value in project_defaults.items():
99             self.set(project_settings, setting_name, setting_value)
100
101     def get(self, section, option, *args, **kwargs):
102         """Extend SafeConfigParser to try project_section before section.
103
104         Args:
105             See SafeConfigParser.
106         Returns:
107             See SafeConfigParser.
108         """
109         try:
110             val = ConfigParser.SafeConfigParser.get(
111                 self, "%s_%s" % (self._project_name, section), option,
112                 *args, **kwargs
113             )
114         except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
115             val = ConfigParser.SafeConfigParser.get(
116                 self, section, option, *args, **kwargs
117             )
118         return tools.ToUnicode(val)
119
120     def items(self, section, *args, **kwargs):
121         """Extend SafeConfigParser to add project_section to section.
122
123         Args:
124             See SafeConfigParser.
125         Returns:
126             See SafeConfigParser.
127         """
128         project_items = []
129         has_project_section = False
130         top_items = []
131
132         # Get items from the project section
133         try:
134             project_items = ConfigParser.SafeConfigParser.items(
135                 self, "%s_%s" % (self._project_name, section), *args, **kwargs
136             )
137             has_project_section = True
138         except ConfigParser.NoSectionError:
139             pass
140
141         # Get top-level items
142         try:
143             top_items = ConfigParser.SafeConfigParser.items(
144                 self, section, *args, **kwargs
145             )
146         except ConfigParser.NoSectionError:
147             # If neither section exists raise the error on...
148             if not has_project_section:
149                 raise
150
151         item_dict = dict(top_items)
152         item_dict.update(project_items)
153         return {(tools.ToUnicode(item), tools.ToUnicode(val))
154                 for item, val in item_dict.items()}
155
156 def 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', encoding='utf-8')
167     except IOError:
168         print("Warning: Cannot find alias file '%s'" % fname)
169         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:
179             print("Warning: Alias file line '%s' not understood" % line)
180             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
191 def 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:
212         print("Couldn't create patman config file\n")
213         raise
214
215     print('''[alias]
216 me: %s <%s>
217
218 [bounces]
219 nxp = Zhikang Zhang <zhikang.zhang@nxp.com>
220 ''' % (name, email), file=f)
221     f.close();
222
223 def _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.
237         config: An instance of _ProjectConfigParser that we will query
238             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:
250             print("WARNING: Unknown setting %s" % name)
251
252 def _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, encoding='utf-8') 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(None, 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:
275             print(bad_line)
276
277 def _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
290 def 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
307 def Setup(parser, project_name, config_fname=''):
308     """Set up the settings module by reading config files.
309
310     Args:
311         parser:         The parser to update
312         project_name:   Name of project that we're working on; we'll look
313             for sections named "project_section" as well.
314         config_fname:   Config filename to read ('' for default)
315     """
316     # First read the git alias file if available
317     _ReadAliasFile('doc/git-mailrc')
318     config = _ProjectConfigParser(project_name)
319     if config_fname == '':
320         config_fname = '%s/.patman' % os.getenv('HOME')
321
322     if not os.path.exists(config_fname):
323         print("No config file found ~/.patman\nCreating one...\n")
324         CreatePatmanConfigFile(config_fname)
325
326     config.read(config_fname)
327
328     for name, value in GetItems(config, 'alias'):
329         alias[name] = value.split(',')
330
331     _ReadBouncesFile('doc/bounces')
332     for name, value in GetItems(config, 'bounces'):
333         bounces.add(value)
334
335     _UpdateDefaults(parser, config)
336
337 # These are the aliases we understand, indexed by alias. Each member is a list.
338 alias = {}
339 bounces = set()
340
341 if __name__ == "__main__":
342     import doctest
343
344     doctest.testmod()