log_formatter.py
author Tero Marttila <terom@fixme.fi>
Wed, 11 Feb 2009 03:46:59 +0200
changeset 99 8719ac564b22
parent 97 6165f1ba458d
child 108 d0aca7894fc5
permissions -rw-r--r--
implement non-blocking locking for the estdb, and our own locking for the autoload statetmpfile... it should work well now
"""
    Format LogLines into some other representation
"""

import re, xml.sax.saxutils

from log_line import LogTypes
from log_formatter_pil import PILImageFormatter
from log_formatter_rss import RSSFormatter

class LogFormatter (object) :
    """
        Provides a method to format series of LogLines into various output formats, with varying themes.
    """

    # machine-readable name
    name = None

    # human-readable name
    title = None

    ## parameters
    # use a fixed-width font for HTML output
    html_fixedwidth = True

    def __init__ (self, tz, timestamp_fmt, img_ttf_path, img_font_size) :
        """
            Initialize to format timestamps with the given timezone and timestamp.

            Use the given TTF font to render image text with the given size, if given, otherwise, a default one.
        """
        
        # store
        self.tz = tz
        self.timestamp_fmt = timestamp_fmt
        self.img_ttf_path = img_ttf_path
        self.img_font_size = img_font_size
        
        # XXX: harcoded
        self.date_fmt = '%Y-%m-%d'
    
    def _format_line_text (self, line, template_dict, type=None, full_timestamp=False, **extra) :
        """
            Format the given line as text, using the given { type: string template } dict.
            
            If type is given, then it overrides line.type

            Any additional keyword args will also be available for the template to use
        """

        # default type?
        if type is None :
            type = line.type
            
        # look up the template
        if type in template_dict :
            template = template_dict[type]

        else :
            raise Exception("Format template not defined for type: %s" % LogTypes.name_from_code(type))
        
        # convert timestamp into display timezone
        dtz = line.timestamp.astimezone(self.tz)
        
        # full timestamps?
        if full_timestamp :
            # XXX: let the user define a 'datetime' format instead?
            timestamp_fmt = self.date_fmt + self.timestamp_fmt

        else :
            timestamp_fmt = self.timestamp_fmt
        
        # breakdown source
        source_nickname, source_username, source_hostname, source_chanflag = line.source
        target_nickname = line.target
        
        # format with dict
        return template % dict(
            channel_name    = line.channel.name,
            datetime        = dtz.strftime('%a %b %d %H:%M:%S %Y'),
            date            = dtz.strftime(self.date_fmt),
            timestamp       = dtz.strftime(timestamp_fmt),
            source_nickname = source_nickname,
            source_username = source_username,
            source_hostname = source_hostname,
            source_chanflag = source_chanflag,
            target_nickname = target_nickname,
            message         = line.data,
            **extra
        )
    
    def format_txt (self, lines, full_timestamps=False) :
        """
            Format given lines as plaintext.

            If full_timestamps is given, the output will contain full timestamps with both date and time.

            No trailing newlines.
        """

        abstract

    def format_html (self, lines, full_timestamps=False) :
        """
            Format as HTML.
            
            See format_txt for information about arguments
        """

        abstract
    
    def format_png (self, lines, full_timestamps=False) :
        """
            Format as a PNG image, returning the binary PNG data
        """

        abstract
    
    def format_rss (self, lines, full_timestamps=False) :
        """
            Format as an XML RSS document
        """
        
        abstract

class BaseHTMLFormatter (LogFormatter) :
    """
        Implements some HTML-formatting utils
    """
    
    # parameters
    html_fixedwidth = True

    # regexp to match URLs
    URL_REGEXP = re.compile(r"http://\S+")

    def _process_links (self, line) :
        """
            Processed the rendered line, adding in <a href>'s for things that look like URLs, returning the new line.

            The line should already be escaped
        """

        def _encode_url (match) :
            # encode URL
            url_html = match.group(0)
            url_link = xml.sax.saxutils.unescape(url_html)

            return '<a href="%(url_link)s">%(url_html)s</a>' % dict(url_link=url_link, url_html=url_html)

        return self.URL_REGEXP.sub(_encode_url, line)
 
    def format_html (self, lines, **kwargs) :
        """
            Just uses format_txt, but processes links, etc
        """
        
        # format using IrssiTextFormatter
        for line, txt in self.format_txt(lines, **kwargs) :
            # escape HTML
            html = xml.sax.saxutils.escape(txt)

            # process links
            html = self._process_links(html)

            # yield
            yield line, html

   
