Package qm :: Module common
[hide private]
[frames] | no frames]

Source Code for Module qm.common

  1  ######################################################################## 
  2  # 
  3  # File:   common.py 
  4  # Author: Alex Samuel 
  5  # Date:   2000-12-20 
  6  # 
  7  # Contents: 
  8  #   General-purpose classes and functions. 
  9  # 
 10  # Copyright (c) 2000, 2001, 2002, 2003 by CodeSourcery, LLC.  All rights reserved.  
 11  # 
 12  # For license terms see the file COPYING. 
 13  # 
 14  ######################################################################## 
 15   
 16  ######################################################################## 
 17  # imports 
 18  ######################################################################## 
 19   
 20  from   calendar import timegm 
 21  import ConfigParser 
 22  import imp 
 23  import lock 
 24  import os 
 25  import os.path 
 26  import qm 
 27  import re 
 28  import string 
 29  import sys 
 30  import tempfile 
 31  import time 
 32  import traceback 
 33  import types 
 34  import getpass 
 35  import StringIO 
 36  import htmllib 
 37  import formatter 
 38  if sys.platform != "win32": 
 39      import fcntl 
 40       
 41  ######################################################################## 
 42  # program name 
 43  ######################################################################## 
 44   
 45  program_name = None 
 46  """The name of the application program.""" 
 47   
 48  ######################################################################## 
 49  # exceptions 
 50  ######################################################################## 
 51   
