Merge with Pawel's head.
[Melange.git] / scripts / settings.py
blob144156df7df53260a890f7b43b97a05a31ac6b07
1 #!/usr/bin/python2.5
3 # Copyright 2008 the Melange authors.
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
17 """Custom optparse OptionParser and functions for reading Python settings files.
19 Default values for trunk/scripts flags can be specified in valid Python syntax
20 in the ~/.soc_scripts_settings file. For example, a default value for the
21 --user flag can be specified with a variable assignment in the settings file
22 like:
24 user = 'joeuser'
26 Defaults in the ~/.soc_scripts_settings file can be explicitly overridden by
27 supplied the actual flag. For example, supplying:
29 --user=someotheruser
31 would override the default value present in the settings file.
33 Option: class derived from optparse.Option that adds a 'required' parameter
34 OptionParser: class derived from optparse.OptionParser for use with Option
36 readPythonSettings(): interprets a valid Python file as a settings file
37 """
39 __authors__ = [
40 # alphabetical order by last name, please
41 '"Todd Larsen" <tlarsen@google.com>',
45 import os
46 import optparse
47 import sys
50 DEF_SETTINGS_FILE_DIR = "~"
51 DEF_SETTINGS_FILE_NAME = '.soc_scripts_settings'
54 class Error(Exception):
55 """Base exception class for all exceptions in the settings module."""
56 pass
59 class Option(optparse.Option):
60 """Class derived from optparse.Option that adds a 'required' parameter."""
62 ATTRS = optparse.Option.ATTRS + ['required']
64 def _check_required(self):
65 """Insures that 'required' option can accept a value."""
66 if self.required and (not self.takes_value()):
67 raise optparse.OptionError(
68 "'required' parameter set for option that does not take a value",
69 self)
71 # Make sure _check_required() is called from the constructor!
72 CHECK_METHODS = optparse.Option.CHECK_METHODS + [_check_required]
74 def process(self, opt, value, values, parser):
75 optparse.Option.process(self, opt, value, values, parser)
76 parser.option_seen[self] = 1
79 class OptionParser(optparse.OptionParser):
80 """Class derived from optparse.OptionParser for use with Option."""
82 def _init_parsing_state(self):
83 """Sets up dict to track options seen so far."""
84 optparse.OptionParser._init_parsing_state(self)
85 self.option_seen = {}
87 def error(self, *args):
88 """Convert errors reported by optparse.OptionParser to Error exceptions.
90 Args:
91 *args: passed through to the Error exception __init__() constructor,
92 usually a list of strings
94 Raises:
95 Error with the supplied *args
96 """
97 raise Error(*args)
99 def check_values(self, values, args):
100 """Checks to make sure all required=True options were supplied.
102 Args:
103 values, args: passed through unchanged (see Returns:)
105 Returns:
106 (values, args) unchanged.
108 Raises:
109 Error if an option was not supplied that had required=True; exception
110 positional arguments are the error message strings.
112 errors = []
114 for option in self.option_list:
115 if (isinstance(option, Option)
116 and option.required
117 and (not self.option_seen.has_key(option))):
118 errors.append(
119 'required %s option not supplied'
120 ' (and default settings not allowed)' % option)
122 if errors:
123 self.error(*errors)
125 return values, args
128 def printErrors(errors, exit_code=1):
129 """Prints error message strings to sys.stderr and returns an exit code.
131 Args:
132 errors: error message string or list of error message strings to be printed
133 to sys.stderr
134 exit_code: exit code to return (so that this function can be used as an
135 expression in sys.exit() for example); default is 1
137 Returns:
138 exit_code
140 sys.stderr.write('\nERRORS:\n')
142 if (not isinstance(errors, tuple)) and (not isinstance(errors, list)):
143 errors = [errors]
145 for msg in errors:
146 sys.stderr.write(' %s\n' % msg)
148 sys.stderr.write('\n')
150 return exit_code
153 def printErrorsAndUsage(errors, parser, exit_code=1):
154 """Prints error messages and usage text to sys.stderr and returns exit code.
156 Args:
157 errors: error message string or list of error message strings to be printed
158 to sys.stderr
159 parser: OptionParser with a print_help() method
160 exit_code: exit code to return (so that this function can be used as an
161 expression in sys.exit() for example); default is 1
163 Returns:
164 exit_code
166 exit_code = printErrors(errors, exit_code=exit_code)
167 parser.print_help(file=sys.stderr)
169 return exit_code
172 def getExpandedPath(path):
173 """Returns an expanded, normalized, absolute path.
175 Args:
176 path: path (possibly relative, possibly containing environment variables,
177 etc.) to be expanded, normalized and made absolute
179 Returns:
180 absolute path, after expanding any environment variables and "~", then
181 removing excess . and .. path elements
183 return os.path.abspath(
184 os.path.normpath(
185 os.path.expanduser(
186 os.path.expandvars(path))))
189 def readPythonSettings(defaults={}, # {} OK since defaults is always copied
190 settings_dir=DEF_SETTINGS_FILE_DIR,
191 settings_file=DEF_SETTINGS_FILE_NAME):
192 """Executes a Python-syntax settings file and returns the local variables.
194 Args:
195 defaults: dict of default values to use when settings are not present
196 in the settings file (or if no settings file is present at all); this
197 dict is *copied* and is not altered at all
198 settings_dir: optional directory containing settings_file
199 settings_file: optional settings file name found in settings_dir
201 Returns:
202 dict of setting name/value pairs (possibly including some values from the
203 defaults parameter). Since the settings file is full-fledged Python
204 source, the values could be any valid Python object.
206 Raises:
207 Error if some error occurred parsing the present settings file; exception
208 positional arguments are the error message strings.
210 # do not let the original defaults be altered
211 defaults = defaults.copy()
213 # form absolute path to the settings file, expanding any environment
214 # variables and "~", then removing excess . and .. path elements
215 path = getExpandedPath(os.path.join(settings_dir, settings_file))
217 # empty dict to capture the local variables in the settings file
218 settings_locals = {}
220 try:
221 # execute the Python source file and recover the local variables as settings
222 execfile(path, {}, settings_locals)
223 except IOError:
224 # If the settings file is not present, there are no defaults.
225 pass
226 except Exception, error:
227 # Other exceptions usually mean a faulty settings file.
228 raise Error(
229 'faulty settings file:',
230 (' %s: %s' % (error.__class__.__name__, str(error))),
231 (' %s' % path))
233 # overwrite defaults copy with values from the (possibly empty) settings file
234 defaults.update(settings_locals)
236 return defaults
239 def readPythonSettingsOrDie(parser=None, **kwargs):
240 """Calls readPythonSettings(), calling sys.exit() on any errors.
242 Args:
243 parser: if supplied, an OptionParser instance used to call print_help()
244 to print usage information if errors occur
245 **kwargs: see readPythonSettings()
247 Returns:
248 On success, returns results of readPythonSettings().
250 Exits:
251 On any error from readPythonSettings(), prints error messages to stderr,
252 possibly prints usage information, and calls sys.exit(1).
254 try:
255 return readPythonSettings(**kwargs)
256 except Error, error:
257 if parser:
258 sys.exit(printErrorsAndUsage(error.args, parser))
259 else:
260 sys.exit(printErrors(error.args))
263 def makeOptionParserOrDie(*args, **kwargs):
264 """Creates and returns an OptionParser, calling sys.exit() on any errors.
266 Args:
267 *args, **kwargs: supplied directly to OptionParser constructor
269 Returns:
270 On success, returns an OptionParser instance.
272 Exits:
273 On any error, prints error messages to stderr and calls sys.exit(1).
275 try:
276 return OptionParser(*args, **kwargs)
277 except Error, error:
278 sys.exit(printErrors(error.args))
281 def parseOptionsOrDie(parser, args):
282 """Parses command-line options, calling sys.exit() on any errors.
284 Args:
285 parser: an OptionParser instance
286 args: list of command-line arguments to supply to parser
288 Returns:
289 On success, returns (options, args) returned by parser.parse_args(args).
291 Exits:
292 On any error, prints error messages and usage information to stderr and
293 calls sys.exit(1).
295 try:
296 return parser.parse_args(args)
297 except Error, error:
298 sys.exit(printErrorsAndUsage(error.args, parser))
301 def checkCommonSvnOptions(options):
302 """Checks a common subset of command-line options.
304 Multiple scripts accept a subset of common command-line options. This
305 function does some sanity checks on these flags. These checks are collected
306 here because they were being duplicated in multiple scripts.
308 Args:
309 options: OptionParser.parse_args() options instance to check
311 Returns:
312 list of error message strings, or an empty list if no errors
314 errors = []
316 if not options.repo:
317 errors.extend(
318 ['--repo must be supplied or have a settings file default'])
320 if not options.wc:
321 errors.extend(
322 ['--wc must be supplied or have a settings file default'])
324 if not options.branch:
325 if not options.user:
326 errors.extend(
327 ['at least one of --branch or --user must be supplied'])
329 return errors
332 def checkCommonSvnOptionsOrDie(options, parser):
333 """Checks subset of command-line options, calling sys.exit() on any errors.
335 Args:
336 options: see checkCommonSvnOptions()
337 parser: an OptionParser instance used to call print_help() to print
338 usage information if errors occur
340 Exits:
341 On any error messages returned by checkCommonSvnOptions(), prints error
342 messages and usage information to stderr and calls sys.exit(1).
344 errors = checkCommonSvnOptions(options)
346 if errors:
347 sys.exit(printErrorsAndUsage(errors, parser))