patman: Avoid duplicate sign-offs
[platform/kernel/u-boot.git] / tools / patman / settings.py
1 # Copyright (c) 2011 The Chromium OS Authors.
2 #
3 # SPDX-License-Identifier:      GPL-2.0+
4 #
5
6 import ConfigParser
7 import os
8 import re
9
10 import command
11 import gitutil
12
13 """Default settings per-project.
14
15 These are used by _ProjectConfigParser.  Settings names should match
16 the "dest" of the option parser from patman.py.
17 """
18 _default_settings = {
19     "u-boot": {},
20     "linux": {
21         "process_tags": "False",
22     }
23 }
24
25 class _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
147 def 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
182 def 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
209 def _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
238 def Setup(parser, project_name, config_fname=''):
239     """Set up the settings module by reading config files.
240
241     Args:
242         parser:         The parser to update
243         project_name:   Name of project that we're working on; we'll look
244             for sections named "project_section" as well.
245         config_fname:   Config filename to read ('' for default)
246     """
247     config = _ProjectConfigParser(project_name)
248     if config_fname == '':
249         config_fname = '%s/.patman' % os.getenv('HOME')
250
251     if not os.path.exists(config_fname):
252         print "No config file found ~/.patman\nCreating one...\n"
253         CreatePatmanConfigFile(config_fname)
254
255     config.read(config_fname)
256
257     for name, value in config.items('alias'):
258         alias[name] = value.split(',')
259
260     _UpdateDefaults(parser, config)
261
262 # These are the aliases we understand, indexed by alias. Each member is a list.
263 alias = {}
264
265 if __name__ == "__main__":
266     import doctest
267
268     doctest.testmod()