# Perforce Defect Tracking Integration Project
# <http://www.ravenbrook.com/project/p4dti/>
#
# TRANSLATOR.PY -- TRANSLATE FIELDS BETWEEN DEFECT TRACKERS
#
# Gareth Rees, Ravenbrook Limited, 2001-03-13
#
#
# 1. INTRODUCTION
#
# This module defines the translator class. An instance of a translator
# class translates between corresponding fields in two defect trackers.
# For example, we could make a state translator that translates between
# an issue state in TeamTrack and a job state in Perforce.
#
# The translator class is generic: it doesn't know anything about the
# identities of the defect tracker it translates between, so it calls
# them "defect tracker 0" and "defect tracker 1". In the P4DTI, defect
# tracker 1 is conventionally Perforce.
#
# Each kind of field will need its own translator subclass; for example,
# in the TeamTrack integration we have date_translator,
# single_select_translator, state_translator, text_translator,
# user_translator and so on. When the P4DTI starts up, the
# configuration generator makes instances of each translator subclass,
# passing additional data where necessary. For example, the
# single_select_translator needs to know the name of the TeamTrack field
# it is translating, so that it can pick out the selections that apply
# to that field.
#
# The aim of the translator class is to make the replicator implentation
# cleaner and easier to maintain and understand. Rather than having a
# big switch statement, or a fixed set of field types, the replicator
# instead has a list of (field in defect tracker 0, field in defect
# tracker 1, translator) triples which it can iterate over.
#
# I wish I could think of better names than translate_dt_to_p4 and
# translate_p4_to_dt for the translation methods.
#
# The intended readership of this document is project developers.
#
# This document is not confidential.
import dt_interface
import re
import string
# 2. TRANSLATOR CLASS
#
# This is an abstract class that defines the interface for all
# translators.
#
# The translator class itself implements the null translator. It
# doesn't change values, but its methods do check the types of its
# arguments.
#
# See [GDR 2000-10-16, 6.5] for more documentation.
class translator:
# 2.1. TRANSLATE FROM DEFECT TRACKER 0 TO DEFECT TRACKER 1
#
# Translate a value from defect tracker 0 to defect tracker 1.
# Arguments:
#
# value The value (in defect tracker 0) being translated.
# dt0 Defect tracker 0.
# dt1 Defect tracker 1.
# issue0 The issue in defect tracker 0 from which the value comes,
# or None if the value doesn't come from an issue.
# issue1 The issue in defect tracker 0 from which the field comes,
# or None if the value doesn't come from an issue.
#
# Returns a suitable translation of value for defect tracker 1, or
# raises an error if translation is impossible.
#
# This method takes defect trackers as arguments because it may need
# to query the defect tracker to carry out the translation. For
# example, in the TeamTrack integration the user translator needs to
# discover all the users in TeamTrack and Perforce so that it can
# match them up by e-mail address. A translator that does not need
# these arguments may specify defaults for them and be used without
# them (for example, the keyword translator in section 3 below does
# this so that it can be used during configuration before any defect
# tracker objects have been constructed).
#
# This method takes issues as arguments because some translators
# need to know about the whole issue in order to carry out the
# translation. For example, in the TeamTrack integration the state
# translator needs to know the project to which the issue belongs
# (because different projects may have different states with the
# same name which correspond to the same Perforce state). Most
# translators will ignore the issue arguments.
def translate_dt_to_p4(self, value, dt0, dt1, issue0 = None,
issue1 = None):
assert isinstance(dt0, dt_interface.defect_tracker)
assert isinstance(dt1, dt_interface.defect_tracker)
assert (issue0 == None
or isinstance(issue0, dt_interface.defect_tracker_issue))
# The issue1 argument is conventionally the Perforce job.
# Unfortunately, Perforce jobs are just ordinary dictionaries,
# and don't belong to a subclass of defect_tracker_issue, so we
# can't check the type of the issue1 argument. This should be
# corrected.
#assert (issue1 == None
# or isinstance(issue1, dt_interface.defect_tracker_issue))
return value
# 2.2. TRANSLATE FROM DEFECT TRACKER 1 TO DEFECT TRACKER 0
#
# Translate a value from defect tracker 0 to defect tracker 1.
# Arguments:
#
# value The value (in defect tracker 1) being translated.
# ... (other arguments are the same as translate_dt_to_p4)
#
# Returns a suitable translation of value for defect tracker 0, or
# raises an error if translation is impossible.
#
# The notes for translate_dt_to_p4() in section 2.1 also apply to this
# method.
def translate_p4_to_dt(self, value, dt0, dt1, issue0 = None,
issue1 = None):
assert isinstance(dt0, dt_interface.defect_tracker)
assert isinstance(dt1, dt_interface.defect_tracker)
assert (issue0 == None
or isinstance(issue0, dt_interface.defect_tracker_issue))
# See comment in translate_dt_to_p4 above.
#assert (issue1 == None
# or isinstance(issue1, dt_interface.defect_tracker_issue))
return value
# 3. KEYWORD TRANSLATOR CLASS
#
# This class translates "keywords" between any defect tracker and
# Perforce. By "keywords" I mean job field names in Perforce, and
# values in "select" fields in Perforce jobs.
#
# Defect tracker keywords can (in general) contain whitespace and
# punctuation characters.
#
# But Perforce job field names can't contain whitespace, hashes, or
# double quotes. Values in "select" fields in Perforce jobspecs also
# can't contain semicolons or slashes.
#
# The translation must be one-to-one so that values in "select" fields
# in Perforce can be accurately translated back to the defect tracker.
#
# We use the following translation:
#
# DT Perforce Description
# ---------------------------------
# _ (space to underscore)
# \ \\ (we use backslash to escape so it must escape itself)
# _ \_ (underscore to backslash underscore)
# ; \: (semicolon to backslash colon)
# # \= (hash to backslash equals)
# / \| (slash to backslash bar)
# " \' (double quote to backslash apostrophe)
# c \xab (where c is some other whitespace character and ab is
# its hex representation).
#
# See job000195 for the motivation behind this design.
class keyword_translator(translator):
# 3.1. Fixed translations
#
# specials is a list of pairs (defect tracker string, Perforce
# string). dt_to_p4 is a map from defect tracker string to Perforce
# string. p4_to_dt is a map from Perforce string to defect tracker
# string.
#
# 'dt_to_p4' and 'p4_to_dt' are generated from specials when an
# instance is created.
specials = [(' ', '_'),
('_', '\\_'),
('\\', '\\\\'),
(';', '\\:'),
('/', '\\|'),
('#', '\\='),
('"', "\\'"),
]
dt_to_p4 = {}
p4_to_dt = {}
def __init__(self):
for (dt,p4) in self.specials:
self.dt_to_p4[dt] = p4
self.p4_to_dt[p4] = dt
# 3.2. Translate a matched single character to an escape sequence
def char_to_p4(self, match):
if self.dt_to_p4.has_key(match.group(0)):
return self.dt_to_p4[match.group(0)]
else:
return '\\x%02x' % ord(match.group(0))
# 3.3. Translate a matched escape sequence to a single character
def p4_to_char(self, match):
if self.p4_to_dt.has_key(match.group(0)):
return self.p4_to_dt[match.group(0)]
else:
return chr(string.atoi(match.group(1)[2:], 0x10))
# 3.4. Translate a keyword from the defect tracker to Perforce
#
# This method ignores its arguments dt0 and dt1 so that it can be
# called during confguration generation, before any defect tracker
# objects have been constructed. See configure_bugzilla.py.
def translate_dt_to_p4(self, s, dt0 = None, dt1 = None, issue0 = None,
issue1 = None):
return re.sub('[\\s_;/#"\\\\]', self.char_to_p4, s)
# 3.5. Translate a keyword from Perforce to the defect tracker.
#
# See the comment for translate_dt_to_p4() in section 3.4 above.
def translate_p4_to_dt(self, s, dt0 = None, dt1 = None, issue0 = None,
issue1 = None):
return re.sub("_|\\\\([_\\\\:|=']|x[0-9a-f]{2})",
self.p4_to_char, s)
# 4. USER TRANSLATOR CLASS
#
# A user translator is a translator between users in two defect
# trackers, but it implements the additional method unmatched_users.
#
# This class is the abstract base class for all user translators.
#
# See [GDR 2000-10-16, 7.5.3] for documentation.
class user_translator(translator):
pass
# A. REFERENCES
#
# [GDR 2000-10-16] "Perforce Defect Tracking Integration Integrator's
# Guide"; Gareth Rees; Ravenbrook Limited; 2000-10-16;
# <http://www.ravenbrook.com/project/p4dti/version/2.1/manual/ig/>.
#
#
# B. DOCUMENT HISTORY
#
# 2001-02-21 GDR Created (as keyword.py).
#
# 2001-03-02 RB Transferred copyright to Perforce under their license.
#
# 2001-03-13 GDR Renamed as translator.py. Formatted as a document.
# Included translator interface (from replicator.py). Moved unit test
# for keyword_translator to test/ directory.
#
# 2001-03-19 GDR Added user_translator class.
#
# 2001-03-21 GDR Corrected description of unmatched_users return value.
# Added references to IG.
#
# 2003-05-21 NB Fixed some broken comments.
#
#
# 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/translator.py#2 $