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