terom@50: """
terom@50: Format LogLines into some other representation
terom@50: """
terom@50:
terom@69: import re, xml.sax.saxutils
terom@50:
terom@50: from log_line import LogTypes
terom@79: from log_formatter_pil import PILImageFormatter
terom@80: from log_formatter_rss import RSSFormatter
terom@50:
terom@50: class LogFormatter (object) :
terom@50: """
terom@65: Provides a method to format series of LogLines into various output formats, with varying themes.
terom@50: """
terom@51:
terom@72: # machine-readable name
terom@51: name = None
terom@72:
terom@72: # human-readable name
terom@72: title = None
terom@72:
terom@72: ## parameters
terom@72: # use a fixed-width font for HTML output
terom@72: html_fixedwidth = True
terom@72:
terom@79: def __init__ (self, tz, timestamp_fmt, img_ttf_path, img_font_size) :
terom@50: """
terom@79: Initialize to format timestamps with the given timezone and timestamp.
terom@79:
terom@79: Use the given TTF font to render image text with the given size, if given, otherwise, a default one.
terom@50: """
terom@97:
terom@97: # store
terom@50: self.tz = tz
terom@50: self.timestamp_fmt = timestamp_fmt
terom@79: self.img_ttf_path = img_ttf_path
terom@79: self.img_font_size = img_font_size
terom@97:
terom@97: # XXX: harcoded
terom@97: self.date_fmt = '%Y-%m-%d'
terom@50:
terom@97: def _format_line_text (self, line, template_dict, type=None, full_timestamp=False, **extra) :
terom@50: """
terom@92: Format the given line as text, using the given { type: string template } dict.
terom@92:
terom@92: If type is given, then it overrides line.type
terom@97:
terom@97: Any additional keyword args will also be available for the template to use
terom@50: """
terom@92:
terom@92: # default type?
terom@92: if type is None :
terom@92: type = line.type
terom@65:
terom@50: # look up the template
terom@97: if type in template_dict :
terom@97: template = template_dict[type]
terom@97:
terom@97: else :
terom@97: raise Exception("Format template not defined for type: %s" % LogTypes.name_from_code(type))
terom@65:
terom@65: # convert timestamp into display timezone
terom@65: dtz = line.timestamp.astimezone(self.tz)
terom@65:
terom@65: # full timestamps?
terom@65: if full_timestamp :
terom@97: # XXX: let the user define a 'datetime' format instead?
terom@108: timestamp_fmt = self.date_fmt + ' ' + self.timestamp_fmt
terom@65:
terom@65: else :
terom@65: timestamp_fmt = self.timestamp_fmt
terom@86:
terom@86: # breakdown source
terom@86: source_nickname, source_username, source_hostname, source_chanflag = line.source
terom@86: target_nickname = line.target
terom@50:
terom@50: # format with dict
terom@50: return template % dict(
terom@86: channel_name = line.channel.name,
terom@86: datetime = dtz.strftime('%a %b %d %H:%M:%S %Y'),
terom@97: date = dtz.strftime(self.date_fmt),
terom@86: timestamp = dtz.strftime(timestamp_fmt),
terom@86: source_nickname = source_nickname,
terom@86: source_username = source_username,
terom@86: source_hostname = source_hostname,
terom@86: source_chanflag = source_chanflag,
terom@86: target_nickname = target_nickname,
terom@86: message = line.data,
terom@97: **extra
terom@50: )
terom@50:
terom@65: def format_txt (self, lines, full_timestamps=False) :
terom@50: """
terom@65: Format given lines as plaintext.
terom@65:
terom@65: If full_timestamps is given, the output will contain full timestamps with both date and time.
terom@65:
terom@65: No trailing newlines.
terom@50: """
terom@50:
terom@50: abstract
terom@50:
terom@65: def format_html (self, lines, full_timestamps=False) :
terom@50: """
terom@65: Format as HTML.
terom@65:
terom@65: See format_txt for information about arguments
terom@50: """
terom@50:
terom@50: abstract
terom@79:
terom@80: def format_png (self, lines, full_timestamps=False) :
terom@79: """
terom@79: Format as a PNG image, returning the binary PNG data
terom@79: """
terom@50:
terom@80: abstract
terom@80:
terom@80: def format_rss (self, lines, full_timestamps=False) :
terom@80: """
terom@80: Format as an XML RSS document
terom@80: """
terom@80:
terom@80: abstract
terom@80:
terom@86: class BaseHTMLFormatter (LogFormatter) :
terom@69: """
terom@69: Implements some HTML-formatting utils
terom@69: """
terom@86:
terom@86: # parameters
terom@86: html_fixedwidth = True
terom@69:
terom@86: # regexp to match URLs
terom@69: URL_REGEXP = re.compile(r"http://\S+")
terom@69:
terom@69: def _process_links (self, line) :
terom@69: """
terom@69: Processed the rendered line, adding in 's for things that look like URLs, returning the new line.
terom@69:
terom@69: The line should already be escaped
terom@69: """
terom@69:
terom@69: def _encode_url (match) :
terom@69: # encode URL
terom@69: url_html = match.group(0)
terom@69: url_link = xml.sax.saxutils.unescape(url_html)
terom@69:
terom@69: return '%(url_html)s' % dict(url_link=url_link, url_html=url_html)
terom@69:
terom@69: return self.URL_REGEXP.sub(_encode_url, line)
terom@86:
terom@79: def format_html (self, lines, **kwargs) :
terom@50: """
terom@72: Just uses format_txt, but processes links, etc
terom@50: """
terom@50:
terom@50: # format using IrssiTextFormatter
terom@79: for line, txt in self.format_txt(lines, **kwargs) :
terom@50: # escape HTML
terom@72: html = xml.sax.saxutils.escape(txt)
terom@69:
terom@69: # process links
terom@72: html = self._process_links(html)
terom@69:
terom@69: # yield
terom@72: yield line, html
terom@50:
terom@86:
terom@86: class IrssiTextFormatter (RSSFormatter, PILImageFormatter, LogFormatter) :
terom@86: """
terom@86: Implements format_txt for irssi-style output
terom@86: """
terom@86:
terom@86: # format definitions by type
terom@86: __FMT = {
terom@86: LogTypes.RAW : "%(timestamp)s %(data)s",
terom@86: LogTypes.LOG_OPEN : "--- Log opened %(datetime)s",
terom@86: LogTypes.LOG_CLOSE : "--- Log closed %(datetime)s",
terom@97: 'DAY_CHANGED' : "--- Day changed %(date)s",
terom@86:
terom@86: LogTypes.MSG : "%(timestamp)s <%(source_chanflag)s%(source_nickname)s> %(message)s",
terom@86: LogTypes.NOTICE : "%(timestamp)s -%(source_nickname)s- %(message)s",
terom@86: LogTypes.ACTION : "%(timestamp)s * %(source_nickname)s %(message)s",
terom@86:
terom@86: LogTypes.JOIN : "%(timestamp)s -!- %(source_nickname)s [%(source_username)s@%(source_hostname)s] has joined %(channel_name)s",
terom@86: LogTypes.PART : "%(timestamp)s -!- %(source_nickname)s [%(source_username)s@%(source_hostname)s] has left %(channel_name)s [%(message)s]",
terom@86: LogTypes.KICK : "%(timestamp)s -!- %(target_nickname)s was kicked from %(channel_name)s by %(source_nickname)s [%(message)s]",
terom@86: LogTypes.MODE : "%(timestamp)s -!- mode/%(channel_name)s [%(message)s] by %(source_nickname)s",
terom@86:
terom@86: LogTypes.NICK : "%(timestamp)s -!- %(source_nickname)s is now known as %(target_nickname)s",
terom@86: LogTypes.QUIT : "%(timestamp)s -!- %(source_nickname)s [%(source_username)s@%(source_hostname)s] has quit [%(message)s]",
terom@86:
terom@86: LogTypes.TOPIC : "%(timestamp)s -!- %(source_nickname)s changed the topic of %(channel_name)s to: %(message)s",
terom@92: 'TOPIC_UNSET' : "%(timestamp)s -!- Topic unset by %(source_nickname)s on %(channel_name)s",
terom@86:
terom@86: LogTypes.SELF_NOTICE: "%(timestamp)s -%(source_nickname)s- %(message)s",
terom@86: LogTypes.SELF_NICK : "%(timestamp)s -!- %(source_nickname)s is now known as %(target_nickname)s",
terom@97:
terom@97: LogTypes.NETSPLIT_START :
terom@97: "%(timestamp)s -!- Netsplit %(source_hostname)s <-> %(target_nickname)s quits: %(_netsplit_targets)s",
terom@97: LogTypes.NETSPLIT_END :
terom@97: "%(timestamp)s -!- Netsplit over, joins: %(_netsplit_targets)s",
terom@86: }
terom@86:
terom@86: def format_txt (self, lines, full_timestamps=False) :
terom@86: # ...handle each line
terom@86: for line in lines :
terom@97: # extra args
terom@97: extra = {}
terom@109:
terom@109: # default to line.type
terom@109: type = line.type
terom@97:
terom@109: # special formatting for unset-Topic
terom@92: if line.type == LogTypes.TOPIC and line.data is None :
terom@92: type = 'TOPIC_UNSET'
terom@92:
terom@97: # format netsplit stuff
terom@109: elif line.type & LogTypes._NETSPLIT_MASK :
terom@97: # format the netsplit-targets stuff
terom@97: extra['_netsplit_targets'] = line.data
terom@97:
terom@86: # using __TYPES
terom@97: yield line, self._format_line_text(line, self.__FMT, type, full_timestamps, **extra)
terom@86:
terom@86: class IrssiFormatter (BaseHTMLFormatter, IrssiTextFormatter) :
terom@86: """
terom@86: Implements plain black-and-white irssi-style formatting
terom@86: """
terom@86:
terom@86: # name
terom@86: name = 'irssi'
terom@86: title = "Irssi (plain)"
terom@86:
terom@86: class DebugFormatter (BaseHTMLFormatter) :
terom@86: """
terom@86: Implements a raw debug-style formatting of LogLines
terom@86: """
terom@86:
terom@86: # name
terom@86: name = 'debug'
terom@86: title = "Raw debugging format"
terom@86:
terom@86: def format_txt (self, lines, full_timestamps=False) :
terom@86: # iterate
terom@86: for line in lines :
terom@86: # just dump
terom@97: yield line, unicode(line)
terom@86:
terom@50: def by_name (name) :
terom@50: """
terom@64: Lookup and return a class LogFormatter by name
terom@50: """
terom@50:
terom@64: return FORMATTERS[name]
terom@50: