# Perforce Defect Tracking Integration Project
# <http://www.ravenbrook.com/project/p4dti/>
#
# 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 math
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 (not used).
ALERT = 1 # Action must be taken immediately (not usedk).
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 = int(math.floor(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;
# <http://info.ravenbrook.com/mail/2000/12/16/06-47-37/0.txt>.
#
# [RB 2000-10-16] "Message IDs" (e-mail message); Richard Brooksby;
# Ravenbrook Limited; 2000-10-16;
# <http://info.ravenbrook.com/mail/2000/10/16/10-50-49/0.txt>.
#
# [Requirements] "Perforce Defect Tracking Integration Project
# Requirements"; Gareth Rees; Ravenbrook Limited; 2000-05-24;
# <http://www.ravenbrook.com/project/p4dti/req/>.
#
#
# 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.
#
# 2001-08-16 GDR Made use of division operator in check_digit() portable
# between Python releases.
#
#
# C. COPYRIGHT AND LICENSE
#
# 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/2.1/code/replicator/message.py#1 $