52 -class QMException(Exception):
53 """An exception generated directly by QM. 54 55 All exceptions thrown by QM should be derived from this class.""" 56
57 - def __init__(self, message):
58 """Construct a new 'QMException'. 59 60 'message' -- A string describing the cause of the message as 61 structured text. If this exception is not handled, the 62 'message' will be displayed as an error message.""" 63 64 Exception.__init__(self, message)
65
66 -class Timeout(QMException):
67 68 pass
69 70
71 -class UserError(QMException):
72 73 pass
74 75 76
77 -class PythonException(QMException):
78 """A 'PythonException' is a wrapper around a Python exception. 79 80 A 'PythonException' is a 'QMException' and, as such, can be 81 processed by the QM error-handling routines. However, the raw 82 Python exception which triggered this exception can be obtained by 83 using the 'exc_type' and 'exc_value' attributes of this 84 exception.""" 85
86 - def __init__(self, message, exc_type, exc_value):
87 """Construct a new 'PythonException'. 88 89 'message' -- A string describing the cause of the message as 90 structured text. If this exception is not handled, the 91 'message' will be displayed as an error message. 92 93 'exc_type' -- The type of the Python exception. 94 95 'exc_value' -- The value of the Python exception.""" 96 97 QMException.__init__(self, message) 98 99 self.exc_type = exc_type 100 self.exc_value = exc_value
101 102 ######################################################################## 103 # classes 104 ######################################################################## 105
106 -class RcConfiguration(ConfigParser.ConfigParser):
107 """Interface object to QM configuration files. 108 109 Configuration files are in the format parsed by the standard 110 'ConfigParser' module, namely 'win.ini'--style files.""" 111 112 user_rc_file_name = ".qmrc" 113 """The name of the user configuration file.""" 114 115
116 - def __init__(self):
117 """Create a new configuration instance.""" 118 119 ConfigParser.ConfigParser.__init__(self) 120 if os.environ.has_key("HOME"): 121 home_directory = os.environ["HOME"] 122 rc_file = os.path.join(home_directory, self.user_rc_file_name) 123 # Note that it's OK to call 'read' even if the file doesn't 124 # exist. In that, case the parser simply will not 125 # accumulate any data. 126 self.read(rc_file)
127 128
129 - def Load(self, section):
130 """Load configuration. 131 132 'section' -- The configuration section from which subsequent 133 variables are loaded.""" 134 135 self.__section = section
136 137
138 - def Get(self, option, default, section=None):
139 """Retrieve a configuration variable. 140 141 'option' -- The name of the option to retrieve. 142 143 'default' -- The default value to return if the option is not 144 found. 145 146 'section' -- The section from which to retrieve the option. 147 'None' indicates the section specified to the 'Load' method for 148 this instance. 149 150 precondition -- The RC configuration must be loaded.""" 151 152 # Use the previously-specified default section, if one wasn't 153 # specified explicitly. 154 if section is None: 155 section = self.__section 156 157 try: 158 # Try to get the requested option. 159 return self.get(section, option) 160 except ConfigParser.NoSectionError: 161 # Couldn't find the section. 162 return default 163 except ConfigParser.NoOptionError: 164 # Couldn't find the option. 165 return default
166 167
168 - def GetOptions(self, section=None):
169 """Return a sequence of options. 170 171 'section' -- The section for which to list options, or 'None' 172 for the section specified to 'Load'. 173 174 precondition -- The RC configuration must be loaded.""" 175 176 # Use the previously-specified default section, if one wasn't 177 # specified explicitly. 178 if section is None: 179 section = self.__section 180 try: 181 options = self.options(section) 182 except ConfigParser.NoSectionError: 183 # Couldn't find the section. 184 return [] 185 else: 186 # 'ConfigParser' puts in a magic option '__name__', which is 187 # the name of the section. Remove it. 188 if "__name__" in options: 189 options.remove("__name__") 190 return options
191 192 193 ######################################################################## 194 # Functions 195 ######################################################################## 196
197 -def get_lib_directory(*components):
198 """Return the path to a file in the QM library directory.""" 199 200 # This is a file in the top-level QM library directory, so we can 201 # just return a path relative to ourselves. 202 return os.path.join(os.path.dirname(__file__), *components)
203 204
205 -def get_share_directory(*components):
206 """Return the path to a file in the QM data file directory.""" 207 208 return os.path.join(qm.prefix, qm.data_dir, *components)
209 210
211 -def get_doc_directory(*components):
212 """Return a path to a file in the QM documentation file directory.""" 213 214 return os.path.join(qm.prefix, qm.doc_dir, *components)
215 216
217 -def format_exception(exc_info):
218 """Format an exception as structured text. 219 220 'exc_info' -- A three-element tuple containing exception info, of 221 the form '(type, value, traceback)'. 222 223 returns -- A string containing a the formatted exception.""" 224 225 # Break up the exection info tuple. 226 type, value, trace = exc_info 227 # Generate output. 228 traceback_listing = format_traceback(exc_info) 229 return "Exception '%s' : '%s'\n\n%s\n" % (type, value, traceback_listing)
230 231
232 -def format_traceback(exc_info):
233 """Format an exception traceback as structured text. 234 235 'exc_info' -- A three-element tuple containing exception info, of 236 the form '(type, value, traceback)'. 237 238 returns -- A string containing a the formatted traceback.""" 239 240 return string.join(traceback.format_tb(exc_info[2]), "\n")
241 242
243 -def convert_from_dos_text(text):
244 """Replace CRLF with LF in 'text'.""" 245 246 return string.replace(text, "\r\n", "\n")
247 248 249 __load_module_lock = lock.RLock() 250 """A lock used by load_module.""" 251
252 -def load_module(name, search_path=sys.path, load_path=sys.path):
253 """Load a Python module. 254 255 'name' -- The fully-qualified name of the module to load, for 256 instance 'package.subpackage.module'. 257 258 'search_path' -- A sequence of directories. These directories are 259 searched to find the module. 260 261 'load_path' -- The setting of 'sys.path' when the module is loaded. 262 263 returns -- A module object. 264 265 raises -- 'ImportError' if the module cannot be found.""" 266 267 # The implementation of this function follows the prescription in 268 # the documentation for the standard Python 'imp' module. See also 269 # the 'knee' package included unofficially in the standard Python 270 # library. 271 272 # In order to avoid getting incomplete modules, grab a lock here. 273 # Use a recursive lock so that deadlock does not occur if the loaded 274 # module loads more modules. 275 __load_module_lock.acquire() 276 try: 277 # Is the module already loaded? 278 module = sys.modules.get(name) 279 if module: 280 return module 281 282 # The module may be in a package. Split the module path into 283 # components. 284 components = string.split(name, ".") 285 if len(components) > 1: 286 # The module is in a package. Construct the name of the 287 # containing package. 288 parent_package = string.join(components[:-1], ".") 289 # Load the containing package. 290 package = load_module(parent_package, search_path, load_path) 291 # Look for the module in the parent package. 292 search_path = package.__path__ 293 else: 294 # No containing package. 295 package = None 296 # The name of the module itself is the last component of the module 297 # path. 298 module_name = components[-1] 299 # Locate the module. 300 file, file_name, description = imp.find_module(module_name, 301 search_path) 302 # Find the module. 303 try: 304 # While loading the module, add 'path' to Python's module path, 305 # so that if the module references other modules, e.g. in the 306 # same directory, Python can find them. But remember the old 307 # path so we can restore it afterwards. 308 old_python_path = sys.path[:] 309 sys.path = load_path + sys.path 310 # Load the module. 311 try: 312 module = imp.load_module(name, file, file_name, description) 313 except: 314 # Don't leave a broken module object in sys.modules. 315 if sys.modules.has_key(name): 316 del sys.modules[name] 317 raise 318 # Restore the old path. 319 sys.path = old_python_path 320 # Loaded successfully. If it's contained in a package, put it 321 # into that package's name space. 322 if package is not None: 323 setattr(package, module_name, module) 324 return module 325 finally: 326 # Close the module file, if one was opened. 327 if file is not None: 328 file.close() 329 finally: 330 # Release the lock. 331 __load_module_lock.release()
332 333
334 -def load_class(name, search_path = sys.path, load_path = sys.path):
335 """Load a Python class. 336 337 'name' -- The fully-qualified (including package and module names) 338 class name, for instance 'package.subpackage.module.MyClass'. The 339 class must be at the top level of the module's namespace, i.e. not 340 nested in another class. 341 342 'search_path' -- A sequence of directories. These directories are 343 searched to find the module. 344 345 'load_path' -- The setting of 'sys.path' when the module is loaded. 346 347 returns -- A class object. 348 349 raises -- 'ImportError' if the module containing the class can't be 350 imported, or if there is no class with the specified name in that 351 module, or if 'name' doesn't correspond to a class.""" 352 353 # Make sure the class name is fully-qualified. It must at least be 354 # in a top-level module, so there should be at least one module path 355 # separator. 356 if not "." in name: 357 raise QMException, \ 358 "%s is not a fully-qualified class name" % name 359 # Split the module path into components. 360 components = string.split(name, ".") 361 # Reconstruct the full path to the containing module. 362 module_name = string.join(components[:-1], ".") 363 # The last element is the name of the class. 364 class_name = components[-1] 365 # Load the containing module. 366 module = load_module(module_name, search_path, load_path) 367 # Extract the requested class. 368 try: 369 klass = module.__dict__[class_name] 370 # Check to see the KLASS really is a class. Python 2.2's 371 # "new-style" classes are not instances of types.ClassType so we 372 # must check two conditions: one for old-style and one for 373 # new-style classes. 374 if (not isinstance(klass, types.ClassType) 375 and not issubclass(klass, object)): 376 # There's something by that name, but it's not a class 377 raise QMException, "%s is not a class" % name 378 return klass 379 except KeyError: 380 # There's no class with the requested name. 381 raise QMException, \ 382 "no class named %s in module %s" % (class_name, module_name)
383 384
385 -def split_path_fully(path):
386 """Split 'path' into components. 387 388 Uses 'os.path.split' recursively on the directory components of 389 'path' to separate all path components. 390 391 'path' -- The path to split. 392 393 returns -- A list of path componets.""" 394 395 dir, entry = os.path.split(path) 396 if dir == "" or dir == os.sep: 397 return [ entry ] 398 else: 399 return split_path_fully(dir) + [ entry ]
400 401
402 -def open_temporary_file_fd(suffix = ""):
403 """Create and open a temporary file. 404 405 'suffix' -- The last part of the temporary file name, as for 406 Python's 'mktemp' function. 407 408 The file is open for reading and writing. The caller is responsible 409 for deleting the file when finished with it. 410 411 returns -- A pair '(file_name, file_descriptor)' for the temporary 412 file.""" 413 414 file_name = tempfile.mktemp(suffix) 415 416 try: 417 # Attempt to open the file. 418 fd = os.open(file_name, 419 os.O_CREAT | os.O_EXCL | os.O_RDWR, 420 0600) 421 except: 422 exc_info = sys.exc_info() 423 raise QMException, \ 424 qm.error("temp file error", 425 file_name=file_name, 426 exc_class=str(exc_info[0]), 427 exc_arg=str(exc_info[1])) 428 return (file_name, fd)
429 430
431 -def open_temporary_file(mode = "w+b", suffix = ""):
432 """Create and open a temporary file. 433 434 'mode' -- The mode argument to pass to 'fopen'. 435 436 'suffix' -- The last part of the temporary file name, as for 437 Python's 'mktemp' function. 438 439 Like 'open_temporary_file_fd', except that the second element of the 440 return value is a file object.""" 441 442 # This function can just use NamedTemporaryFile when QMTest requires 443 # Python 2.3. 444 file_name, fd = open_temporary_file_fd(suffix) 445 return (file_name, os.fdopen(fd, mode))
446 447
448 -def close_file_on_exec(fd):
449 """Prevent 'fd' from being inherited across 'exec'. 450 451 'fd' -- A file descriptor, or object providing a 'fileno()' 452 method. 453 454 This function has no effect on Windows.""" 455 456 if sys.platform != "win32": 457 flags = fcntl.fcntl(fd, fcntl.F_GETFD) 458 try: 459 flags |= fcntl.FD_CLOEXEC 460 except AttributeError: 461 # The Python 2.2 RPM shipped with Red Hat Linux 7.3 does 462 # not define FD_CLOEXEC. Fortunately, FD_CLOEXEC is 1 on 463 # every UNIX system. 464 flags |= 1 465 fcntl.fcntl(fd, fcntl.F_SETFD, flags)
466 467
468 -def copy(object):
469 """Make a best-effort attempt to copy 'object'. 470 471 returns -- A copy of 'object', if feasible, or otherwise 472 'object'.""" 473 474 if type(object) is types.ListType: 475 # Copy lists. 476 return object[:] 477 elif type(object) is types.DictionaryType: 478 # Copy dictionaries. 479 return object.copy() 480 elif type(object) is types.InstanceType: 481 # For objects, look for a method named 'copy'. If there is one, 482 # call it. Otherwise, just return the object. 483 copy_function = getattr(object, "copy", None) 484 if callable(copy_function): 485 return object.copy() 486 else: 487 return object 488 else: 489 # Give up. 490 return object
491 492
493 -def wrap_lines(text, columns=72, break_delimiter="\\", indent=""):
494 """Wrap lines in 'text' to 'columns' columns. 495 496 'text' -- The text to wrap. 497 498 'columns' -- The maximum number of columns of text. 499 500 'break_delimiter' -- Text to place at the end of each broken line 501 (may be an empty string). 502 503 'indent' -- Text to place at the start of each line. The length of 504 'indent' does not count towards 'columns'. 505 506 returns -- The wrapped text.""" 507 508 # Break into lines. 509 lines = string.split(text, "\n") 510 # The length into which to break lines, leaving room for the 511 # delimiter. 512 new_length = columns - len(break_delimiter) 513 # Loop over lines. 514 for index in range(0, len(lines)): 515 line = lines[index] 516 # Too long? 517 if len(line) > columns: 518 # Yes. How many times will we have to break it? 519 breaks = len(line) / new_length 520 new_line = "" 521 # Construct the new line, disassembling the old as we go. 522 while breaks > 0: 523 new_line = new_line \ 524 + line[:new_length] \ 525 + break_delimiter \ 526 + "\n" + indent 527 line = line[new_length:] 528 breaks = breaks - 1 529 new_line = new_line + line 530 # Replace the old line with the new. 531 lines[index] = new_line 532 # Indent each line. 533 lines = map(lambda l, i=indent: i + l, lines) 534 # Rejoin lines. 535 return string.join(lines, "\n")
536 537
538 -def format_time(time_secs, local_time_zone=1):
539 """Generate a text format representing a date and time. 540 541 The output is in the format "YYYY-MM-DD HH:MM ZZZ". 542 543 'time_secs' -- The number of seconds since the start of the UNIX 544 epoch, UTC. 545 546 'local_time_zone' -- If true, format the time in the local time 547 zone. Otherwise, format it as UTC.""" 548 549 # Convert the time in seconds to a Python time 9-tuple. 550 if local_time_zone: 551 time_tuple = time.localtime(time_secs) 552 time_zone = time.tzname[time_tuple[8]] 553 else: 554 time_tuple = time.gmtime(time_secs) 555 time_zone = "UTC" 556 # Unpack the tuple. 557 year, month, day, hour, minute, second, weekday, julian_day, \ 558 dst_flag = time_tuple 559 # Generate the format. 560 return "%(year)4d-%(month)02d-%(day)02d " \ 561 "%(hour)02d:%(minute)02d %(time_zone)s" % locals()
562 563
564 -def format_time_iso(time_secs=None):
565 """Generate a ISO8601-compliant formatted date and time. 566 567 The output is in the format "YYYY-MM-DDThh:mm:ss+TZ", where TZ is 568 a timezone specifier. We always normalize to UTC (and hence 569 always use the special timezone specifier "Z"), to get proper 570 sorting behaviour. 571 572 'time_secs' -- The time to be formatted, as returned by 573 e.g. 'time.time()'. If 'None' (the default), uses the current 574 time. 575 576 returns -- The formatted time as a string.""" 577 578 if time_secs is None: 579 time_secs = time.time() 580 return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime(time_secs))
581 582
583 -def parse_time_iso(time_string):
584 """Parse a ISO8601-compliant formatted date and time. 585 586 See also 'format_time_iso'. 587 588 'time_string' -- The string to be parsed, as returned by 589 e.g. 'format_time_iso'. 590 591 returns -- The time as a float, like that returned by 592 'time.time'.""" 593 594 return time.mktime(time.strptime(time_string, "%Y-%m-%dT%H:%M:%SZ"))
595 596
597 -def make_unique_tag():
598 """Return a unique tag string.""" 599 600 global _unique_tag 601 602 tag = "%d_%d" % (_unique_tag, os.getpid()) 603 _unique_tag = _unique_tag + 1 604 return tag
605 606
607 -def split_argument_list(command):
608 """Split a command into an argument list. 609 610 'command' -- A string containing a shell or similar command. 611 612 returns -- An argument list obtained by splitting the command.""" 613 614 # Strip leading and trailing whitespace. 615 command = string.strip(command) 616 # Split the command into argument list elements at spaces. 617 argument_list = re.split(" +", command) 618 return argument_list
619 620
621 -def parse_boolean(value):
622 """Parse a boolean string. 623 624 'value' -- A string. 625 626 returns -- True if 'value' is a true string, false if 'value' is a 627 false string. 628 629 raises -- 'ValueError' if 'value' is neither a true string, nor a 630 false string.""" 631 632 value = value.lower() 633 if value in ("1", "true", "yes", "on"): 634 return 1 635 elif value in ("0", "false", "no", "off"): 636 return 0 637 else: 638 raise ValueError, value
639 640
641 -def parse_string_list(value):
642 """Parse a string list. 643 644 'value' -- A string. 645 646 returns -- A list of strings. 647 648 raises -- 'ValueError' if 'value' contains unbalanced quotes.""" 649 650 # If the string doesn't contain quotes, simply split it. 651 if "'" not in value and '"' not in value: 652 return value.split() 653 # Else split it manually at non-quoted whitespace only. 654 breaks = [] 655 esc = False 656 quoted_1 = False # in '' quotes 657 quoted_2 = False # in "" quotes 658 value.strip() 659 # Find all non-quoted space. 660 for i, c in enumerate(value): 661 if c == '\\': 662 esc = not esc 663 continue 664 elif c == "'": 665 if not esc and not quoted_2: 666 quoted_1 = not quoted_1 667 elif c == '"': 668 if not esc and not quoted_1: 669 quoted_2 = not quoted_2 670 elif c in [' ', '\t']: 671 # This is a breakpoint if it is neither quoted nor escaped. 672 if not (quoted_1 or quoted_2 or esc): 673 breaks.append(i) 674 esc = False 675 # Make sure quotes are matched. 676 if quoted_1 or quoted_2 or esc: 677 raise ValueError, value 678 string_list = [] 679 start = 0 680 for end in breaks: 681 string_list.append(value[start:end]) 682 start = end 683 string_list.append(value[start:]) 684 return [s.strip() for s in string_list if s not in [' ', '\t']]
685 686 687 # No 'time.strptime' on non-UNIX systems, so use this instead. This 688 # version is more forgiving, anyway, and uses our standardized timestamp 689 # format. 690
691 -def parse_time(time_string, default_local_time_zone=1):
692 """Parse a date and/or time string. 693 694 'time_string' -- A string representing a date and time in the format 695 returned by 'format_time'. This function makes a best-effort 696 attempt to parse incomplete strings as well. 697 698 'default_local_time_zone' -- If the time zone is not specified in 699 'time_string' and this parameter is true, assume the time is in the 700 local time zone. If this parameter is false, assume the time is 701 UTC. 702 703 returns -- An integer number of seconds since the start of the UNIX 704 epoch, UTC. 705 706 Only UTC and the current local time zone may be specified explicitly 707 in 'time_string'.""" 708 709 # Sanitize. 710 time_string = string.strip(time_string) 711 time_string = re.sub(" +", " ", time_string) 712 time_string = re.sub("/", "-", time_string) 713 # On Windows, "UTC" is spelled "GMT Standard Time". Change that 714 # to "UTC" so that we can process it with the same code we use 715 # for UNIX. 716 time_string = re.sub("GMT Standard Time", "UTC", time_string) 717 # Break it apart. 718 components = string.split(time_string, " ") 719 720 # Do we have a time zone at the end? 721 if components[-1] == "UTC": 722 # It's explicitly UTC. 723 utc = 1 724 dst = 0 725 components.pop() 726 elif components[-1] == time.tzname[0]: 727 # It's explicitly our local non-DST time zone. 728 utc = 0 729 dst = 0 730 components.pop() 731 elif time.daylight and components[-1] == time.tzname[1]: 732 # It's explicitly our local DST time zone. 733 utc = 0 734 dst = 1 735 components.pop() 736 else: 737 # No explicit time zone. Use the specified default. 738 if default_local_time_zone: 739 utc = 0 740 dst = -1 741 else: 742 utc = 1 743 dst = 0 744 745 # Start with the current time, in the appropriate format. 746 if utc: 747 time_tuple = time.gmtime(time.time()) 748 else: 749 time_tuple = time.localtime(time.time()) 750 # Unpack the date tuple. 751 year, month, day = time_tuple[:3] 752 # Assume midnight. 753 hour = 0 754 minute = 0 755 756 # Look at each part of the date/time. 757 for component in components: 758 if string.count(component, "-") == 2: 759 # Looks like a date. 760 year, month, day = map(int, string.split(component, "-")) 761 elif string.count(component, ":") in [1, 2]: 762 # Looks like a time. 763 hour, minute = map(int, string.split(component, ":")[:2]) 764 else: 765 # Don't understand it. 766 raise ValueError 767 768 # Construct a Python time tuple. 769 time_tuple = (year, month, day, hour, minute, 0, 0, 0, dst) 770 # Convert it to seconds. 771 if utc: 772 return int(timegm(time_tuple)) 773 else: 774 return int(time.mktime(time_tuple))
775 776
777 -def parse_assignment(assignment):
778 """Parse an 'assignment' of the form 'name=value'. 779 780 'aassignment' -- A string. The string should have the form 781 'name=value'. 782 783 returns -- A pair '(name, value)'.""" 784 785 # Parse the assignment. 786 try: 787 (name, value) = string.split(assignment, "=", 1) 788 return (name, value) 789 except: 790 raise QMException, \ 791 qm.error("invalid keyword assignment", 792 argument=assignment)
793 794
795 -def read_assignments(file):
796 """Read assignments from a 'file'. 797 798 'file' -- A file object containing the context. When the file is 799 read, leading and trailing whitespace is discarded from each line 800 in the file. Then, lines that begin with a '#' and lines that 801 contain no characters are discarded. All other lines must be of 802 the form 'NAME=VALUE' and indicate an assignment to the context 803 variable 'NAME' of the indicated 'VALUE'. 804 805 returns -- A dictionary mapping each of the indicated 'NAME's to its 806 corresponding 'VALUE'. If multiple assignments to the same 'NAME' 807 are present, only the 'VALUE' from the last assignment is stored.""" 808 809 # Create an empty dictionary. 810 assignments = {} 811 812 # Read all the lines in the file. 813 lines = file.readlines() 814 # Strip out leading and trailing whitespace. 815 lines = map(string.strip, lines) 816 # Drop any lines that are completely blank or lines that are 817 # comments. 818 lines = filter(lambda x: x != "" and not x.startswith("#"), 819 lines) 820 # Go through each of the lines to process the context assignment. 821 for line in lines: 822 # Parse the assignment. 823 (name, value) = parse_assignment(line) 824 # Add it to the context. 825 assignments[name] = value 826 827 return assignments
828 829
830 -def get_username():
831 """Returns the current username as a string. 832 833 This is our best guess as to the username of the user who is 834 actually logged in, as opposed to the effective user id used for 835 running tests. 836 837 If the username cannot be found, raises a 'QMException'.""" 838 839 # First try using the 'getpass' module. 840 try: 841 return getpass.getuser() 842 except: 843 pass 844 845 # 'getpass' doesn't necessarily work on Windows, so if that fails, 846 # try the win32 function. 847 try: 848 import win32api 849 except ImportError: 850 pass 851 else: 852 try: 853 return win32api.GetUserName() 854 except: 855 raise PythonException("Error accessing win32 user database", 856 *sys.exc_info()[:2]) 857 858 # And if none of that worked, give up. 859 raise QMException, "Cannot determine user name."
860 861
862 -def get_userid():
863 """Returns the current user id as an integer. 864 865 This is the real user id, not the effective user id, to better track 866 who is actually running the tests. 867 868 If the user id cannot be found or is not defined, raises a 869 'QMException'.""" 870 871 try: 872 uid = os.getuid() 873 except AttributeError: 874 raise QMException, "User ids not supported on this system." 875 return uid
876 877
878 -def html_to_text(html, width=72):
879 """Renders HTML to text in a simple way. 880 881 'html' -- A string containing the HTML code to be rendered. 882 883 'width' -- Column at which to word-wrap. Default 72. 884 885 returns -- A string containing a plain text rendering of the 886 HTML.""" 887 888 s = StringIO.StringIO() 889 w = formatter.DumbWriter(s, width) 890 f = formatter.AbstractFormatter(w) 891 p = htmllib.HTMLParser(f) 892 p.feed(html) 893 p.close() 894 return s.getvalue()
895 896 897 ######################################################################## 898 # variables 899 ######################################################################## 900 901 rc = RcConfiguration() 902 """The configuration stored in system and user rc files.""" 903 904 # The next number to be used when handing out unqiue tag strings. 905 _unique_tag = 0 906 907 ######################################################################## 908 # Local Variables: 909 # mode: python 910 # indent-tabs-mode: nil 911 # fill-column: 72 912 # End: 913