log_formatter.py
changeset 140 6db2527b67cf
parent 139 9c7769850195
child 141 65c98c9e1716
equal deleted inserted replaced
139:9c7769850195 140:6db2527b67cf
     1 """
       
     2     Format LogLines into some other representation
       
     3 """
       
     4 
       
     5 import re, xml.sax.saxutils
       
     6 
       
     7 from log_line import LogTypes
       
     8 from log_formatter_pil import PILImageFormatter
       
     9 from log_formatter_rss import RSSFormatter
       
    10 
       
    11 class LogFormatter (object) :
       
    12     """
       
    13         Provides a method to format series of LogLines into various output formats, with varying themes.
       
    14     """
       
    15 
       
    16     # machine-readable name
       
    17     name = None
       
    18 
       
    19     # human-readable name
       
    20     title = None
       
    21 
       
    22     ## parameters
       
    23     # use a fixed-width font for HTML output
       
    24     html_fixedwidth = True
       
    25 
       
    26     def __init__ (self, tz, timestamp_fmt, img_ttf_path, img_font_size) :
       
    27         """
       
    28             Initialize to format timestamps with the given timezone and timestamp.
       
    29 
       
    30             Use the given TTF font to render image text with the given size, if given, otherwise, a default one.
       
    31         """
       
    32         
       
    33         # store
       
    34         self.tz = tz
       
    35         self.timestamp_fmt = timestamp_fmt
       
    36         self.img_ttf_path = img_ttf_path
       
    37         self.img_font_size = img_font_size
       
    38         
       
    39         # XXX: harcoded
       
    40         self.date_fmt = '%Y-%m-%d'
       
    41     
       
    42     def _format_line_text (self, line, template_dict, type=None, full_timestamp=False, **extra) :
       
    43         """
       
    44             Format the given line as text, using the given { type: string template } dict.
       
    45             
       
    46             If type is given, then it overrides line.type
       
    47 
       
    48             Any additional keyword args will also be available for the template to use
       
    49         """
       
    50 
       
    51         # default type?
       
    52         if type is None :
       
    53             type = line.type
       
    54             
       
    55         # look up the template
       
    56         if type in template_dict :
       
    57             template = template_dict[type]
       
    58 
       
    59         else :
       
    60             raise Exception("Format template not defined for type: %s" % LogTypes.name_from_code(type))
       
    61         
       
    62         # convert timestamp into display timezone
       
    63         dtz = line.timestamp.astimezone(self.tz)
       
    64         
       
    65         # full timestamps?
       
    66         if full_timestamp :
       
    67             # XXX: let the user define a 'datetime' format instead?
       
    68             timestamp_fmt = self.date_fmt + ' ' + self.timestamp_fmt
       
    69 
       
    70         else :
       
    71             timestamp_fmt = self.timestamp_fmt
       
    72         
       
    73         # breakdown source
       
    74         source_nickname, source_username, source_hostname, source_chanflag = line.source
       
    75         target_nickname = line.target
       
    76         
       
    77         # format with dict
       
    78         return template % dict(
       
    79             channel_name    = line.channel.name,
       
    80             datetime        = dtz.strftime('%a %b %d %H:%M:%S %Y'),
       
    81             date            = dtz.strftime(self.date_fmt),
       
    82             timestamp       = dtz.strftime(timestamp_fmt),
       
    83             source_nickname = source_nickname,
       
    84             source_username = source_username,
       
    85             source_hostname = source_hostname,
       
    86             source_chanflag = source_chanflag,
       
    87             target_nickname = target_nickname,
       
    88             message         = line.data,
       
    89             **extra
       
    90         )
       
    91     
       
    92     def format_txt (self, lines, full_timestamps=False) :
       
    93         """
       
    94             Format given lines as plaintext.
       
    95 
       
    96             If full_timestamps is given, the output will contain full timestamps with both date and time.
       
    97 
       
    98             No trailing newlines.
       
    99         """
       
   100 
       
   101         abstract
       
   102 
       
   103     def format_html (self, lines, full_timestamps=False) :
       
   104         """
       
   105             Format as HTML.
       
   106             
       
   107             See format_txt for information about arguments
       
   108         """
       
   109 
       
   110         abstract
       
   111     
       
   112     def format_png (self, lines, full_timestamps=False) :
       
   113         """
       
   114             Format as a PNG image, returning the binary PNG data
       
   115         """
       
   116 
       
   117         abstract
       
   118     
       
   119     def format_rss (self, lines, full_timestamps=False) :
       
   120         """
       
   121             Format as an XML RSS document
       
   122         """
       
   123         
       
   124         abstract
       
   125 
       
   126 class BaseHTMLFormatter (LogFormatter) :
       
   127     """
       
   128         Implements some HTML-formatting utils
       
   129     """
       
   130     
       
   131     # parameters
       
   132     html_fixedwidth = True
       
   133 
       
   134     # regexp to match URLs
       
   135     URL_REGEXP = re.compile(r"http://\S+")
       
   136 
       
   137     def _process_links (self, line) :
       
   138         """
       
   139             Processed the rendered line, adding in <a href>'s for things that look like URLs, returning the new line.
       
   140 
       
   141             The line should already be escaped
       
   142         """
       
   143 
       
   144         def _encode_url (match) :
       
   145             # encode URL
       
   146             url_html = match.group(0)
       
   147             url_link = xml.sax.saxutils.unescape(url_html)
       
   148 
       
   149             return '<a href="%(url_link)s">%(url_html)s</a>' % dict(url_link=url_link, url_html=url_html)
       
   150 
       
   151         return self.URL_REGEXP.sub(_encode_url, line)
       
   152  
       
   153     def format_html (self, lines, **kwargs) :
       
   154         """
       
   155             Just uses format_txt, but processes links, etc
       
   156         """
       
   157         
       
   158         # format using IrssiTextFormatter
       
   159         for line, txt in self.format_txt(lines, **kwargs) :
       
   160             # escape HTML
       
   161             html = xml.sax.saxutils.escape(txt)
       
   162 
       
   163             # process links
       
   164             html = self._process_links(html)
       
   165 
       
   166             # yield
       
   167             yield line, html
       
   168 
       
   169    
       
   170 class IrssiTextFormatter (RSSFormatter, PILImageFormatter, LogFormatter) :
       
   171     """
       
   172         Implements format_txt for irssi-style output
       
   173     """
       
   174 
       
   175     # format definitions by type
       
   176     __FMT = {
       
   177         LogTypes.RAW        : "%(timestamp)s %(data)s",
       
   178         LogTypes.LOG_OPEN   : "--- Log opened %(datetime)s",
       
   179         LogTypes.LOG_CLOSE  : "--- Log closed %(datetime)s",
       
   180         'DAY_CHANGED'       : "--- Day changed %(date)s",
       
   181 
       
   182         LogTypes.MSG        : "%(timestamp)s <%(source_chanflag)s%(source_nickname)s> %(message)s",
       
   183         LogTypes.NOTICE     : "%(timestamp)s -%(source_nickname)s- %(message)s",
       
   184         LogTypes.ACTION     : "%(timestamp)s  * %(source_nickname)s %(message)s",
       
   185 
       
   186         LogTypes.JOIN       : "%(timestamp)s -!- %(source_nickname)s [%(source_username)s@%(source_hostname)s] has joined %(channel_name)s",
       
   187         LogTypes.PART       : "%(timestamp)s -!- %(source_nickname)s [%(source_username)s@%(source_hostname)s] has left %(channel_name)s [%(message)s]",
       
   188         LogTypes.KICK       : "%(timestamp)s -!- %(target_nickname)s was kicked from %(channel_name)s by %(source_nickname)s [%(message)s]",
       
   189         LogTypes.MODE       : "%(timestamp)s -!- mode/%(channel_name)s [%(message)s] by %(source_nickname)s",
       
   190 
       
   191         LogTypes.NICK       : "%(timestamp)s -!- %(source_nickname)s is now known as %(target_nickname)s",
       
   192         LogTypes.QUIT       : "%(timestamp)s -!- %(source_nickname)s [%(source_username)s@%(source_hostname)s] has quit [%(message)s]",
       
   193 
       
   194         LogTypes.TOPIC      : "%(timestamp)s -!- %(source_nickname)s changed the topic of %(channel_name)s to: %(message)s",
       
   195         'TOPIC_UNSET'       : "%(timestamp)s -!- Topic unset by %(source_nickname)s on %(channel_name)s",
       
   196 
       
   197         LogTypes.SELF_NOTICE: "%(timestamp)s -%(source_nickname)s- %(message)s",
       
   198         LogTypes.SELF_NICK  : "%(timestamp)s -!- %(source_nickname)s is now known as %(target_nickname)s",
       
   199 
       
   200         LogTypes.NETSPLIT_START : 
       
   201                               "%(timestamp)s -!- Netsplit %(source_hostname)s <-> %(target_nickname)s quits: %(_netsplit_targets)s",
       
   202         LogTypes.NETSPLIT_END :
       
   203                               "%(timestamp)s -!- Netsplit over, joins: %(_netsplit_targets)s",
       
   204     }
       
   205 
       
   206     def format_txt (self, lines, full_timestamps=False) :
       
   207         # ...handle each line
       
   208         for line in lines :
       
   209             # extra args
       
   210             extra = {}
       
   211             
       
   212             # default to line.type
       
   213             type = line.type
       
   214 
       
   215             # special formatting for unset-Topic
       
   216             if line.type == LogTypes.TOPIC and line.data is None :
       
   217                 type = 'TOPIC_UNSET'
       
   218             
       
   219             # format netsplit stuff
       
   220             elif line.type & LogTypes._NETSPLIT_MASK :
       
   221                 # format the netsplit-targets stuff
       
   222                 extra['_netsplit_targets'] = line.data
       
   223 
       
   224             # using __TYPES
       
   225             yield line, self._format_line_text(line, self.__FMT, type, full_timestamps, **extra)
       
   226 
       
   227 class IrssiFormatter (BaseHTMLFormatter, IrssiTextFormatter) :
       
   228     """
       
   229         Implements plain black-and-white irssi-style formatting
       
   230     """
       
   231     
       
   232     # name
       
   233     name = 'irssi'
       
   234     title = "Irssi (plain)"
       
   235 
       
   236 class DebugFormatter (BaseHTMLFormatter) :
       
   237     """
       
   238         Implements a raw debug-style formatting of LogLines
       
   239     """
       
   240 
       
   241     # name
       
   242     name = 'debug'
       
   243     title = "Raw debugging format"
       
   244     
       
   245     def format_txt (self, lines, full_timestamps=False) :
       
   246         # iterate
       
   247         for line in lines :
       
   248             # just dump
       
   249             yield line, unicode(line)
       
   250 
       
   251 def by_name (name) :
       
   252     """
       
   253         Lookup and return a class LogFormatter by name
       
   254     """
       
   255 
       
   256     return FORMATTERS[name]
       
   257