xref: /openbmc/u-boot/tools/patman/settings.py (revision 90571a4a)
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    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
168def 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
203def 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]
228me: %s <%s>
229
230[bounces]
231nxp = Zhikang Zhang <zhikang.zhang@nxp.com>
232''' % (name, email), file=f)
233    f.close();
234
235def _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
264def _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
289def _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
302def 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
319def 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.
350alias = {}
351bounces = set()
352
353if __name__ == "__main__":
354    import doctest
355
356    doctest.testmod()
357