675 lines
22 KiB
Python
675 lines
22 KiB
Python
# 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 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,
|
|
output_encoding=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)
|
|
return out
|
|
|
|
|
|
class HTML(_BaseWriter):
|
|
|
|
body = textwrap.dedent('''\
|
|
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
|
<html>
|
|
<head>
|
|
<meta http-equiv="Content-type" content="text/html;charset=UTF-8">
|
|
<title>%(pageTitle)s</title>
|
|
</head>
|
|
<body>
|
|
<h1>%(pageTitle)s</h1>
|
|
Meeting started by %(owner)s at %(starttime)s %(timeZone)s.
|
|
(<a href="%(fullLogs)s">full logs</a>)<br>
|
|
|
|
|
|
<table border=1>
|
|
%(MeetingItems)s
|
|
</table>
|
|
Meeting ended at %(endtime)s %(timeZone)s.
|
|
(<a href="%(fullLogs)s">full logs</a>)
|
|
|
|
<br><br><br>
|
|
|
|
<b>Action Items</b><ol>
|
|
%(ActionItems)s
|
|
</ol>
|
|
<br>
|
|
|
|
<b>Action Items, by person</b>
|
|
<ol>
|
|
%(ActionItemsPerson)s
|
|
</ol><br>
|
|
|
|
<b>People Present (lines said):</b><ol>
|
|
%(PeoplePresent)s
|
|
</ol>
|
|
|
|
<br>
|
|
Generated by <a href="%(MeetBotInfoURL)s">MeetBot</a>%(MeetBotVersion)s.
|
|
</body></html>
|
|
''')
|
|
|
|
def format(self, extension=None):
|
|
"""Write the minutes summary."""
|
|
M = self.M
|
|
|
|
# Add all minute items to the table
|
|
MeetingItems = [ ]
|
|
for m in M.minutes:
|
|
MeetingItems.append(m.html(M))
|
|
MeetingItems = "\n".join(MeetingItems)
|
|
|
|
# Action Items
|
|
ActionItems = [ ]
|
|
for m in M.minutes:
|
|
# The hack below is needed because of pickling problems
|
|
if m.itemtype != "ACTION": continue
|
|
ActionItems.append(" <li>%s</li>"%html(m.line))
|
|
if len(ActionItems) == 0:
|
|
ActionItems.append(" <li>(none)</li>")
|
|
ActionItems = "\n".join(ActionItems)
|
|
|
|
# Action Items, by person (This could be made lots more efficient)
|
|
ActionItemsPerson = [ ]
|
|
for nick, items in self.iterActionItemsNick():
|
|
headerPrinted = False
|
|
for m in items:
|
|
if not headerPrinted:
|
|
ActionItemsPerson.append(" <li> %s <ol>"%html(nick))
|
|
headerPrinted = True
|
|
ActionItemsPerson.append(" <li>%s</li>"%html(m.line))
|
|
if headerPrinted:
|
|
ActionItemsPerson.append(" </ol></li>")
|
|
# unassigned items:
|
|
ActionItemsPerson.append(" <li><b>UNASSIGNED</b><ol>")
|
|
numberUnassigned = 0
|
|
for m in self.iterActionItemsUnassigned():
|
|
ActionItemsPerson.append(" <li>%s</li>"%html(m.line))
|
|
numberUnassigned += 1
|
|
if numberUnassigned == 0:
|
|
ActionItemsPerson.append(" <li>(none)</li>")
|
|
ActionItemsPerson.append(' </ol>\n</li>')
|
|
ActionItemsPerson = "\n".join(ActionItemsPerson)
|
|
|
|
# People Attending
|
|
PeoplePresent = [ ]
|
|
# sort by number of lines spoken
|
|
for nick, count in self.iterNickCounts():
|
|
PeoplePresent.append(' <li>%s (%s)</li>'%(html(nick), count))
|
|
PeoplePresent = "\n".join(PeoplePresent)
|
|
|
|
# Actual formatting and replacement
|
|
repl = self.replacements()
|
|
repl.update({'MeetingItems':MeetingItems,
|
|
'ActionItems': ActionItems,
|
|
'ActionItemsPerson': ActionItemsPerson,
|
|
'PeoplePresent':PeoplePresent,
|
|
})
|
|
body = self.body
|
|
body = body%repl
|
|
body = replaceWRAP(body)
|
|
return body
|
|
class HTML2(_BaseWriter):
|
|
|
|
body = textwrap.dedent('''\
|
|
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
|
<html>
|
|
<head>
|
|
<meta http-equiv="Content-type" content="text/html;charset=UTF-8">
|
|
<title>%(pageTitle)s</title>
|
|
</head>
|
|
<body>
|
|
<h1>%(pageTitle)s</h1>
|
|
Meeting started by %(owner)s at %(starttime)s %(timeZone)s.
|
|
(<a href="%(fullLogs)s">full logs</a>)<br>
|
|
|
|
%(MeetingItems)s
|
|
Meeting ended at %(endtime)s %(timeZone)s.
|
|
(<a href="%(fullLogs)s">full logs</a>)
|
|
|
|
<br><br><br>
|
|
|
|
<b>Action Items</b><ol>
|
|
%(ActionItems)s
|
|
</ol>
|
|
<br>
|
|
|
|
<b>Action Items, by person</b>
|
|
<ol>
|
|
%(ActionItemsPerson)s
|
|
</ol><br>
|
|
|
|
<b>People Present (lines said):</b><ol>
|
|
%(PeoplePresent)s
|
|
</ol>
|
|
|
|
<br>
|
|
Generated by <a href="%(MeetBotInfoURL)s">MeetBot</a>%(MeetBotVersion)s.
|
|
</body></html>
|
|
''')
|
|
|
|
def format(self, extension=None):
|
|
"""Write the minutes summary."""
|
|
M = self.M
|
|
|
|
# Add all minute items to the table
|
|
MeetingItems = [ ]
|
|
MeetingItems.append("<ol>")
|
|
|
|
|
|
|
|
haveTopic = None
|
|
inSublist = False
|
|
for m in M.minutes:
|
|
item = '<li>'+m.html2(M)
|
|
if m.itemtype == "TOPIC":
|
|
if inSublist:
|
|
MeetingItems.append("</ol>")
|
|
inSublist = False
|
|
if haveTopic:
|
|
MeetingItems.append("<br></li>")
|
|
item = item
|
|
haveTopic = True
|
|
else:
|
|
if not inSublist:
|
|
MeetingItems.append('<ol type="a">')
|
|
inSublist = True
|
|
if haveTopic: item = wrapList(item, 2)+"</li>"
|
|
else: item = wrapList(item, 0)+"</li>"
|
|
MeetingItems.append(item)
|
|
#MeetingItems.append("</li>")
|
|
|
|
if inSublist:
|
|
MeetingItems.append("</ol>")
|
|
if haveTopic:
|
|
MeetingItems.append("</li>")
|
|
|
|
|
|
|
|
MeetingItems.append("</ol>")
|
|
MeetingItems = "\n".join(MeetingItems)
|
|
|
|
# Action Items
|
|
ActionItems = [ ]
|
|
for m in M.minutes:
|
|
# The hack below is needed because of pickling problems
|
|
if m.itemtype != "ACTION": continue
|
|
ActionItems.append(" <li>%s</li>"%html(m.line))
|
|
if len(ActionItems) == 0:
|
|
ActionItems.append(" <li>(none)</li>")
|
|
ActionItems = "\n".join(ActionItems)
|
|
|
|
# Action Items, by person (This could be made lots more efficient)
|
|
ActionItemsPerson = [ ]
|
|
for nick, items in self.iterActionItemsNick():
|
|
headerPrinted = False
|
|
for m in items:
|
|
if not headerPrinted:
|
|
ActionItemsPerson.append(" <li> %s <ol>"%html(nick))
|
|
headerPrinted = True
|
|
ActionItemsPerson.append(" <li>%s</li>"%html(m.line))
|
|
if headerPrinted:
|
|
ActionItemsPerson.append(" </ol></li>")
|
|
# unassigned items:
|
|
ActionItemsPerson.append(" <li><b>UNASSIGNED</b><ol>")
|
|
numberUnassigned = 0
|
|
for m in self.iterActionItemsUnassigned():
|
|
ActionItemsPerson.append(" <li>%s</li>"%html(m.line))
|
|
numberUnassigned += 1
|
|
if numberUnassigned == 0:
|
|
ActionItemsPerson.append(" <li>(none)</li>")
|
|
ActionItemsPerson.append(' </ol>\n</li>')
|
|
ActionItemsPerson = "\n".join(ActionItemsPerson)
|
|
|
|
# People Attending
|
|
PeoplePresent = [ ]
|
|
# sort by number of lines spoken
|
|
for nick, count in self.iterNickCounts():
|
|
PeoplePresent.append(' <li>%s (%s)</li>'%(html(nick), count))
|
|
PeoplePresent = "\n".join(PeoplePresent)
|
|
|
|
# Actual formatting and replacement
|
|
repl = self.replacements()
|
|
repl.update({'MeetingItems':MeetingItems,
|
|
'ActionItems': ActionItems,
|
|
'ActionItemsPerson': ActionItemsPerson,
|
|
'PeoplePresent':PeoplePresent,
|
|
})
|
|
body = self.body
|
|
body = body%repl
|
|
body = replaceWRAP(body)
|
|
return body
|
|
|
|
|
|
class ReST(_BaseWriter):
|
|
|
|
body = textwrap.dedent("""\
|
|
%(titleBlock)s
|
|
%(pageTitle)s
|
|
%(titleBlock)s
|
|
|
|
|
|
sWRAPsMeeting started by %(owner)s at %(starttime)s %(timeZone)s.
|
|
The `full logs`_ are available.eWRAPe
|
|
|
|
.. _`full logs`: %(fullLogs)s
|
|
|
|
|
|
|
|
Meeting log
|
|
-----------
|
|
%(MeetingItems)s
|
|
|
|
Meeting ended at %(endtime)s %(timeZone)s.
|
|
|
|
|
|
|
|
|
|
Action Items
|
|
------------
|
|
%(ActionItems)s
|
|
|
|
|
|
|
|
|
|
Action Items, by person
|
|
-----------------------
|
|
%(ActionItemsPerson)s
|
|
|
|
|
|
|
|
|
|
People Present (lines said)
|
|
---------------------------
|
|
%(PeoplePresent)s
|
|
|
|
|
|
|
|
|
|
Generated by `MeetBot`_%(MeetBotVersion)s
|
|
|
|
.. _`MeetBot`: %(MeetBotInfoURL)s
|
|
""")
|
|
|
|
def format(self, extension=None):
|
|
"""Return a ReStructured Text minutes summary."""
|
|
M = self.M
|
|
|
|
# Agenda items
|
|
MeetingItems = [ ]
|
|
M.rst_urls = [ ]
|
|
M.rst_refs = { }
|
|
haveTopic = None
|
|
for m in M.minutes:
|
|
item = "* "+m.rst(M)
|
|
if m.itemtype == "TOPIC":
|
|
if haveTopic:
|
|
MeetingItems.append("")
|
|
item = wrapList(item, 0)
|
|
haveTopic = True
|
|
else:
|
|
if haveTopic: item = wrapList(item, 2)
|
|
else: item = wrapList(item, 0)
|
|
MeetingItems.append(item)
|
|
MeetingItems = '\n\n'.join(MeetingItems)
|
|
MeetingURLs = "\n".join(M.rst_urls)
|
|
del M.rst_urls, M.rst_refs
|
|
MeetingItems = MeetingItems + '\n\n'+MeetingURLs
|
|
|
|
# Action Items
|
|
ActionItems = [ ]
|
|
for m in M.minutes:
|
|
# The hack below is needed because of pickling problems
|
|
if m.itemtype != "ACTION": continue
|
|
#already escaped
|
|
ActionItems.append(wrapList("* %s"%rst(m.line), indent=0))
|
|
ActionItems = "\n\n".join(ActionItems)
|
|
|
|
# Action Items, by person (This could be made lots more efficient)
|
|
ActionItemsPerson = [ ]
|
|
for nick in sorted(M.attendees.keys(), key=lambda x: x.lower()):
|
|
headerPrinted = False
|
|
for m in M.minutes:
|
|
# The hack below is needed because of pickling problems
|
|
if m.itemtype != "ACTION": continue
|
|
if m.line.find(nick) == -1: continue
|
|
if not headerPrinted:
|
|
ActionItemsPerson.append("* %s"%rst(nick))
|
|
headerPrinted = True
|
|
ActionItemsPerson.append(wrapList("* %s"%rst(m.line), 2))
|
|
m.assigned = True
|
|
# unassigned items:
|
|
ActionItemsPerson.append("* **UNASSIGNED**")
|
|
numberUnassigned = 0
|
|
for m in M.minutes:
|
|
if m.itemtype != "ACTION": continue
|
|
if getattr(m, 'assigned', False): continue
|
|
ActionItemsPerson.append(wrapList("* %s"%rst(m.line), 2))
|
|
numberUnassigned += 1
|
|
if numberUnassigned == 0: ActionItemsPerson.append(" * (none)")
|
|
ActionItemsPerson = "\n\n".join(ActionItemsPerson)
|
|
|
|
# People Attending
|
|
PeoplePresent = [ ]
|
|
# sort by number of lines spoken
|
|
for nick, count in self.iterNickCounts():
|
|
PeoplePresent.append('* %s (%s)'%(rst(nick), count))
|
|
PeoplePresent = "\n\n".join(PeoplePresent)
|
|
|
|
# Actual formatting and replacement
|
|
repl = self.replacements()
|
|
repl.update({'titleBlock':('='*len(repl['pageTitle'])),
|
|
'MeetingItems':MeetingItems,
|
|
'ActionItems': ActionItems,
|
|
'ActionItemsPerson': ActionItemsPerson,
|
|
'PeoplePresent':PeoplePresent,
|
|
})
|
|
body = self.body
|
|
body = body%repl
|
|
body = replaceWRAP(body)
|
|
return body
|
|
|
|
class HTMLfromReST(_BaseWriter):
|
|
|
|
def format(self, extension=None):
|
|
M = self.M
|
|
import docutils.core
|
|
rst = ReST(M).format(extension)
|
|
rstToHTML = docutils.core.publish_string(rst, writer_name='html',
|
|
settings_overrides={'file_insertion_enabled': 0,
|
|
'raw_enabled': 0,
|
|
'output_encoding':self.M.config.output_codec})
|
|
return rstToHTML
|
|
|
|
|
|
|
|
class Text(_BaseWriter):
|
|
|
|
body = textwrap.dedent("""\
|
|
%(titleBlock)s
|
|
%(pageTitle)s
|
|
%(titleBlock)s
|
|
|
|
|
|
sWRAPsMeeting started by %(owner)s at %(starttime)s %(timeZone)s.
|
|
The full logs are available at %(fullLogsFullURL)s .eWRAPe
|
|
|
|
|
|
|
|
Meeting log
|
|
-----------
|
|
%(MeetingItems)s
|
|
|
|
Meeting ended at %(endtime)s %(timeZone)s.
|
|
|
|
|
|
|
|
|
|
Action Items
|
|
------------
|
|
%(ActionItems)s
|
|
|
|
|
|
|
|
|
|
Action Items, by person
|
|
-----------------------
|
|
%(ActionItemsPerson)s
|
|
|
|
|
|
|
|
|
|
People Present (lines said)
|
|
---------------------------
|
|
%(PeoplePresent)s
|
|
|
|
|
|
|
|
|
|
Generated by `MeetBot`_%(MeetBotVersion)s
|
|
|
|
.. _`MeetBot`: %(MeetBotInfoURL)s
|
|
""")
|
|
|
|
def format(self, extension=None):
|
|
"""Return a ReStructured Text minutes summary."""
|
|
M = self.M
|
|
|
|
# Agenda items
|
|
MeetingItems = [ ]
|
|
#M.rst_urls = [ ]
|
|
#M.rst_refs = { }
|
|
haveTopic = None
|
|
for m in M.minutes:
|
|
item = "* "+m.text(M)
|
|
if m.itemtype == "TOPIC":
|
|
if haveTopic:
|
|
MeetingItems.append("")
|
|
item = wrapList(item, 0)
|
|
haveTopic = True
|
|
else:
|
|
if haveTopic: item = wrapList(item, 2)
|
|
else: item = wrapList(item, 0)
|
|
MeetingItems.append(item)
|
|
MeetingItems = '\n'.join(MeetingItems)
|
|
#MeetingURLs = "\n".join(M.rst_urls)
|
|
#del M.rst_urls, M.rst_refs
|
|
MeetingItems = MeetingItems# + '\n\n'+MeetingURLs
|
|
|
|
# Action Items
|
|
ActionItems = [ ]
|
|
for m in M.minutes:
|
|
# The hack below is needed because of pickling problems
|
|
if m.itemtype != "ACTION": continue
|
|
#already escaped
|
|
ActionItems.append(wrapList("* %s"%text(m.line), indent=0))
|
|
ActionItems = "\n".join(ActionItems)
|
|
|
|
# Action Items, by person (This could be made lots more efficient)
|
|
ActionItemsPerson = [ ]
|
|
for nick in sorted(M.attendees.keys(), key=lambda x: x.lower()):
|
|
headerPrinted = False
|
|
for m in M.minutes:
|
|
# The hack below is needed because of pickling problems
|
|
if m.itemtype != "ACTION": continue
|
|
if m.line.find(nick) == -1: continue
|
|
if not headerPrinted:
|
|
ActionItemsPerson.append("* %s"%text(nick))
|
|
headerPrinted = True
|
|
ActionItemsPerson.append(wrapList("* %s"%text(m.line), 2))
|
|
m.assigned = True
|
|
# unassigned items:
|
|
ActionItemsPerson.append("* **UNASSIGNED**")
|
|
numberUnassigned = 0
|
|
for m in M.minutes:
|
|
if m.itemtype != "ACTION": continue
|
|
if getattr(m, 'assigned', False): continue
|
|
ActionItemsPerson.append(wrapList("* %s"%text(m.line), 2))
|
|
numberUnassigned += 1
|
|
if numberUnassigned == 0: ActionItemsPerson.append(" * (none)")
|
|
ActionItemsPerson = "\n".join(ActionItemsPerson)
|
|
|
|
# People Attending
|
|
PeoplePresent = [ ]
|
|
# sort by number of lines spoken
|
|
for nick, count in self.iterNickCounts():
|
|
PeoplePresent.append('* %s (%s)'%(text(nick), count))
|
|
PeoplePresent = "\n".join(PeoplePresent)
|
|
|
|
# Actual formatting and replacement
|
|
repl = self.replacements()
|
|
repl.update({'titleBlock':('='*len(repl['pageTitle'])),
|
|
'MeetingItems':MeetingItems,
|
|
'ActionItems': ActionItems,
|
|
'ActionItemsPerson': ActionItemsPerson,
|
|
'PeoplePresent':PeoplePresent,
|
|
})
|
|
body = self.body
|
|
body = body%repl
|
|
body = replaceWRAP(body)
|
|
return body
|
|
|