xref: /openbmc/u-boot/tools/patman/settings.py (revision f4abbee3)
1# Copyright (c) 2011 The Chromium OS Authors.
2#
3# SPDX-License-Identifier:	GPL-2.0+
4#
5
6import ConfigParser
7import os
8import re
9
10import command
11import gitutil
12
13"""Default settings per-project.
14
15These are used by _ProjectConfigParser.  Settings names should match
16the "dest" of the option parser from patman.py.
17"""
18_default_settings = {
19    "u-boot": {},
20    "linux": {
21        "process_tags": "False",
22    }
23}
24
25class _ProjectConfigParser(ConfigParser.SafeConfigParser):
26    """ConfigParser that handles projects.
27
28    There are two main goals of this class:
29    - Load project-specific default settings.
30    - Merge general default settings/aliases with project-specific ones.
31
32    # Sample config used for tests below...
33    >>> import StringIO
34    >>> sample_config = '''
35    ... [alias]
36    ... me: Peter P. <likesspiders@example.com>
37    ... enemies: Evil <evil@example.com>
38    ...
39    ... [sm_alias]
40    ... enemies: Green G. <ugly@example.com>
41    ...
42    ... [sm2_alias]
43    ... enemies: Doc O. <pus@example.com>
44    ...
45    ... [settings]
46    ... am_hero: True
47    ... '''
48
49    # Check to make sure that bogus project gets general alias.
50    >>> config = _ProjectConfigParser("zzz")
51    >>> config.readfp(StringIO.StringIO(sample_config))
52    >>> config.get("alias", "enemies")
53    'Evil <evil@example.com>'
54
55    # Check to make sure that alias gets overridden by project.
56    >>> config = _ProjectConfigParser("sm")
57    >>> config.readfp(StringIO.StringIO(sample_config))
58    >>> config.get("alias", "enemies")
59    'Green G. <ugly@example.com>'
60
61    # Check to make sure that settings get merged with project.
62    >>> config = _ProjectConfigParser("linux")
63    >>> config.readfp(StringIO.StringIO(sample_config))
64    >>> sorted(config.items("settings"))
65    [('am_hero', 'True'), ('process_tags', 'False')]
66
67    # Check to make sure that settings works with unknown project.
68    >>> config = _ProjectConfigParser("unknown")
69    >>> config.readfp(StringIO.StringIO(sample_config))
70    >>> sorted(config.items("settings"))
71    [('am_hero', 'True')]
72    """
73    def __init__(self, project_name):
74        """Construct _ProjectConfigParser.
75
76        In addition to standard SafeConfigParser initialization, this also loads
77        project defaults.
78
79        Args:
80            project_name: The name of the project.
81        """
82        self._project_name = project_name
83        ConfigParser.SafeConfigParser.__init__(self)
84
85        # Update the project settings in the config based on
86        # the _default_settings global.
87        project_settings = "%s_settings" % project_name
88        if not self.has_section(project_settings):
89            self.add_section(project_settings)
90        project_defaults = _default_settings.get(project_name, {})
91        for setting_name, setting_value in project_defaults.iteritems():
92            self.set(project_settings, setting_name, setting_value)
93
94    def get(self, section, option, *args, **kwargs):
95        """Extend SafeConfigParser to try project_section before section.
96
97        Args:
98            See SafeConfigParser.
99        Returns:
100            See SafeConfigParser.
101        """
102        try:
103            return ConfigParser.SafeConfigParser.get(
104                self, "%s_%s" % (self._project_name, section), option,
105                *args, **kwargs
106            )
107        except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
108            return ConfigParser.SafeConfigParser.get(
109                self, section, option, *args, **kwargs
110            )
111
112    def items(self, section, *args, **kwargs):
113        """Extend SafeConfigParser to add project_section to section.
114
115        Args:
116            See SafeConfigParser.
117        Returns:
118            See SafeConfigParser.
119        """
120        project_items = []
121        has_project_section = False
122        top_items = []
123
124        # Get items from the project section
125        try:
126            project_items = ConfigParser.SafeConfigParser.items(
127                self, "%s_%s" % (self._project_name, section), *args, **kwargs
128            )
129            has_project_section = True
130        except ConfigParser.NoSectionError:
131            pass
132
133        # Get top-level items
134        try:
135            top_items = ConfigParser.SafeConfigParser.items(
136                self, section, *args, **kwargs
137            )
138        except ConfigParser.NoSectionError:
139            # If neither section exists raise the error on...
140            if not has_project_section:
141                raise
142
143        item_dict = dict(top_items)
144        item_dict.update(project_items)
145        return item_dict.items()
146
147def ReadGitAliases(fname):
148    """Read a git alias file. This is in the form used by git:
149
150    alias uboot  u-boot@lists.denx.de
151    alias wd     Wolfgang Denk <wd@denx.de>
152
153    Args:
154        fname: Filename to read
155    """
156    try:
157        fd = open(fname, 'r')
158    except IOError:
159        print "Warning: Cannot find alias file '%s'" % fname
160        return
161
162    re_line = re.compile('alias\s+(\S+)\s+(.*)')
163    for line in fd.readlines():
164        line = line.strip()
165        if not line or line[0] == '#':
166            continue
167
168        m = re_line.match(line)
169        if not m:
170            print "Warning: Alias file line '%s' not understood" % line
171            continue
172
173        list = alias.get(m.group(1), [])
174        for item in m.group(2).split(','):
175            item = item.strip()
176            if item:
177                list.append(item)
178        alias[m.group(1)] = list
179
180    fd.close()
181
182def CreatePatmanConfigFile(config_fname):
183    """Creates a config file under $(HOME)/.patman if it can't find one.
184
185    Args:
186        config_fname: Default config filename i.e., $(HOME)/.patman
187
188    Returns:
189        None
190    """
191    name = gitutil.GetDefaultUserName()
192    if name == None:
193        name = raw_input("Enter name: ")
194
195    email = gitutil.GetDefaultUserEmail()
196
197    if email == None:
198        email = raw_input("Enter email: ")
199
200    try:
201        f = open(config_fname, 'w')
202    except IOError:
203        print "Couldn't create patman config file\n"
204        raise
205
206    print >>f, "[alias]\nme: %s <%s>" % (name, email)
207    f.close();
208
209def _UpdateDefaults(parser, config):
210    """Update the given OptionParser defaults based on config.
211
212    We'll walk through all of the settings from the parser
213    For each setting we'll look for a default in the option parser.
214    If it's found we'll update the option parser default.
215
216    The idea here is that the .patman file should be able to update
217    defaults but that command line flags should still have the final
218    say.
219
220    Args:
221        parser: An instance of an OptionParser whose defaults will be
222            updated.
223        config: An instance of _ProjectConfigParser that we will query
224            for settings.
225    """
226    defaults = parser.get_default_values()
227    for name, val in config.items('settings'):
228        if hasattr(defaults, name):
229            default_val = getattr(defaults, name)
230            if isinstance(default_val, bool):
231                val = config.getboolean('settings', name)
232            elif isinstance(default_val, int):
233                val = config.getint('settings', name)
234            parser.set_default(name, val)
235        else:
236            print "WARNING: Unknown setting %s" % name
237
238def _ReadAliasFile(fname):
239    """Read in the U-Boot git alias file if it exists.
240
241    Args:
242        fname: Filename to read.
243    """
244    if os.path.exists(fname):
245        bad_line = None
246        with open(fname) as fd:
247            linenum = 0
248            for line in fd:
249                linenum += 1
250                line = line.strip()
251                if not line or line.startswith('#'):
252                    continue
253                words = line.split(' ', 2)
254                if len(words) < 3 or words[0] != 'alias':
255                    if not bad_line:
256                        bad_line = "%s:%d:Invalid line '%s'" % (fname, linenum,
257                                                                line)
258                    continue
259                alias[words[1]] = [s.strip() for s in words[2].split(',')]
260        if bad_line:
261            print bad_line
262
263def Setup(parser, project_name, config_fname=''):
264    """Set up the settings module by reading config files.
265
266    Args:
267        parser:         The parser to update
268        project_name:   Name of project that we're working on; we'll look
269            for sections named "project_section" as well.
270        config_fname:   Config filename to read ('' for default)
271    """
272    # First read the git alias file if available
273    _ReadAliasFile('doc/git-mailrc')
274    config = _ProjectConfigParser(project_name)
275    if config_fname == '':
276        config_fname = '%s/.patman' % os.getenv('HOME')
277
278    if not os.path.exists(config_fname):
279        print "No config file found ~/.patman\nCreating one...\n"
280        CreatePatmanConfigFile(config_fname)
281
282    config.read(config_fname)
283
284    for name, value in config.items('alias'):
285        alias[name] = value.split(',')
286
287    _UpdateDefaults(parser, config)
288
289# These are the aliases we understand, indexed by alias. Each member is a list.
290alias = {}
291
292if __name__ == "__main__":
293    import doctest
294
295    doctest.testmod()
296