class IrssiTextFormatter (RSSFormatter, PILImageFormatter, LogFormatter) :
    """
        Implements format_txt for irssi-style output
    """

    # format definitions by type
    __FMT = {
        LogTypes.RAW        : "%(timestamp)s %(data)s",
        LogTypes.LOG_OPEN   : "--- Log opened %(datetime)s",
        LogTypes.LOG_CLOSE  : "--- Log closed %(datetime)s",
        'DAY_CHANGED'       : "--- Day changed %(date)s",

        LogTypes.MSG        : "%(timestamp)s <%(source_chanflag)s%(source_nickname)s> %(message)s",
        LogTypes.NOTICE     : "%(timestamp)s -%(source_nickname)s- %(message)s",
        LogTypes.ACTION     : "%(timestamp)s  * %(source_nickname)s %(message)s",

        LogTypes.JOIN       : "%(timestamp)s -!- %(source_nickname)s [%(source_username)s@%(source_hostname)s] has joined %(channel_name)s",
        LogTypes.PART       : "%(timestamp)s -!- %(source_nickname)s [%(source_username)s@%(source_hostname)s] has left %(channel_name)s [%(message)s]",
        LogTypes.KICK       : "%(timestamp)s -!- %(target_nickname)s was kicked from %(channel_name)s by %(source_nickname)s [%(message)s]",
        LogTypes.MODE       : "%(timestamp)s -!- mode/%(channel_name)s [%(message)s] by %(source_nickname)s",

        LogTypes.NICK       : "%(timestamp)s -!- %(source_nickname)s is now known as %(target_nickname)s",
        LogTypes.QUIT       : "%(timestamp)s -!- %(source_nickname)s [%(source_username)s@%(source_hostname)s] has quit [%(message)s]",

        LogTypes.TOPIC      : "%(timestamp)s -!- %(source_nickname)s changed the topic of %(channel_name)s to: %(message)s",
        'TOPIC_UNSET'       : "%(timestamp)s -!- Topic unset by %(source_nickname)s on %(channel_name)s",

        LogTypes.SELF_NOTICE: "%(timestamp)s -%(source_nickname)s- %(message)s",
        LogTypes.SELF_NICK  : "%(timestamp)s -!- %(source_nickname)s is now known as %(target_nickname)s",

        LogTypes.NETSPLIT_START : 
                              "%(timestamp)s -!- Netsplit %(source_hostname)s <-> %(target_nickname)s quits: %(_netsplit_targets)s",
        LogTypes.NETSPLIT_END :
                              "%(timestamp)s -!- Netsplit over, joins: %(_netsplit_targets)s",
    }

    def format_txt (self, lines, full_timestamps=False) :
        # ...handle each line
        for line in lines :
            # extra args
            extra = {}

            # specialcase type?
            if line.type == LogTypes.TOPIC and line.data is None :
                type = 'TOPIC_UNSET'
            
            else :
                type = line.type

            # format netsplit stuff
            if line.type & LogTypes._NETSPLIT_MASK :
                # format the netsplit-targets stuff
                extra['_netsplit_targets'] = line.data

            # using __TYPES
            yield line, self._format_line_text(line, self.__FMT, type, full_timestamps, **extra)

class IrssiFormatter (BaseHTMLFormatter, IrssiTextFormatter) :
    """
        Implements plain black-and-white irssi-style formatting
    """
    
    # name
    name = 'irssi'
    title = "Irssi (plain)"

class DebugFormatter (BaseHTMLFormatter) :
    """
        Implements a raw debug-style formatting of LogLines
    """

    # name
    name = 'debug'
    title = "Raw debugging format"
    
    def format_txt (self, lines, full_timestamps=False) :
        # iterate
        for line in lines :
            # just dump
            yield line, unicode(line)

def by_name (name) :
    """
        Lookup and return a class LogFormatter by name
    """

    return FORMATTERS[name]