# Perforce Defect Tracking Integration Project # # # 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; # . # # # 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 $