xref: /openbmc/u-boot/tools/patman/settings.py (revision 2399e401)
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]\nme: %s <%s>" % (name, email), file=f)
216    f.close();
217
218def _UpdateDefaults(parser, config):
219    """Update the given OptionParser defaults based on config.
220
221    We'll walk through all of the settings from the parser
222    For each setting we'll look for a default in the option parser.
223    If it's found we'll update the option parser default.
224
225    The idea here is that the .patman file should be able to update
226    defaults but that command line flags should still have the final
227    say.
228
229    Args:
230        parser: An instance of an OptionParser whose defaults will be
231            updated.
232        config: An instance of _ProjectConfigParser that we will query
233            for settings.
234    """
235    defaults = parser.get_default_values()
236    for name, val in config.items('settings'):
237        if hasattr(defaults, name):
238            default_val = getattr(defaults, name)
239            if isinstance(default_val, bool):
240                val = config.getboolean('settings', name)
241            elif isinstance(default_val, int):
242                val = config.getint('settings', name)
243            parser.set_default(name, val)
244        else:
245            print("WARNING: Unknown setting %s" % name)
246
247def _ReadAliasFile(fname):
248    """Read in the U-Boot git alias file if it exists.
249
250    Args:
251        fname: Filename to read.
252    """
253    if os.path.exists(fname):
254        bad_line = None
255        with open(fname) as fd:
256            linenum = 0
257            for line in fd:
258                linenum += 1
259                line = line.strip()
260                if not line or line.startswith('#'):
261                    continue
262                words = line.split(' ', 2)
263                if len(words) < 3 or words[0] != 'alias':
264                    if not bad_line:
265                        bad_line = "%s:%d:Invalid line '%s'" % (fname, linenum,
266                                                                line)
267                    continue
268                alias[words[1]] = [s.strip() for s in words[2].split(',')]
269        if bad_line:
270            print(bad_line)
271
272def Setup(parser, project_name, config_fname=''):
273    """Set up the settings module by reading config files.
274
275    Args:
276        parser:         The parser to update
277        project_name:   Name of project that we're working on; we'll look
278            for sections named "project_section" as well.
279        config_fname:   Config filename to read ('' for default)
280    """
281    # First read the git alias file if available
282    _ReadAliasFile('doc/git-mailrc')
283    config = _ProjectConfigParser(project_name)
284    if config_fname == '':
285        config_fname = '%s/.patman' % os.getenv('HOME')
286
287    if not os.path.exists(config_fname):
288        print("No config file found ~/.patman\nCreating one...\n")
289        CreatePatmanConfigFile(config_fname)
290
291    config.read(config_fname)
292
293    for name, value in config.items('alias'):
294        alias[name] = value.split(',')
295
296    _UpdateDefaults(parser, config)
297
298# These are the aliases we understand, indexed by alias. Each member is a list.
299alias = {}
300
301if __name__ == "__main__":
302    import doctest
303
304    doctest.testmod()
305