# Richard Darst, June 2009 ### # Copyright (c) 2009, Richard Darst # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * 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. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # 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 OWNER OR 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. ### import os import re import textwrap import time #from meeting import timeZone, meetBotInfoURL # Needed for testing with isinstance() for properly writing. #from items import Topic, Action import items # Data sanitizing for various output methods def html(text): """Escape bad sequences (in HTML) in user-generated lines.""" return text.replace("&", "&").replace("<", "<").replace(">", ">") rstReplaceRE = re.compile('_( |-|$)') def rst(text): """Escapes bad sequences in reST""" return rstReplaceRE.sub(r'\_\1', text) def text(text): """Escapes bad sequences in text (not implemented yet)""" return text # wraping functions (for RST) class TextWrapper(textwrap.TextWrapper): wordsep_re = re.compile(r'(\s+)') def wrapList(item, indent=0): return TextWrapper(width=72, initial_indent=' '*indent, subsequent_indent= ' '*(indent+2), break_long_words=False).fill(item) def replaceWRAP(item): re_wrap = re.compile(r'sWRAPs(.*)eWRAPe', re.DOTALL) def repl(m): return TextWrapper(width=72, break_long_words=False).fill(m.group(1)) return re_wrap.sub(repl, item) def MeetBotVersion(): import meeting if hasattr(meeting, '__version__'): return ' '+meeting.__version__ else: return '' class _BaseWriter(object): def __init__(self, M, **kwargs): self.M = M def format(self, extension=None): """Override this method to implement the formatting. For file output writers, the method should return a unicode object containing the contents of the file to write. The argument 'extension' is the key from `writer_map`. For file writers, this can (and should) be ignored. For non-file outputs, this can be used to This can be used to pass data, """ raise NotImplementedError @property def pagetitle(self): if self.M._meetingTopic: return "%s: %s"%(self.M.channel, self.M._meetingTopic) return "%s Meeting"%self.M.channel def replacements(self): return {'pageTitle':self.pagetitle, 'owner':self.M.owner, 'starttime':time.strftime("%H:%M:%S", self.M.starttime), 'endtime':time.strftime("%H:%M:%S", self.M.endtime), 'timeZone':self.M.config.timeZone, 'fullLogs':self.M.config.basename+'.log.html', 'fullLogsFullURL':self.M.config.filename(url=True)+'.log.html', 'MeetBotInfoURL':self.M.config.MeetBotInfoURL, 'MeetBotVersion':MeetBotVersion(), } def iterNickCounts(self): nicks = [ (n,c) for (n,c) in self.M.attendees.iteritems() ] nicks.sort(key=lambda x: x[1], reverse=True) return nicks def iterActionItemsNick(self): for nick in sorted(self.M.attendees.keys(), key=lambda x: x.lower()): def nickitems(): for m in self.M.minutes: # The hack below is needed because of pickling problems if m.itemtype != "ACTION": continue if m.line.find(nick) == -1: continue m.assigned = True yield m yield nick, nickitems() def iterActionItemsUnassigned(self): for m in self.M.minutes: if m.itemtype != "ACTION": continue if getattr(m, 'assigned', False): continue yield m class _CSSmanager(object): _css_head = textwrap.dedent('''\ ''') def getCSS(self, name): cssfile = getattr(self.M.config, 'cssFile_'+name, '') if cssfile.lower() == 'none': # special string 'None' means no style at all return '' elif cssfile in ('', 'default'): # default CSS file css_fname = os.path.join(os.path.dirname(__file__), 'css-'+name+'-default.css') else: css_fname = cssfile try: # Stylesheet specified if getattr(self.M.config, 'cssEmbed_'+name, True): # external stylesheet css = file(css_fname).read() return self._css_head%css else: # linked stylesheet css_head = (''''''%cssfile) return css_head except Exception, exc: if not self.M.config.safeMode: raise import traceback traceback.print_exc() print "(exception above ignored, continuing)" try: css_fname = os.path.join(os.path.dirname(__file__), 'css-'+name+'-default.css') css = open(css_fname).read() return self._css_head%css except: if not self.M.config.safeMode: raise import traceback traceback.print_exc() return '' class TextLog(_BaseWriter): def format(self, extension=None): M = self.M """Write raw text logs.""" return "\n".join(M.lines) update_realtime = True class HTMLlog(_BaseWriter): def format(self, extension=None): """Write pretty HTML logs.""" M = self.M # pygments lexing setup: # (pygments HTML-formatter handles HTML-escaping) import pygments from pygments.lexers import IrcLogsLexer from pygments.formatters import HtmlFormatter import pygments.token as token from pygments.lexer import bygroups # Don't do any encoding in this function with pygments. # That's only right before the i/o functions in the Config # object. formatter = HtmlFormatter(lineanchors='l', full=True, style=M.config.pygmentizeStyle, outencoding=self.M.config.output_codec) Lexer = IrcLogsLexer Lexer.tokens['msg'][1:1] = \ [ # match: #topic commands (r"(\#topic[ \t\f\v]*)(.*\n)", bygroups(token.Keyword, token.Generic.Heading), '#pop'), # match: #command (others) (r"(\#[^\s]+[ \t\f\v]*)(.*\n)", bygroups(token.Keyword, token.Generic.Strong), '#pop'), ] lexer = Lexer() #from rkddp.interact import interact ; interact() out = pygments.highlight("\n".join(M.lines), lexer, formatter) # Hack it to add "pre { white-space: pre-wrap; }", which make # it wrap the pygments html logs. I think that in a newer # version of pygmetns, the "prestyles" HTMLFormatter option # would do this, but I want to maintain compatibility with # lenny. Thus, I do these substitution hacks to add the # format in. Thanks to a comment on the blog of Francis # Giannaros (http://francis.giannaros.org) for the suggestion # and instructions for how. out,n = re.subn(r"(\n\s*pre\s*\{[^}]+;\s*)(\})", r"\1\n white-space: pre-wrap;\2", out, count=1) if n == 0: out = re.sub(r"(\n\s*)", r"\npre { white-space: pre-wrap; }\1", out, count=1) return out class HTMLlog2(_BaseWriter, _CSSmanager): def format(self, extension=None): """Write pretty HTML logs.""" M = self.M lines = [ ] line_re = re.compile(r"""\s* (?P