blob: 084d1b80e525d097dad1f0a809b8d389c794b9de [file] [log] [blame]
Simon Glass0d24de92012-01-14 15:12:45 +00001# Copyright (c) 2011 The Chromium OS Authors.
2#
3# See file CREDITS for list of people who contributed to this
4# project.
5#
6# This program is free software; you can redistribute it and/or
7# modify it under the terms of the GNU General Public License as
8# published by the Free Software Foundation; either version 2 of
9# the License, or (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program; if not, write to the Free Software
18# Foundation, Inc., 59 Temple Place, Suite 330, Boston,
19# MA 02111-1307 USA
20#
21
22import ConfigParser
23import os
24import re
25
26import command
Vikram Narayanan87d65552012-05-23 09:01:06 +000027import gitutil
Simon Glass0d24de92012-01-14 15:12:45 +000028
Doug Andersona1dcee82012-12-03 14:43:18 +000029"""Default settings per-project.
30
31These are used by _ProjectConfigParser. Settings names should match
32the "dest" of the option parser from patman.py.
33"""
34_default_settings = {
35 "u-boot": {},
36 "linux": {
37 "process_tags": "False",
38 }
39}
40
41class _ProjectConfigParser(ConfigParser.SafeConfigParser):
42 """ConfigParser that handles projects.
43
44 There are two main goals of this class:
45 - Load project-specific default settings.
46 - Merge general default settings/aliases with project-specific ones.
47
48 # Sample config used for tests below...
49 >>> import StringIO
50 >>> sample_config = '''
51 ... [alias]
52 ... me: Peter P. <likesspiders@example.com>
53 ... enemies: Evil <evil@example.com>
54 ...
55 ... [sm_alias]
56 ... enemies: Green G. <ugly@example.com>
57 ...
58 ... [sm2_alias]
59 ... enemies: Doc O. <pus@example.com>
60 ...
61 ... [settings]
62 ... am_hero: True
63 ... '''
64
65 # Check to make sure that bogus project gets general alias.
66 >>> config = _ProjectConfigParser("zzz")
67 >>> config.readfp(StringIO.StringIO(sample_config))
68 >>> config.get("alias", "enemies")
69 'Evil <evil@example.com>'
70
71 # Check to make sure that alias gets overridden by project.
72 >>> config = _ProjectConfigParser("sm")
73 >>> config.readfp(StringIO.StringIO(sample_config))
74 >>> config.get("alias", "enemies")
75 'Green G. <ugly@example.com>'
76
77 # Check to make sure that settings get merged with project.
78 >>> config = _ProjectConfigParser("linux")
79 >>> config.readfp(StringIO.StringIO(sample_config))
80 >>> sorted(config.items("settings"))
81 [('am_hero', 'True'), ('process_tags', 'False')]
82
83 # Check to make sure that settings works with unknown project.
84 >>> config = _ProjectConfigParser("unknown")
85 >>> config.readfp(StringIO.StringIO(sample_config))
86 >>> sorted(config.items("settings"))
87 [('am_hero', 'True')]
88 """
89 def __init__(self, project_name):
90 """Construct _ProjectConfigParser.
91
92 In addition to standard SafeConfigParser initialization, this also loads
93 project defaults.
94
95 Args:
96 project_name: The name of the project.
97 """
98 self._project_name = project_name
99 ConfigParser.SafeConfigParser.__init__(self)
100
101 # Update the project settings in the config based on
102 # the _default_settings global.
103 project_settings = "%s_settings" % project_name
104 if not self.has_section(project_settings):
105 self.add_section(project_settings)
106 project_defaults = _default_settings.get(project_name, {})
107 for setting_name, setting_value in project_defaults.iteritems():
108 self.set(project_settings, setting_name, setting_value)
109
110 def get(self, section, option, *args, **kwargs):
111 """Extend SafeConfigParser to try project_section before section.
112
113 Args:
114 See SafeConfigParser.
115 Returns:
116 See SafeConfigParser.
117 """
118 try:
119 return ConfigParser.SafeConfigParser.get(
120 self, "%s_%s" % (self._project_name, section), option,
121 *args, **kwargs
122 )
123 except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
124 return ConfigParser.SafeConfigParser.get(
125 self, section, option, *args, **kwargs
126 )
127
128 def items(self, section, *args, **kwargs):
129 """Extend SafeConfigParser to add project_section to section.
130
131 Args:
132 See SafeConfigParser.
133 Returns:
134 See SafeConfigParser.
135 """
136 project_items = []
137 has_project_section = False
138 top_items = []
139
140 # Get items from the project section
141 try:
142 project_items = ConfigParser.SafeConfigParser.items(
143 self, "%s_%s" % (self._project_name, section), *args, **kwargs
144 )
145 has_project_section = True
146 except ConfigParser.NoSectionError:
147 pass
148
149 # Get top-level items
150 try:
151 top_items = ConfigParser.SafeConfigParser.items(
152 self, section, *args, **kwargs
153 )
154 except ConfigParser.NoSectionError:
155 # If neither section exists raise the error on...
156 if not has_project_section:
157 raise
158
159 item_dict = dict(top_items)
160 item_dict.update(project_items)
161 return item_dict.items()
162
Simon Glass0d24de92012-01-14 15:12:45 +0000163def ReadGitAliases(fname):
164 """Read a git alias file. This is in the form used by git:
165
166 alias uboot u-boot@lists.denx.de
167 alias wd Wolfgang Denk <wd@denx.de>
168
169 Args:
170 fname: Filename to read
171 """
172 try:
173 fd = open(fname, 'r')
174 except IOError:
175 print "Warning: Cannot find alias file '%s'" % fname
176 return
177
178 re_line = re.compile('alias\s+(\S+)\s+(.*)')
179 for line in fd.readlines():
180 line = line.strip()
181 if not line or line[0] == '#':
182 continue
183
184 m = re_line.match(line)
185 if not m:
186 print "Warning: Alias file line '%s' not understood" % line
187 continue
188
189 list = alias.get(m.group(1), [])
190 for item in m.group(2).split(','):
191 item = item.strip()
192 if item:
193 list.append(item)
194 alias[m.group(1)] = list
195
196 fd.close()
197
Vikram Narayanan87d65552012-05-23 09:01:06 +0000198def CreatePatmanConfigFile(config_fname):
199 """Creates a config file under $(HOME)/.patman if it can't find one.
200
201 Args:
202 config_fname: Default config filename i.e., $(HOME)/.patman
203
204 Returns:
205 None
206 """
207 name = gitutil.GetDefaultUserName()
208 if name == None:
209 name = raw_input("Enter name: ")
210
211 email = gitutil.GetDefaultUserEmail()
212
213 if email == None:
214 email = raw_input("Enter email: ")
215
216 try:
217 f = open(config_fname, 'w')
218 except IOError:
219 print "Couldn't create patman config file\n"
220 raise
221
222 print >>f, "[alias]\nme: %s <%s>" % (name, email)
223 f.close();
224
Doug Anderson8568bae2012-12-03 14:43:17 +0000225def _UpdateDefaults(parser, config):
226 """Update the given OptionParser defaults based on config.
227
228 We'll walk through all of the settings from the parser
229 For each setting we'll look for a default in the option parser.
230 If it's found we'll update the option parser default.
231
232 The idea here is that the .patman file should be able to update
233 defaults but that command line flags should still have the final
234 say.
235
236 Args:
237 parser: An instance of an OptionParser whose defaults will be
238 updated.
Doug Andersona1dcee82012-12-03 14:43:18 +0000239 config: An instance of _ProjectConfigParser that we will query
Doug Anderson8568bae2012-12-03 14:43:17 +0000240 for settings.
241 """
242 defaults = parser.get_default_values()
243 for name, val in config.items('settings'):
244 if hasattr(defaults, name):
245 default_val = getattr(defaults, name)
246 if isinstance(default_val, bool):
247 val = config.getboolean('settings', name)
248 elif isinstance(default_val, int):
249 val = config.getint('settings', name)
250 parser.set_default(name, val)
251 else:
252 print "WARNING: Unknown setting %s" % name
253
Doug Andersona1dcee82012-12-03 14:43:18 +0000254def Setup(parser, project_name, config_fname=''):
Simon Glass0d24de92012-01-14 15:12:45 +0000255 """Set up the settings module by reading config files.
256
257 Args:
Doug Anderson8568bae2012-12-03 14:43:17 +0000258 parser: The parser to update
Doug Andersona1dcee82012-12-03 14:43:18 +0000259 project_name: Name of project that we're working on; we'll look
260 for sections named "project_section" as well.
Simon Glass0d24de92012-01-14 15:12:45 +0000261 config_fname: Config filename to read ('' for default)
262 """
Doug Andersona1dcee82012-12-03 14:43:18 +0000263 config = _ProjectConfigParser(project_name)
Simon Glass0d24de92012-01-14 15:12:45 +0000264 if config_fname == '':
Vikram Narayanan2b36c752012-05-23 08:58:58 +0000265 config_fname = '%s/.patman' % os.getenv('HOME')
Vikram Narayanan87d65552012-05-23 09:01:06 +0000266
267 if not os.path.exists(config_fname):
268 print "No config file found ~/.patman\nCreating one...\n"
269 CreatePatmanConfigFile(config_fname)
270
Doug Anderson8568bae2012-12-03 14:43:17 +0000271 config.read(config_fname)
Simon Glass0d24de92012-01-14 15:12:45 +0000272
Doug Anderson8568bae2012-12-03 14:43:17 +0000273 for name, value in config.items('alias'):
Simon Glass0d24de92012-01-14 15:12:45 +0000274 alias[name] = value.split(',')
275
Doug Anderson8568bae2012-12-03 14:43:17 +0000276 _UpdateDefaults(parser, config)
Simon Glass0d24de92012-01-14 15:12:45 +0000277
278# These are the aliases we understand, indexed by alias. Each member is a list.
279alias = {}
Doug Andersona1dcee82012-12-03 14:43:18 +0000280
281if __name__ == "__main__":
282 import doctest
283
284 doctest.testmod()