xref: /openbmc/u-boot/tools/patman/settings.py (revision d6400c3f)
1# Copyright (c) 2011 The Chromium OS Authors.
2#
3# SPDX-License-Identifier:	GPL-2.0+
4#
5
6from __future__ import print_function
7
8try:
9    import configparser as ConfigParser
10except:
11    import ConfigParser
12
13import os
14import re
15
16import command
17import gitutil
18
19"""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    >>> 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    >>> 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    >>> 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(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(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            return ConfigParser.SafeConfigParser.get(
113                self, "%s_%s" % (self._project_name, section), option,
114                *args, **kwargs
115            )
116        except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
117            return ConfigParser.SafeConfigParser.get(
118                self, section, option, *args, **kwargs
119            )
120
121    def items(self, section, *args, **kwargs):
122        """Extend SafeConfigParser to add project_section to section.
123
124        Args:
125            See SafeConfigParser.
126        Returns:
127            See SafeConfigParser.
128        """
129        project_items = []
130        has_project_section = False
131        top_items = []
132
133        # Get items from the project section
134        try:
135            project_items = ConfigParser.SafeConfigParser.items(
136                self, "%s_%s" % (self._project_name, section), *args, **kwargs
137            )
138            has_project_section = True
139        except ConfigParser.NoSectionError:
140            pass
141
142        # Get top-level items
143        try:
144            top_items = ConfigParser.SafeConfigParser.items(
145                self, section, *args, **kwargs
146            )
147        except ConfigParser.NoSectionError:
148            # If neither section exists raise the error on...
149            if not has_project_section:
150                raise
151
152        item_dict = dict(top_items)
153        item_dict.update(project_items)
154        return item_dict.items()
155
156def 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')
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
191def 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]
216me: %s <%s>
217
218[bounces]
219nxp = Zhikang Zhang <zhikang.zhang@nxp.com>
220''' % (name, email), file=f)
221    f.close();
222
223def _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
252def _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) 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(' ', 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
277def _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
290def 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
307def 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.
338alias = {}
339bounces = set()
340
341if __name__ == "__main__":
342    import doctest
343
344    doctest.testmod()
345