'''Contains an object to log hightlighted tokens USAGE: >>> logger = Logger('colored_log.rtf') >>> logger.set(['The', 'cat', 'is', nice', '.']) >>> logger.markup(red = 1) >>> logger.set(['The', 'dog', 'smiles', '.']) >>> logger.markup(blue = 1) >>> logger.stop() This will create a file colored_log.rtf with the sentences "The cat is nice ." in which cat is red. "The dog smiles ." in which dog is blue. - It is possible to add info and sentenceids. - The set() and markup() methods are split up. This makes it possible to log the same sentence multiple times with different markups. - If you just want to write without colors: set a sentence and >>> logger.markup() - Forgetting to stop() the logger will result in illegal file contents. - Inserting an empty line can be done with the cr() method. Note: you cannot reopen a file that has been closed. # License: GNU General Public License, see http://www.clips.ua.ac.be/~vincent/scripts/LICENSE.txt ''' __author__='Vincent Van Asch' __date__ = '2010' __url__ = 'http://www.clips.ua.ac.be/~vincent/software.html' import os class Logger: def __init__(self, fname): '''fname: the file to log to''' if not fname.endswith('.rtf'): raise ValueError('Only filenames with rtf extension are accepted') self.fname = os.path.abspath(os.path.expanduser(fname)) self._sentence=None self._sentenceid = None self._file=None self._first=True self._color=None #Open the file already self._openfile() def __repr__(self): status = 'Open' if self._file in [None, '__DONE__']: status = 'Closed' return '<%s %s-instance logging to %s>' %(status, self.__class__.__name__, self.fname) def set(self, sentence, id=None): '''Set the sentence that we are logging. sentence: a list of tokens. If a string is given it will be tokenized at whitespace. id: the id of teh sentence. If given this is also logged ''' if self._file == '__DONE__': raise ValueError('This instance has been closed and cannot be reopened') if isinstance(sentence, (str, unicode)): sentence = sentence.strip().split() if not isinstance(sentence, (list, tuple)): raise TypeError('sentence should be a list not: %s' %str(typle(sentence))) self._sentence=sentence if id is not None: self._sentenceid = str(id) @property def sentence(self): '''The sentence that we are logging''' return self._sentence @property def sentenceid(self): '''The id of the sentence that we are logging. Not guaranteed to be set.''' return self._sentenceid def markup(self, red=None, green=None, blue=None, yellow=None, info=None): '''Writes the sentence that is in memory with the tokens at the given indices highlighted. A color can be an integer or a tuple of integers. There cannot be an overlap between the different colors. info: a string that is added in front of the sentence ''' # Make sure the file is open if self._file == '__DONE__': raise ValueError('This instance has been closed and cannot be reopened') if self._file is None: self._openfile() # Define the colors regular = r'\cf0 ' redmark = r'\cf2 ' greenmark = r'\cf3 ' bluemark = r'\cf4 ' yellowmark = r'\cf5 ' #Make sure all colors are tuples if red is None: red = () if green is None: green = () if blue is None: blue = () if yellow is None: yellow = () if isinstance(red, int): red = (red,) if isinstance(green, int): green = (green,) if isinstance(blue, int): blue = (blue,) if isinstance(yellow, int): yellow = (yellow,) #No overlap allowed overlap = tuple(red) + tuple(green) + tuple(blue) + tuple(yellow) overlapunique = set(overlap) if len(overlap) != len(overlapunique): raise ValueError('No overlapping indices allowed: %s' %str(overlap)) #Add a newline for the next sentence if not self._first: self._file.write('\\\n') #Start writing for i, word in enumerate(self.sentence): if self._first: self._file.write(r'\f0\fs24 ') # Create metadata metadata = '' if self.sentenceid: metadata += (self.sentenceid+': ') if info: metadata += (str(info).strip() + ': ') if i == 0: if self._color == regular: self._file.write(metadata) else: self._color=regular if self._first: self._file.write(regular+metadata) self._first=False else: self._file.write('\n\\b0 '+regular+metadata) #Write the words if i in red: if self._color == redmark: self._file.write(word+' ') else: self._color = redmark self._file.write('\n\\b '+ redmark + word +' ') elif i in green: if self._color == greenmark: self._file.write(word+' ') else: self._color = greenmark self._file.write('\n\\b '+ greenmark + word +' ') elif i in blue: if self._color == bluemark: self._file.write(word+' ') else: self._color = bluemark self._file.write('\n\\b '+ bluemark + word +' ') elif i in yellow: if self._color == yellowmark: self._file.write(word+' ') else: self._color = yellowmark self._file.write('\n\\b '+ yellowmark + word +' ') else: if self._color == regular: self._file.write(word +' ') else: self._color = regular self._file.write('\n\\b0 '+ regular + word +' ') self._file.flush() def cr(self): '''Write an empty line to the logfile''' # Make sure the file is open if self._file == '__DONE__': raise ValueError('This instance has been closed and cannot be reopened') if self._file is None: self._openfile() self._file.write('\\\n') self._file.flush() def _openfile(self): '''Opens the file and writes the RTF preamble''' if self._file == '__DONE__': raise ValueError('Cannot reopen an already used file') if self._file is not None: raise ValueError('File is already open') # Open file self._file = open(self.fname, 'w') # Write preamble preamble = r'''{\rtf1\ansi\ansicpg1252\cocoartf949\cocoasubrtf350 {\fonttbl\f0\fswiss\fcharset0 Helvetica;} {\colortbl;\red255\green255\blue255;\red255\green0\blue6;\red23\green255\blue0;\red3\green0\blue255;\red255\green234\blue2;} \paperw11900\paperh16840\margl1440\margr1440\vieww9000\viewh8400\viewkind0 \pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\ql\qnatural\pardirnatural ''' self._file.write(preamble) def _closefile(self): '''Closes the file and add the final }''' if not isinstance(self._file, file): raise ValueError('self._file is not a file but %s' %str(type(self._file))) if self._file.closed: raise ValueError('Cannot close closed file') if self._file == '__DONE__': raise ValueError('Cannot close closed file') self._file.write('}') self._file.close() self._file = '__DONE__' def stop(self): '''Closes the logger''' self._closefile()