# Perforce Defect Tracking Integration Project # # # MESSAGE.PY -- MESSAGE OBJECTS FOR LOGGING AND EXCEPTIONS # # Gareth Rees, Ravenbrook Limited, 2001-03-11 # # # 1. INTRODUCTION # # This module defines these classes: # # 1. message. Each message has an id, text, priority and product. A set of # priority levels are defined. Messages have methods for printing and # computing check digits. # # 2. factory. A message factory is an object used to create messages # (typically for the same product). # # 3. catalog_factory. A message factory that creates messages whose priority # and text comes from a message catalog. # # These classes are intended to # # 1. Allow administrators to debug their configuration [Requirements, 63] by # providing each message with a unique message id that can be used to look up # troubleshooting information. See also job000030 (Users can't get help based # on messages). # # 2. Help Perforce support to assist administrators [Requirements, 33, 34, 35] # ditto. # # 3. Help developers debug modifications of the P4DTI [Requirements, 25] by # providing debugging messages and allowing them to control the messages they # see. See also job000065 (Not enough logging control). # # 4. Help integrators debug their extensions to new defect trackers # [Requirements, 21] ditto. # # 5. Internationalize the P4DTI. # # See [RB 2000-10-16] for the decision to adopt ISBN-style checkdigits on # message ids. # # See [RB 2000-12-16] for the decision to make messages and exceptions into # classes. # # Because we want people to be able to extend the P4DTI [Requirements, 25], we # need to make it easy for people to allocate message ids without clashing with # existing ones or ones which other integrators are using. So each message has # a "product" field which forms part of the message id. Messages from the # supported P4DTI have product="P4DTI". # # The intended readership of this document is project developers. # # This document is not confidential. import os import string import types # 2. MESSAGE PRIORITIES # # These are based on the syslog priorities on Unix. The first two priorities # aren't relevant for the P4DTI, but I've included them for completeness. # # Prorities must be numbers because the logger module needs to compare # priorities when deciding whether a message is severe enough to log. # # On Unix we arrange for the values for these priorities to exactly match the # syslog priorities, so that the values can be passed directly to syslog(). On # Windows, we supply our own values (namely the values in syslog.h on FreeBSD). NOT_USED = -99 if os.name == 'posix': import syslog EMERG = syslog.LOG_EMERG ALERT = syslog.LOG_ALERT CRIT = syslog.LOG_CRIT ERR = syslog.LOG_ERR WARNING = syslog.LOG_WARNING NOTICE = syslog.LOG_NOTICE INFO = syslog.LOG_INFO DEBUG = syslog.LOG_DEBUG else: EMERG = 0 # System is unusable (can't happen with P4DTI). ALERT = 1 # Action must be taken immediately (can't happen with P4DTI). CRIT = 2 # Fatal error; the P4DTI will stop. ERR = 3 # Error, but not a fatal error. WARNING = 4 # Pay attention: something may be wrong. NOTICE = 5 # Expected condition, but significant. INFO = 6 # For information only. DEBUG = 7 # Unlikely to be useful except for debugging. # 3. MESSAGE CLASS class message: id = None priority = None product = None text = None def __init__(self, id, text, priority, product): assert isinstance(id, types.IntType) assert id >= 0 assert isinstance(text, types.StringType) assert isinstance(priority, types.IntType) assert priority >= EMERG and priority <= DEBUG assert isinstance(product, types.StringType) self.id = id self.priority = priority self.text = text self.product = product # 3.1. Format the message as a string, including the message id. def __str__(self): return "(%s) %s" % (self.message_id(), self.text) # 3.2. Compute a message check-digit. # # Compute the check-digit (0-9 or X) for the given message id and return it # as a string. See [RB 2000-10-16] for the design decision and # justification. def check_digit(self): id = self.id sum = 0 i = 0 while 1: if id <= 0: break else: i = i + 1 sum = sum + i * (id % 10) id = id / 10 return "0123456789X"[sum % 11] # 3.3. Format a message id for display # # Message ids look like P-NC, where P is the project name, N is the id # number and C is the check digit. Or just NC if the product is empty. # See [RB 2000-10-16] for the design decision. def message_id(self): if self.product: return "%s-%d%s" % (self.product, self.id, self.check_digit()) else: return "%d%s" % (self.id, self.check_digit()) # 3.4. Text-wrap the message # # Text-wrap the message to the given number of columns. def wrap(self, columns): text = str(self) lines = [ ] pos = 0 length = len(text) while pos < length: if pos + columns >= length: space = length else: space = string.rfind(text, ' ', pos, pos + columns + 1) if space == -1: space = string.find(text, ' ', pos, length) if space == -1: space = length while space > 0 and text[space-1] == ' ': space = space - 1 lines.append(text[pos:space]) while space < length and text[space] == ' ': space = space + 1 pos = space return string.join(lines, '\n') # 4. MESSAGE FACTORY CLASS # # A message factory has a new() method which constructs a message object. class factory: priority = None product = None def __init__(self, priority = INFO, product = ""): assert isinstance(priority, types.IntType) assert priority >= EMERG and priority <= DEBUG assert isinstance(product, types.StringType) self.priority = priority self.product = product def new(self, id, text, priority = None, product = None): if priority == None: priority = self.priority if product == None: product = self.product return message(id, text, priority, product) # 5. CATALOG MESSAGE FACTORY CLASS # # The new() method of this message factory class doesn't need to be passed the # fixed text of the message, only the arguments (as a tuple). It looks up the # fixed text in the message catalog and uses that fixed text together with the # arguments to build the message text. # # This class is intended to: # # 1. Support future localization of the P4DTI (by using different message # catalogs for different languages); and # # 2. Help developers to prevent message ids from clashing, by providing a # catalog of all messages. # # The catalog argument is a dictionary mapping message id to (priority, # format string). It must not have an entry for id 0, because id 0 is # used when an invalid message id is passed to the new() method. class catalog_factory(factory): catalog = None def __init__(self, catalog, product = ""): assert isinstance(catalog, types.DictType) assert not catalog.has_key(0) factory.__init__(self, product = product) self.catalog = catalog def new(self, id, args = ()): if self.catalog.has_key(id): (priority, format) = self.catalog[id] try: return factory.new(self, id, format % args, priority) except TypeError: return factory.new(self, 0, "Message %s has format " "string '%s' but arguments %s." % (id, format, args), ERR) else: return factory.new(self, 0, "No message with id '%s' " "(args = %s)." % (id,args), ERR) # A. REFERENCES # # [RB 2000-12-16] "Should exceptions be classes?" (e-mail message); Richard # Brooksby; Ravenbrook; 2000-12-16; # . # # [RB 2000-10-16] "Message IDs" (e-mail message); Richard Brooksby; Ravenbrook # Limited; 2000-10-16; # . # # [Requirements] "Perforce Defect Tracking Integration Project Requirements" # (living document); Gareth Rees; Ravenbrook Limited; 2000-05-24; # . # # # B. DOCUMENT HISTORY # # 2001-03-11 GDR Created. # # 2001-05-22 GDR catalog_factory.new generates useful error messages # when passed an invalid message id or the wrong arguments. # # # C. COPYRIGHT AND LICENCE # # This file is copyright (c) 2001 Perforce Software, Inc. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # # 2. Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # # # $Id: //info.ravenbrook.com/project/p4dti/version/1.1/code/replicator/message.py#3 $