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