build/unix/build-clang/tooltool.py

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

michael@0 1 #!/usr/bin/env python
michael@0 2
michael@0 3 #tooltool is a lookaside cache implemented in Python
michael@0 4 #Copyright (C) 2011 John H. Ford <john@johnford.info>
michael@0 5 #
michael@0 6 #This program is free software; you can redistribute it and/or
michael@0 7 #modify it under the terms of the GNU General Public License
michael@0 8 #as published by the Free Software Foundation version 2
michael@0 9 #
michael@0 10 #This program is distributed in the hope that it will be useful,
michael@0 11 #but WITHOUT ANY WARRANTY; without even the implied warranty of
michael@0 12 #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
michael@0 13 #GNU General Public License for more details.
michael@0 14 #
michael@0 15 #You should have received a copy of the GNU General Public License
michael@0 16 #along with this program; if not, write to the Free Software
michael@0 17 #Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
michael@0 18
michael@0 19 # An manifest file specifies files in that directory that are stored
michael@0 20 # elsewhere. This file should only contain file in the directory
michael@0 21 # which the manifest file resides in and it should be called 'manifest.manifest'
michael@0 22
michael@0 23 __version__ = '1'
michael@0 24
michael@0 25 import json
michael@0 26 import os
michael@0 27 import optparse
michael@0 28 import logging
michael@0 29 import hashlib
michael@0 30 import urllib2
michael@0 31 import ConfigParser
michael@0 32
michael@0 33 log = logging.getLogger(__name__)
michael@0 34
michael@0 35 class FileRecordJSONEncoderException(Exception): pass
michael@0 36 class InvalidManifest(Exception): pass
michael@0 37 class ExceptionWithFilename(Exception):
michael@0 38 def __init__(self, filename):
michael@0 39 Exception.__init__(self)
michael@0 40 self.filename = filename
michael@0 41
michael@0 42 class DigestMismatchException(ExceptionWithFilename): pass
michael@0 43 class MissingFileException(ExceptionWithFilename): pass
michael@0 44
michael@0 45 class FileRecord(object):
michael@0 46 def __init__(self, filename, size, digest, algorithm):
michael@0 47 object.__init__(self)
michael@0 48 self.filename = filename
michael@0 49 self.size = size
michael@0 50 self.digest = digest
michael@0 51 self.algorithm = algorithm
michael@0 52 log.debug("creating %s 0x%x" % (self.__class__.__name__, id(self)))
michael@0 53
michael@0 54 def __eq__(self, other):
michael@0 55 if self is other:
michael@0 56 return True
michael@0 57 if self.filename == other.filename and \
michael@0 58 self.size == other.size and \
michael@0 59 self.digest == other.digest and \
michael@0 60 self.algorithm == other.algorithm:
michael@0 61 return True
michael@0 62 else:
michael@0 63 return False
michael@0 64
michael@0 65 def __ne__(self, other):
michael@0 66 return not self.__eq__(other)
michael@0 67
michael@0 68 def __str__(self):
michael@0 69 return repr(self)
michael@0 70
michael@0 71 def __repr__(self):
michael@0 72 return "%s.%s(filename='%s', size='%s', digest='%s', algorithm='%s')" % (__name__,
michael@0 73 self.__class__.__name__,
michael@0 74 self.filename, self.size, self.digest, self.algorithm)
michael@0 75
michael@0 76 def present(self):
michael@0 77 # Doesn't check validity
michael@0 78 return os.path.exists(self.filename)
michael@0 79
michael@0 80 def validate_size(self):
michael@0 81 if self.present():
michael@0 82 return self.size == os.path.getsize(self.filename)
michael@0 83 else:
michael@0 84 log.debug("trying to validate size on a missing file, %s", self.filename)
michael@0 85 raise MissingFileException(filename=self.filename)
michael@0 86
michael@0 87 def validate_digest(self):
michael@0 88 if self.present():
michael@0 89 with open(self.filename, 'rb') as f:
michael@0 90 return self.digest == digest_file(f, self.algorithm)
michael@0 91 else:
michael@0 92 log.debug("trying to validate digest on a missing file, %s', self.filename")
michael@0 93 raise MissingFileException(filename=self.filename)
michael@0 94
michael@0 95 def validate(self):
michael@0 96 if self.validate_size():
michael@0 97 if self.validate_digest():
michael@0 98 return True
michael@0 99 return False
michael@0 100
michael@0 101 def describe(self):
michael@0 102 if self.present() and self.validate():
michael@0 103 return "'%s' is present and valid" % self.filename
michael@0 104 elif self.present():
michael@0 105 return "'%s' is present and invalid" % self.filename
michael@0 106 else:
michael@0 107 return "'%s' is absent" % self.filename
michael@0 108
michael@0 109
michael@0 110 def create_file_record(filename, algorithm):
michael@0 111 fo = open(filename, 'rb')
michael@0 112 stored_filename = os.path.split(filename)[1]
michael@0 113 fr = FileRecord(stored_filename, os.path.getsize(filename), digest_file(fo, algorithm), algorithm)
michael@0 114 fo.close()
michael@0 115 return fr
michael@0 116
michael@0 117
michael@0 118 class FileRecordJSONEncoder(json.JSONEncoder):
michael@0 119 def encode_file_record(self, obj):
michael@0 120 if not issubclass(type(obj), FileRecord):
michael@0 121 err = "FileRecordJSONEncoder is only for FileRecord and lists of FileRecords, not %s" % obj.__class__.__name__
michael@0 122 log.warn(err)
michael@0 123 raise FileRecordJSONEncoderException(err)
michael@0 124 else:
michael@0 125 return {'filename': obj.filename, 'size': obj.size, 'algorithm': obj.algorithm, 'digest': obj.digest}
michael@0 126
michael@0 127 def default(self, f):
michael@0 128 if issubclass(type(f), list):
michael@0 129 record_list = []
michael@0 130 for i in f:
michael@0 131 record_list.append(self.encode_file_record(i))
michael@0 132 return record_list
michael@0 133 else:
michael@0 134 return self.encode_file_record(f)
michael@0 135
michael@0 136
michael@0 137 class FileRecordJSONDecoder(json.JSONDecoder):
michael@0 138 """I help the json module materialize a FileRecord from
michael@0 139 a JSON file. I understand FileRecords and lists of
michael@0 140 FileRecords. I ignore things that I don't expect for now"""
michael@0 141 # TODO: make this more explicit in what it's looking for
michael@0 142 # and error out on unexpected things
michael@0 143 def process_file_records(self, obj):
michael@0 144 if isinstance(obj, list):
michael@0 145 record_list = []
michael@0 146 for i in obj:
michael@0 147 record = self.process_file_records(i)
michael@0 148 if issubclass(type(record), FileRecord):
michael@0 149 record_list.append(record)
michael@0 150 return record_list
michael@0 151 if isinstance(obj, dict) and \
michael@0 152 len(obj.keys()) == 4 and \
michael@0 153 obj.has_key('filename') and \
michael@0 154 obj.has_key('size') and \
michael@0 155 obj.has_key('algorithm') and \
michael@0 156 obj.has_key('digest'):
michael@0 157 rv = FileRecord(obj['filename'], obj['size'], obj['digest'], obj['algorithm'])
michael@0 158 log.debug("materialized %s" % rv)
michael@0 159 return rv
michael@0 160 return obj
michael@0 161
michael@0 162 def decode(self, s):
michael@0 163 decoded = json.JSONDecoder.decode(self, s)
michael@0 164 rv = self.process_file_records(decoded)
michael@0 165 return rv
michael@0 166
michael@0 167
michael@0 168 class Manifest(object):
michael@0 169
michael@0 170 valid_formats = ('json',)
michael@0 171
michael@0 172 def __init__(self, file_records=[]):
michael@0 173 self.file_records = file_records
michael@0 174
michael@0 175 def __eq__(self, other):
michael@0 176 if self is other:
michael@0 177 return True
michael@0 178 if len(self.file_records) != len(other.file_records):
michael@0 179 log.debug('Manifests differ in number of files')
michael@0 180 return False
michael@0 181 #TODO: Lists in a different order should be equal
michael@0 182 for record in range(0,len(self.file_records)):
michael@0 183 if self.file_records[record] != other.file_records[record]:
michael@0 184 log.debug('FileRecords differ, %s vs %s' % (self.file_records[record],
michael@0 185 other.file_records[record]))
michael@0 186 return False
michael@0 187 return True
michael@0 188
michael@0 189 def __deepcopy__(self, memo):
michael@0 190 # This is required for a deep copy
michael@0 191 return Manifest(self.file_records[:])
michael@0 192
michael@0 193 def __copy__(self):
michael@0 194 return Manifest(self.file_records)
michael@0 195
michael@0 196 def copy(self):
michael@0 197 return Manifest(self.file_records[:])
michael@0 198
michael@0 199 def present(self):
michael@0 200 return all(i.present() for i in self.file_records)
michael@0 201
michael@0 202 def validate_sizes(self):
michael@0 203 return all(i.validate_size() for i in self.file_records)
michael@0 204
michael@0 205 def validate_digests(self):
michael@0 206 return all(i.validate_digest() for i in self.file_records)
michael@0 207
michael@0 208 def validate(self):
michael@0 209 return all(i.validate() for i in self.file_records)
michael@0 210
michael@0 211 def sort(self):
michael@0 212 #TODO: WRITE TESTS
michael@0 213 self.file_records.sort(key=lambda x: x.size)
michael@0 214
michael@0 215 def load(self, data_file, fmt='json'):
michael@0 216 assert fmt in self.valid_formats
michael@0 217 if fmt == 'json':
michael@0 218 try:
michael@0 219 self.file_records.extend(json.load(data_file, cls=FileRecordJSONDecoder))
michael@0 220 self.sort()
michael@0 221 except ValueError:
michael@0 222 raise InvalidManifest("trying to read invalid manifest file")
michael@0 223
michael@0 224 def loads(self, data_string, fmt='json'):
michael@0 225 assert fmt in self.valid_formats
michael@0 226 if fmt == 'json':
michael@0 227 try:
michael@0 228 self.file_records.extend(json.loads(data_string, cls=FileRecordJSONDecoder))
michael@0 229 self.sort()
michael@0 230 except ValueError:
michael@0 231 raise InvalidManifest("trying to read invalid manifest file")
michael@0 232
michael@0 233 def dump(self, output_file, fmt='json'):
michael@0 234 assert fmt in self.valid_formats
michael@0 235 self.sort()
michael@0 236 if fmt == 'json':
michael@0 237 rv = json.dump(self.file_records, output_file, indent=0, cls=FileRecordJSONEncoder)
michael@0 238 print >> output_file, ''
michael@0 239 return rv
michael@0 240
michael@0 241 def dumps(self, fmt='json'):
michael@0 242 assert fmt in self.valid_formats
michael@0 243 self.sort()
michael@0 244 if fmt == 'json':
michael@0 245 return json.dumps(self.file_records, cls=FileRecordJSONEncoder)
michael@0 246
michael@0 247
michael@0 248 def digest_file(f, a):
michael@0 249 """I take a file like object 'f' and return a hex-string containing
michael@0 250 of the result of the algorithm 'a' applied to 'f'."""
michael@0 251 h = hashlib.new(a)
michael@0 252 chunk_size = 1024*10
michael@0 253 data = f.read(chunk_size)
michael@0 254 while data:
michael@0 255 h.update(data)
michael@0 256 data = f.read(chunk_size)
michael@0 257 if hasattr(f, 'name'):
michael@0 258 log.debug('hashed %s with %s to be %s', f.name, a, h.hexdigest())
michael@0 259 else:
michael@0 260 log.debug('hashed a file with %s to be %s', a, h.hexdigest())
michael@0 261 return h.hexdigest()
michael@0 262
michael@0 263 # TODO: write tests for this function
michael@0 264 def open_manifest(manifest_file):
michael@0 265 """I know how to take a filename and load it into a Manifest object"""
michael@0 266 if os.path.exists(manifest_file):
michael@0 267 manifest = Manifest()
michael@0 268 with open(manifest_file) as f:
michael@0 269 manifest.load(f)
michael@0 270 log.debug("loaded manifest from file '%s'" % manifest_file)
michael@0 271 return manifest
michael@0 272 else:
michael@0 273 log.debug("tried to load absent file '%s' as manifest" % manifest_file)
michael@0 274 raise InvalidManifest("manifest file '%s' does not exist" % manifest_file)
michael@0 275
michael@0 276 # TODO: write tests for this function
michael@0 277 def list_manifest(manifest_file):
michael@0 278 """I know how print all the files in a location"""
michael@0 279 try:
michael@0 280 manifest = open_manifest(manifest_file)
michael@0 281 except InvalidManifest:
michael@0 282 log.error("failed to load manifest file at '%s'" % manifest_file)
michael@0 283 return False
michael@0 284 for f in manifest.file_records:
michael@0 285 print "%s\t%s\t%s" % ("P" if f.present() else "-",
michael@0 286 "V" if f.present() and f.validate() else "-",
michael@0 287 f.filename)
michael@0 288 return True
michael@0 289
michael@0 290 def validate_manifest(manifest_file):
michael@0 291 """I validate that all files in a manifest are present and valid but
michael@0 292 don't fetch or delete them if they aren't"""
michael@0 293 try:
michael@0 294 manifest = open_manifest(manifest_file)
michael@0 295 except InvalidManifest:
michael@0 296 log.error("failed to load manifest file at '%s'" % manifest_file)
michael@0 297 return False
michael@0 298 invalid_files = []
michael@0 299 absent_files = []
michael@0 300 for f in manifest.file_records:
michael@0 301 if not f.present():
michael@0 302 absent_files.append(f)
michael@0 303 else:
michael@0 304 if not f.validate():
michael@0 305 invalid_files.append(f)
michael@0 306 if len(invalid_files + absent_files) == 0:
michael@0 307 return True
michael@0 308 else:
michael@0 309 return False
michael@0 310
michael@0 311 # TODO: write tests for this function
michael@0 312 def add_files(manifest_file, algorithm, filenames):
michael@0 313 # returns True if all files successfully added, False if not
michael@0 314 # and doesn't catch library Exceptions. If any files are already
michael@0 315 # tracked in the manifest, return will be False because they weren't
michael@0 316 # added
michael@0 317 all_files_added = True
michael@0 318 # Create a old_manifest object to add to
michael@0 319 if os.path.exists(manifest_file):
michael@0 320 old_manifest = open_manifest(manifest_file)
michael@0 321 else:
michael@0 322 old_manifest = Manifest()
michael@0 323 log.debug("creating a new manifest file")
michael@0 324 new_manifest = Manifest() # use a different manifest for the output
michael@0 325 for filename in filenames:
michael@0 326 log.debug("adding %s" % filename)
michael@0 327 path, name = os.path.split(filename)
michael@0 328 new_fr = create_file_record(filename, algorithm)
michael@0 329 log.debug("appending a new file record to manifest file")
michael@0 330 add = True
michael@0 331 for fr in old_manifest.file_records:
michael@0 332 log.debug("manifest file has '%s'" % "', ".join([x.filename for x in old_manifest.file_records]))
michael@0 333 if new_fr == fr and new_fr.validate():
michael@0 334 # TODO: Decide if this case should really cause a False return
michael@0 335 log.info("file already in old_manifest file and matches")
michael@0 336 add = False
michael@0 337 elif new_fr == fr and not new_fr.validate():
michael@0 338 log.error("file already in old_manifest file but is invalid")
michael@0 339 add = False
michael@0 340 if filename == fr.filename:
michael@0 341 log.error("manifest already contains file named %s" % filename)
michael@0 342 add = False
michael@0 343 if add:
michael@0 344 new_manifest.file_records.append(new_fr)
michael@0 345 log.debug("added '%s' to manifest" % filename)
michael@0 346 else:
michael@0 347 all_files_added = False
michael@0 348 with open(manifest_file, 'wb') as output:
michael@0 349 new_manifest.dump(output, fmt='json')
michael@0 350 return all_files_added
michael@0 351
michael@0 352
michael@0 353 # TODO: write tests for this function
michael@0 354 def fetch_file(base_url, file_record, overwrite=False, grabchunk=1024*4):
michael@0 355 # A file which is requested to be fetched that exists locally will be hashed.
michael@0 356 # If the hash matches the requested file's hash, nothing will be done and the
michael@0 357 # function will return. If the function is told to overwrite and there is a
michael@0 358 # digest mismatch, the exiting file will be overwritten
michael@0 359 if file_record.present():
michael@0 360 if file_record.validate():
michael@0 361 log.info("existing '%s' is valid, not fetching" % file_record.filename)
michael@0 362 return True
michael@0 363 if overwrite:
michael@0 364 log.info("overwriting '%s' as requested" % file_record.filename)
michael@0 365 else:
michael@0 366 # All of the following is for a useful error message
michael@0 367 with open(file_record.filename, 'rb') as f:
michael@0 368 d = digest_file(f, file_record.algorithm)
michael@0 369 log.error("digest mismatch between manifest(%s...) and local file(%s...)" % \
michael@0 370 (file_record.digest[:8], d[:8]))
michael@0 371 log.debug("full digests: manifest (%s) local file (%s)" % (file_record.digest, d))
michael@0 372 # Let's bail!
michael@0 373 return False
michael@0 374
michael@0 375 # Generate the URL for the file on the server side
michael@0 376 url = "%s/%s/%s" % (base_url, file_record.algorithm, file_record.digest)
michael@0 377
michael@0 378 log.debug("fetching from '%s'" % url)
michael@0 379
michael@0 380 # TODO: This should be abstracted to make generic retreival protocol handling easy
michael@0 381 # Well, the file doesn't exist locally. Lets fetch it.
michael@0 382 try:
michael@0 383 f = urllib2.urlopen(url)
michael@0 384 log.debug("opened %s for reading" % url)
michael@0 385 with open(file_record.filename, 'wb') as out:
michael@0 386 k = True
michael@0 387 size = 0
michael@0 388 while k:
michael@0 389 # TODO: print statistics as file transfers happen both for info and to stop
michael@0 390 # buildbot timeouts
michael@0 391 indata = f.read(grabchunk)
michael@0 392 out.write(indata)
michael@0 393 size += len(indata)
michael@0 394 if indata == '':
michael@0 395 k = False
michael@0 396 if size != file_record.size:
michael@0 397 log.error("transfer from %s to %s failed due to a difference of %d bytes" % (url,
michael@0 398 file_record.filename, file_record.size - size))
michael@0 399 return False
michael@0 400 log.info("fetched %s" % file_record.filename)
michael@0 401 except (urllib2.URLError, urllib2.HTTPError) as e:
michael@0 402 log.error("failed to fetch '%s': %s" % (file_record.filename, e),
michael@0 403 exc_info=True)
michael@0 404 return False
michael@0 405 except IOError:
michael@0 406 log.error("failed to write to '%s'" % file_record.filename,
michael@0 407 exc_info=True)
michael@0 408 return False
michael@0 409 return True
michael@0 410
michael@0 411
michael@0 412 # TODO: write tests for this function
michael@0 413 def fetch_files(manifest_file, base_url, overwrite, filenames=[]):
michael@0 414 # Lets load the manifest file
michael@0 415 try:
michael@0 416 manifest = open_manifest(manifest_file)
michael@0 417 except InvalidManifest:
michael@0 418 log.error("failed to load manifest file at '%s'" % manifest_file)
michael@0 419 return False
michael@0 420 # We want to track files that fail to be fetched as well as
michael@0 421 # files that are fetched
michael@0 422 failed_files = []
michael@0 423
michael@0 424 # Lets go through the manifest and fetch the files that we want
michael@0 425 fetched_files = []
michael@0 426 for f in manifest.file_records:
michael@0 427 if f.filename in filenames or len(filenames) == 0:
michael@0 428 log.debug("fetching %s" % f.filename)
michael@0 429 if fetch_file(base_url, f, overwrite):
michael@0 430 fetched_files.append(f)
michael@0 431 else:
michael@0 432 failed_files.append(f.filename)
michael@0 433 else:
michael@0 434 log.debug("skipping %s" % f.filename)
michael@0 435
michael@0 436 # Even if we get the file, lets ensure that it matches what the
michael@0 437 # manifest specified
michael@0 438 for localfile in fetched_files:
michael@0 439 if not localfile.validate():
michael@0 440 log.error("'%s'" % localfile.describe())
michael@0 441
michael@0 442 # If we failed to fetch or validate a file, we need to fail
michael@0 443 if len(failed_files) > 0:
michael@0 444 log.error("The following files failed: '%s'" % "', ".join(failed_files))
michael@0 445 return False
michael@0 446 return True
michael@0 447
michael@0 448
michael@0 449 # TODO: write tests for this function
michael@0 450 def process_command(options, args):
michael@0 451 """ I know how to take a list of program arguments and
michael@0 452 start doing the right thing with them"""
michael@0 453 cmd = args[0]
michael@0 454 cmd_args = args[1:]
michael@0 455 log.debug("processing '%s' command with args '%s'" % (cmd, '", "'.join(cmd_args)))
michael@0 456 log.debug("using options: %s" % options)
michael@0 457 if cmd == 'list':
michael@0 458 return list_manifest(options['manifest'])
michael@0 459 if cmd == 'validate':
michael@0 460 return validate_manifest(options['manifest'])
michael@0 461 elif cmd == 'add':
michael@0 462 return add_files(options['manifest'], options['algorithm'], cmd_args)
michael@0 463 elif cmd == 'fetch':
michael@0 464 if not options.has_key('base_url') or options.get('base_url') is None:
michael@0 465 log.critical('fetch command requires url option')
michael@0 466 return False
michael@0 467 return fetch_files(options['manifest'], options['base_url'], options['overwrite'], cmd_args)
michael@0 468 else:
michael@0 469 log.critical('command "%s" is not implemented' % cmd)
michael@0 470 return False
michael@0 471
michael@0 472 # fetching api:
michael@0 473 # http://hostname/algorithm/hash
michael@0 474 # example: http://people.mozilla.org/sha1/1234567890abcedf
michael@0 475 # This will make it possible to have the server allow clients to
michael@0 476 # use different algorithms than what was uploaded to the server
michael@0 477
michael@0 478 # TODO: Implement the following features:
michael@0 479 # -optimization: do small files first, justification is that they are faster
michael@0 480 # and cause a faster failure if they are invalid
michael@0 481 # -store permissions
michael@0 482 # -local renames i.e. call the file one thing on the server and
michael@0 483 # something different locally
michael@0 484 # -deal with the cases:
michael@0 485 # -local data matches file requested with different filename
michael@0 486 # -two different files with same name, different hash
michael@0 487 # -?only ever locally to digest as filename, symlink to real name
michael@0 488 # -?maybe deal with files as a dir of the filename with all files in that dir as the versions of that file
michael@0 489 # - e.g. ./python-2.6.7.dmg/0123456789abcdef and ./python-2.6.7.dmg/abcdef0123456789
michael@0 490
michael@0 491 def main():
michael@0 492 # Set up logging, for now just to the console
michael@0 493 ch = logging.StreamHandler()
michael@0 494 cf = logging.Formatter("%(levelname)s - %(message)s")
michael@0 495 ch.setFormatter(cf)
michael@0 496
michael@0 497 # Set up option parsing
michael@0 498 parser = optparse.OptionParser()
michael@0 499 # I wish there was a way to say "only allow args to be
michael@0 500 # sequential and at the end of the argv.
michael@0 501 # OH! i could step through sys.argv and check for things starting without -/-- before things starting with them
michael@0 502 parser.add_option('-q', '--quiet', default=False,
michael@0 503 dest='quiet', action='store_true')
michael@0 504 parser.add_option('-v', '--verbose', default=False,
michael@0 505 dest='verbose', action='store_true')
michael@0 506 parser.add_option('-m', '--manifest', default='manifest.tt',
michael@0 507 dest='manifest', action='store',
michael@0 508 help='specify the manifest file to be operated on')
michael@0 509 parser.add_option('-d', '--algorithm', default='sha512',
michael@0 510 dest='algorithm', action='store',
michael@0 511 help='openssl hashing algorithm to use')
michael@0 512 parser.add_option('-o', '--overwrite', default=False,
michael@0 513 dest='overwrite', action='store_true',
michael@0 514 help='if fetching, remote copy will overwrite a local copy that is different. ')
michael@0 515 parser.add_option('--url', dest='base_url', action='store',
michael@0 516 help='base url for fetching files')
michael@0 517 parser.add_option('--ignore-config-files', action='store_true', default=False,
michael@0 518 dest='ignore_cfg_files')
michael@0 519 (options_obj, args) = parser.parse_args()
michael@0 520 # Dictionaries are easier to work with
michael@0 521 options = vars(options_obj)
michael@0 522
michael@0 523
michael@0 524 # Use some of the option parser to figure out application
michael@0 525 # log level
michael@0 526 if options.get('verbose'):
michael@0 527 ch.setLevel(logging.DEBUG)
michael@0 528 elif options.get('quiet'):
michael@0 529 ch.setLevel(logging.ERROR)
michael@0 530 else:
michael@0 531 ch.setLevel(logging.INFO)
michael@0 532 log.addHandler(ch)
michael@0 533
michael@0 534 cfg_file = ConfigParser.SafeConfigParser()
michael@0 535 if not options.get("ignore_cfg_files"):
michael@0 536 read_files = cfg_file.read(['/etc/tooltool', os.path.expanduser('~/.tooltool'),
michael@0 537 os.path.join(os.getcwd(), '.tooltool')])
michael@0 538 log.debug("read in the config files '%s'" % '", '.join(read_files))
michael@0 539 else:
michael@0 540 log.debug("skipping config files")
michael@0 541
michael@0 542 for option in ('base_url', 'algorithm'):
michael@0 543 if not options.get(option):
michael@0 544 try:
michael@0 545 options[option] = cfg_file.get('general', option)
michael@0 546 log.debug("read '%s' as '%s' from cfg_file" % (option, options[option]))
michael@0 547 except (ConfigParser.NoSectionError, ConfigParser.NoOptionError) as e:
michael@0 548 log.debug("%s in config file" % e, exc_info=True)
michael@0 549
michael@0 550 if not options.has_key('manifest'):
michael@0 551 parser.error("no manifest file specified")
michael@0 552
michael@0 553 if len(args) < 1:
michael@0 554 parser.error('You must specify a command')
michael@0 555 exit(0 if process_command(options, args) else 1)
michael@0 556
michael@0 557 if __name__ == "__main__":
michael@0 558 main()
michael@0 559 else:
michael@0 560 log.addHandler(logging.NullHandler())
michael@0 561 #log.addHandler(logging.StreamHandler())

mercurial