summaryrefslogtreecommitdiff
path: root/ureports
diff options
context:
space:
mode:
authorroot <devnull@localhost>2006-04-26 10:48:09 +0000
committerroot <devnull@localhost>2006-04-26 10:48:09 +0000
commit8b1e1c104bdff504b3e775b450432e6462b8d09b (patch)
tree0367359f6a18f318741f387d82dc3dcfd8139950 /ureports
downloadlogilab-common-8b1e1c104bdff504b3e775b450432e6462b8d09b.tar.gz
forget the past.
forget the past.
Diffstat (limited to 'ureports')
-rw-r--r--ureports/__init__.py173
-rw-r--r--ureports/docbook_writer.py138
-rw-r--r--ureports/html_writer.py131
-rw-r--r--ureports/nodes.py200
-rw-r--r--ureports/text_writer.py141
5 files changed, 783 insertions, 0 deletions
diff --git a/ureports/__init__.py b/ureports/__init__.py
new file mode 100644
index 0000000..b4c6c60
--- /dev/null
+++ b/ureports/__init__.py
@@ -0,0 +1,173 @@
+# Copyright (c) 2004-2005 LOGILAB S.A. (Paris, FRANCE).
+# http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+""" Universal report objects and some formatting drivers
+
+a way to create simple reports using python objects, primarly designed to be
+formatted as text and html
+"""
+
+from __future__ import generators
+
+__revision__ = "$Id: __init__.py,v 1.8 2005-07-02 13:22:30 syt Exp $"
+
+import sys
+from os import linesep
+from cStringIO import StringIO
+from StringIO import StringIO as UStringIO
+
+
+def get_nodes(node, klass):
+ """return an iterator on all children node of the given klass"""
+ for child in node.children:
+ if isinstance(child, klass):
+ yield child
+ # recurse (FIXME: recursion controled by an option)
+ for grandchild in get_nodes(child, klass):
+ yield grandchild
+
+def layout_title(layout):
+ """try to return the layout's title as string, return None if not found
+ """
+ for child in layout.children:
+ if isinstance(child, Title):
+ return ' '.join([node.data for node in get_nodes(child, Text)])
+
+def build_summary(layout, level=1):
+ """make a summary for the report, including X level"""
+ assert level > 0
+ level -= 1
+ summary = List(klass='summary')
+ for child in layout.children:
+ if not isinstance(child, Section):
+ continue
+ label = layout_title(child)
+ if not label and not child.id:
+ continue
+ if not child.id:
+ child.id = label.replace(' ', '-')
+ node = Link('#'+child.id, label=label or child.id)
+ # FIXME: Three following lines produce not very compliant
+ # docbook: there are some useless <para><para>. They might be
+ # replaced by the three commented lines but this then produces
+ # a bug in html display...
+ if level and [n for n in child.children if isinstance(n, Section)]:
+ node = Paragraph([node, build_summary(child, level)])
+ summary.append(node)
+# summary.append(node)
+# if level and [n for n in child.children if isinstance(n, Section)]:
+# summary.append(build_summary(child, level))
+ return summary
+
+
+class BaseWriter(object):
+ """base class for ureport writers"""
+
+ def format(self, layout, stream=None, encoding=None):
+ """format and write the given layout into the stream object
+
+ unicode policy: unicode strings may be found in the layout;
+ try to call stream.write with it, but give it back encoded using
+ the given encoding if it fails
+ """
+ if stream is None:
+ stream = sys.stdout
+ if not encoding:
+ encoding = getattr(stream, 'encoding', 'UTF-8')
+ self.encoding = encoding or 'UTF-8'
+ self.__compute_funcs = []
+ self.out = stream
+ self.begin_format(layout)
+ layout.accept(self)
+ self.end_format(layout)
+
+ def format_children(self, layout):
+ """recurse on the layout children and call their accept method
+ (see the Visitor pattern)
+ """
+ for child in getattr(layout, 'children', ()):
+ child.accept(self)
+
+ def writeln(self, string=''):
+ """write a line in the output buffer"""
+ self.write(string + linesep)
+
+ def write(self, string):
+ """write a string in the output buffer"""
+ try:
+ self.out.write(string)
+ except UnicodeEncodeError:
+ self.out.write(string.encode(self.encoding))
+
+ def begin_format(self, layout):
+ """begin to format a layout"""
+ self.section = 0
+
+ def end_format(self, layout):
+ """finished to format a layout"""
+
+ def get_table_content(self, table):
+ """trick to get table content without actually writing it
+
+ return an aligned list of lists containing table cells values as string
+ """
+ result = [[]]
+ cols = table.cols
+ for cell in self.compute_content(table):
+ if cols == 0:
+ result.append([])
+ cols = table.cols
+ cols -= 1
+ result[-1].append(cell)
+ # fill missing cells
+ while len(result[-1]) < cols:
+ result[-1].append('')
+ return result
+
+ def compute_content(self, layout):
+ """trick to compute the formatting of children layout before actually
+ writing it
+
+ return an iterator on strings (one for each child element)
+ """
+ # use cells !
+ def write(data):
+ try:
+ stream.write(data)
+ except UnicodeEncodeError:
+ stream.write(data.encode(self.encoding))
+ def writeln(data=''):
+ try:
+ stream.write(data+linesep)
+ except UnicodeEncodeError:
+ stream.write(data.encode(self.encoding)+linesep)
+ self.write = write
+ self.writeln = writeln
+ self.__compute_funcs.append((write, writeln))
+ for child in layout.children:
+ stream = UStringIO()
+ child.accept(self)
+ yield stream.getvalue()
+ self.__compute_funcs.pop()
+ try:
+ self.write, self.writeln = self.__compute_funcs[-1]
+ except IndexError:
+ del self.write
+ del self.writeln
+
+
+from logilab.common.ureports.nodes import *
+from logilab.common.ureports.text_writer import TextWriter
+from logilab.common.ureports.html_writer import HTMLWriter
diff --git a/ureports/docbook_writer.py b/ureports/docbook_writer.py
new file mode 100644
index 0000000..5ce5760
--- /dev/null
+++ b/ureports/docbook_writer.py
@@ -0,0 +1,138 @@
+# Copyright (c) 2002-2004 LOGILAB S.A. (Paris, FRANCE).
+# http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+"""HTML formatting drivers for ureports
+"""
+
+__revision__ = "$Id: docbook_writer.py,v 1.4 2005-05-20 16:42:23 emb Exp $"
+
+from logilab.common.ureports import HTMLWriter
+
+class DocbookWriter(HTMLWriter):
+ """format layouts as HTML"""
+
+ def begin_format(self, layout):
+ """begin to format a layout"""
+ super(HTMLWriter, self).begin_format(layout)
+ if self.snipet is None:
+ self.writeln('<?xml version="1.0" encoding="ISO-8859-1"?>')
+ self.writeln("""
+<book xmlns:xi='http://www.w3.org/2001/XInclude'
+ lang='fr'>
+""")
+
+ def end_format(self, layout):
+ """finished to format a layout"""
+ if self.snipet is None:
+ self.writeln('</book>')
+
+ def visit_section(self, layout):
+ """display a section (using <chapter> (level 0) or <section>)"""
+ if self.section == 0:
+ tag = "chapter"
+ else:
+ tag = "section"
+ self.section += 1
+ self.writeln(self._indent('<%s%s>' % (tag, self.handle_attrs(layout))))
+ self.format_children(layout)
+ self.writeln(self._indent('</%s>'% tag))
+ self.section -= 1
+
+ def visit_title(self, layout):
+ """display a title using <title>"""
+ self.write(self._indent(' <title%s>' % self.handle_attrs(layout)))
+ self.format_children(layout)
+ self.writeln('</title>')
+
+ def visit_table(self, layout):
+ """display a table as html"""
+ self.writeln(self._indent(' <table%s><title>%s</title>' \
+ % (self.handle_attrs(layout), layout.title)))
+ self.writeln(self._indent(' <tgroup cols="%s">'% layout.cols))
+ for i in range(layout.cols):
+ self.writeln(self._indent(' <colspec colname="c%s" colwidth="1*"/>' % i))
+
+ table_content = self.get_table_content(layout)
+ # write headers
+ if layout.cheaders:
+ self.writeln(self._indent(' <thead>'))
+ self._write_row(table_content[0])
+ self.writeln(self._indent(' </thead>'))
+ table_content = table_content[1:]
+ elif layout.rcheaders:
+ self.writeln(self._indent(' <thead>'))
+ self._write_row(table_content[-1])
+ self.writeln(self._indent(' </thead>'))
+ table_content = table_content[:-1]
+ # write body
+ self.writeln(self._indent(' <tbody>'))
+ for i in range(len(table_content)):
+ row = table_content[i]
+ self.writeln(self._indent(' <row>'))
+ for j in range(len(row)):
+ cell = row[j] or '&#160;'
+ self.writeln(self._indent(' <entry>%s</entry>' % cell))
+ self.writeln(self._indent(' </row>'))
+ self.writeln(self._indent(' </tbody>'))
+ self.writeln(self._indent(' </tgroup>'))
+ self.writeln(self._indent(' </table>'))
+
+ def _write_row(self, row):
+ """write content of row (using <row> <entry>)"""
+ self.writeln(' <row>')
+ for j in range(len(row)):
+ cell = row[j] or '&#160;'
+ self.writeln(' <entry>%s</entry>' % cell)
+ self.writeln(self._indent(' </row>'))
+
+ def visit_list(self, layout):
+ """display a list (using <itemizedlist>)"""
+ self.writeln(self._indent(' <itemizedlist%s>' % self.handle_attrs(layout)))
+ for row in list(self.compute_content(layout)):
+ self.writeln(' <listitem><para>%s</para></listitem>' % row)
+ self.writeln(self._indent(' </itemizedlist>'))
+
+ def visit_paragraph(self, layout):
+ """display links (using <para>)"""
+ self.write(self._indent(' <para>'))
+ self.format_children(layout)
+ self.writeln('</para>')
+
+ def visit_span(self, layout):
+ """display links (using <p>)"""
+ #TODO: translate in docbook
+ self.write('<literal %s>' % self.handle_attrs(layout))
+ self.format_children(layout)
+ self.write('</literal>')
+
+ def visit_link(self, layout):
+ """display links (using <ulink>)"""
+ self.write('<ulink url="%s"%s>%s</ulink>' % (layout.url,
+ self.handle_attrs(layout),
+ layout.label))
+
+ def visit_verbatimtext(self, layout):
+ """display verbatim text (using <programlisting>)"""
+ self.writeln(self._indent(' <programlisting>'))
+ self.write(layout.data.replace('&', '&amp;').replace('<', '&lt;'))
+ self.writeln(self._indent(' </programlisting>'))
+
+ def visit_text(self, layout):
+ """add some text"""
+ self.write(layout.data.replace('&', '&amp;').replace('<', '&lt;'))
+
+ def _indent(self, string):
+ """correctly indent string according to section"""
+ return ' ' * 2*(self.section) + string
diff --git a/ureports/html_writer.py b/ureports/html_writer.py
new file mode 100644
index 0000000..33506d0
--- /dev/null
+++ b/ureports/html_writer.py
@@ -0,0 +1,131 @@
+# Copyright (c) 2004-2005 LOGILAB S.A. (Paris, FRANCE).
+# http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+"""HTML formatting drivers for ureports
+"""
+
+__revision__ = "$Id: html_writer.py,v 1.10 2006-03-08 09:47:29 katia Exp $"
+
+from cgi import escape
+
+from logilab.common.ureports import BaseWriter
+
+
+class HTMLWriter(BaseWriter):
+ """format layouts as HTML"""
+
+ def __init__(self, snipet=None):
+ super(HTMLWriter, self).__init__(self)
+ self.snipet = snipet
+
+ def handle_attrs(self, layout):
+ """get an attribute string from layout member attributes"""
+ attrs = ''
+ klass = getattr(layout, 'klass', None)
+ if klass:
+ attrs += ' class="%s"' % klass
+ nid = getattr(layout, 'id', None)
+ if nid:
+ attrs += ' id="%s"' % nid
+ return attrs
+
+ def begin_format(self, layout):
+ """begin to format a layout"""
+ super(HTMLWriter, self).begin_format(layout)
+ if self.snipet is None:
+ self.writeln('<html>')
+ self.writeln('<body>')
+
+ def end_format(self, layout):
+ """finished to format a layout"""
+ if self.snipet is None:
+ self.writeln('</body>')
+ self.writeln('</html>')
+
+
+ def visit_section(self, layout):
+ """display a section as html, using div + h[section level]"""
+ self.section += 1
+ self.writeln('<div%s>' % self.handle_attrs(layout))
+ self.format_children(layout)
+ self.writeln('</div>')
+ self.section -= 1
+
+ def visit_title(self, layout):
+ """display a title using <hX>"""
+ self.write('<h%s%s>' % (self.section, self.handle_attrs(layout)))
+ self.format_children(layout)
+ self.writeln('</h%s>' % self.section)
+
+ def visit_table(self, layout):
+ """display a table as html"""
+ self.writeln('<table%s>' % self.handle_attrs(layout))
+ table_content = self.get_table_content(layout)
+ for i in range(len(table_content)):
+ row = table_content[i]
+ if i == 0 and layout.rheaders:
+ self.writeln('<tr class="header">')
+ elif i+1 == len(table_content) and layout.rrheaders:
+ self.writeln('<tr class="header">')
+ else:
+ self.writeln('<tr class="%s">' % (i%2 and 'even' or 'odd'))
+ for j in range(len(row)):
+ cell = row[j] or '&nbsp;'
+ if (layout.rheaders and i == 0) or \
+ (layout.cheaders and j == 0) or \
+ (layout.rrheaders and i+1 == len(table_content)) or \
+ (layout.rcheaders and j+1 == len(row)):
+ self.writeln('<th>%s</th>' % cell)
+ else:
+ self.writeln('<td>%s</td>' % cell)
+ self.writeln('</tr>')
+ self.writeln('</table>')
+
+ def visit_list(self, layout):
+ """display a list as html"""
+ self.writeln('<ul%s>' % self.handle_attrs(layout))
+ for row in list(self.compute_content(layout)):
+ self.writeln('<li>%s</li>' % row)
+ self.writeln('</ul>')
+
+ def visit_paragraph(self, layout):
+ """display links (using <p>)"""
+ self.write('<p>')
+ self.format_children(layout)
+ self.write('</p>')
+
+ def visit_span(self, layout):
+ """display links (using <p>)"""
+ self.write('<span%s>' % self.handle_attrs(layout))
+ self.format_children(layout)
+ self.write('</span>')
+
+ def visit_link(self, layout):
+ """display links (using <a>)"""
+ self.write(' <a href="%s"%s>%s</a>' % (layout.url,
+ self.handle_attrs(layout),
+ layout.label))
+ def visit_verbatimtext(self, layout):
+ """display verbatim text (using <pre>)"""
+ self.write('<pre>')
+ self.write(layout.data.replace('&', '&amp;').replace('<', '&lt;'))
+ self.write('</pre>')
+
+ def visit_text(self, layout):
+ """add some text"""
+ data = layout.data
+ if layout.escaped:
+ data = data.replace('&', '&amp;').replace('<', '&lt;')
+ self.write(data)
diff --git a/ureports/nodes.py b/ureports/nodes.py
new file mode 100644
index 0000000..d0829ae
--- /dev/null
+++ b/ureports/nodes.py
@@ -0,0 +1,200 @@
+# Copyright (c) 2004-2005 LOGILAB S.A. (Paris, FRANCE).
+# http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+"""Universal reports objects
+
+A Universal report is a tree of layout and content objects
+"""
+
+__revision__ = "$Id: nodes.py,v 1.11 2006-03-08 09:47:38 katia Exp $"
+
+from logilab.common.tree import VNode
+
+class BaseComponent(VNode):
+ """base report component
+
+ attributes
+ * id : the component's optional id
+ * klass : the component's optional klass
+ """
+ def __init__(self, id=None, klass=None):
+ VNode.__init__(self, id)
+ self.klass = klass
+
+class BaseLayout(BaseComponent):
+ """base container node
+
+ attributes
+ * BaseComponent attributes
+ * children : components in this table (i.e. the table's cells)
+ """
+ def __init__(self, children=(), **kwargs):
+ super(BaseLayout, self).__init__(**kwargs)
+ for child in children:
+ if isinstance(child, BaseComponent):
+ self.append(child)
+ else:
+ self.add_text(child)
+
+ def append(self, child):
+ """overriden to detect problems easily"""
+ assert child not in self.parents()
+ VNode.append(self, child)
+
+ def parents(self):
+ """return the ancestor nodes"""
+ assert self.parent is not self
+ if self.parent is None:
+ return []
+ return [self.parent] + self.parent.parents()
+
+ def add_text(self, text):
+ """shortcut to add text data"""
+ self.children.append(Text(text))
+
+
+# non container nodes #########################################################
+
+class Text(BaseComponent):
+ """a text portion
+
+ attributes :
+ * BaseComponent attributes
+ * data : the text value as an encoded or unicode string
+ """
+ def __init__(self, data, escaped=True, **kwargs):
+ super(Text, self).__init__(**kwargs)
+ #if isinstance(data, unicode):
+ # data = data.encode('ascii')
+ assert isinstance(data, (str, unicode)), data.__class__
+ self.escaped = escaped
+ self.data = data
+
+class VerbatimText(Text):
+ """a verbatim text, display the raw data
+
+ attributes :
+ * BaseComponent attributes
+ * data : the text value as an encoded or unicode string
+ """
+
+class Link(BaseComponent):
+ """a labelled link
+
+ attributes :
+ * BaseComponent attributes
+ * url : the link's target (REQUIRED)
+ * label : the link's label as a string (use the url by default)
+ """
+ def __init__(self, url, label=None, **kwargs):
+ super(Link, self).__init__(**kwargs)
+ assert url
+ self.url = url
+ self.label = label or url
+
+
+class Image(BaseComponent):
+ """an embeded or a single image
+
+ attributes :
+ * BaseComponent attributes
+ * filename : the image's filename (REQUIRED)
+ * stream : the stream object containing the image data (REQUIRED)
+ * title : the image's optional title
+ """
+ def __init__(self, filename, stream, title=None, **kwargs):
+ super(Link, self).__init__(**kwargs)
+ assert filename
+ assert stream
+ self.filename = filename
+ self.stream = stream
+ self.title = title
+
+
+# container nodes #############################################################
+
+class Section(BaseLayout):
+ """a section
+
+ attributes :
+ * BaseLayout attributes
+
+ a title may also be given to the constructor, it'll be added
+ as a first element
+ a description may also be given to the constructor, it'll be added
+ as a first paragraph
+ """
+ def __init__(self, title=None, description=None, **kwargs):
+ super(Section, self).__init__(**kwargs)
+ if description:
+ self.insert(0, Paragraph([Text(description)]))
+ if title:
+ self.insert(0, Title(children=(title,)))
+
+class Title(BaseLayout):
+ """a title
+
+ attributes :
+ * BaseLayout attributes
+
+ A title must not contains a section nor a paragraph!
+ """
+
+class Span(BaseLayout):
+ """a title
+
+ attributes :
+ * BaseLayout attributes
+
+ A span should only contains Text and Link nodes (in-line elements)
+ """
+
+class Paragraph(BaseLayout):
+ """a simple text paragraph
+
+ attributes :
+ * BaseLayout attributes
+
+ A paragraph must not contains a section !
+ """
+
+class Table(BaseLayout):
+ """some tabular data
+
+ attributes :
+ * BaseLayout attributes
+ * cols : the number of columns of the table (REQUIRED)
+ * rheaders : the first row's elements are table's header
+ * cheaders : the first col's elements are table's header
+ * title : the table's optional title
+ """
+ def __init__(self, cols, title=None,
+ rheaders=0, cheaders=0, rrheaders=0, rcheaders=0,
+ **kwargs):
+ super(Table, self).__init__(**kwargs)
+ assert isinstance(cols, int)
+ self.cols = cols
+ self.title = title
+ self.rheaders = rheaders
+ self.cheaders = cheaders
+ self.rrheaders = rrheaders
+ self.rcheaders = rcheaders
+
+class List(BaseLayout):
+ """some list data
+
+ attributes :
+ * BaseLayout attributes
+ """
diff --git a/ureports/text_writer.py b/ureports/text_writer.py
new file mode 100644
index 0000000..f0a9617
--- /dev/null
+++ b/ureports/text_writer.py
@@ -0,0 +1,141 @@
+# Copyright (c) 2004-2005 LOGILAB S.A. (Paris, FRANCE).
+# http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+"""Text formatting drivers for ureports"""
+
+__revision__ = "$Id: text_writer.py,v 1.9 2005-11-22 13:13:13 syt Exp $"
+
+from os import linesep
+
+from logilab.common.ureports import BaseWriter
+
+TITLE_UNDERLINES = ['', '=', '-', '`', '.', '~', '^']
+BULLETS = ['*', '-']
+
+class TextWriter(BaseWriter):
+ """format layouts as text
+ (ReStructured inspiration but not totally handled yet)
+ """
+ def begin_format(self, layout):
+ super(TextWriter, self).begin_format(layout)
+ self.list_level = 0
+ self.pending_urls = []
+
+ def visit_section(self, layout):
+ """display a section as text
+ """
+ self.section += 1
+ self.writeln()
+ self.format_children(layout)
+ if self.pending_urls:
+ self.writeln()
+ for label, url in self.pending_urls:
+ self.writeln('.. _`%s`: %s' % (label, url))
+ self.pending_urls = []
+ self.section -= 1
+ self.writeln()
+
+ def visit_title(self, layout):
+ title = ''.join(list(self.compute_content(layout)))
+ self.writeln(title)
+ try:
+ self.writeln(TITLE_UNDERLINES[self.section] * len(title))
+ except IndexError:
+ print "FIXME TITLE TOO DEEP. TURNING TITLE INTO TEXT"
+
+ def visit_paragraph(self, layout):
+ """enter a paragraph"""
+ self.format_children(layout)
+ self.writeln()
+
+ def visit_span(self, layout):
+ """enter a span"""
+ self.format_children(layout)
+
+ def visit_table(self, layout):
+ """display a table as text"""
+ table_content = self.get_table_content(layout)
+ # get columns width
+ cols_width = [0]*len(table_content[0])
+ for row in table_content:
+ for index in range(len(row)):
+ col = row[index]
+ cols_width[index] = max(cols_width[index], len(col))
+ if layout.klass == 'field':
+ self.field_table(layout, table_content, cols_width)
+ else:
+ self.default_table(layout, table_content, cols_width)
+ self.writeln()
+
+ def default_table(self, layout, table_content, cols_width):
+ """format a table"""
+ cols_width = [size+1 for size in cols_width]
+ format_strings = ' '.join(['%%-%ss'] * len(cols_width))
+ format_strings = format_strings % tuple(cols_width)
+ format_strings = format_strings.split(' ')
+ table_linesep = '\n+' + '+'.join(['-'*w for w in cols_width]) + '+\n'
+ headsep = '\n+' + '+'.join(['='*w for w in cols_width]) + '+\n'
+ # FIXME: layout.cheaders
+ self.write(table_linesep)
+ for i in range(len(table_content)):
+ self.write('|')
+ line = table_content[i]
+ for j in range(len(line)):
+ self.write(format_strings[j] % line[j])
+ self.write('|')
+ if i == 0 and layout.rheaders:
+ self.write(headsep)
+ else:
+ self.write(table_linesep)
+
+ def field_table(self, layout, table_content, cols_width):
+ """special case for field table"""
+ assert layout.cols == 2
+ format_string = '%s%%-%ss: %%s' % (linesep, cols_width[0])
+ for field, value in table_content:
+ self.write(format_string % (field, value))
+
+
+ def visit_list(self, layout):
+ """display a list layout as text"""
+ bullet = BULLETS[self.list_level % len(BULLETS)]
+ indent = ' ' * self.list_level
+ self.list_level += 1
+ for child in layout.children:
+ self.write('%s%s%s ' % (linesep, indent, bullet))
+ child.accept(self)
+ self.list_level -= 1
+
+ def visit_link(self, layout):
+ """add a hyperlink"""
+ if layout.label != layout.url:
+ self.write('`%s`_' % layout.label)
+ self.pending_urls.append( (layout.label, layout.url) )
+ else:
+ self.write(layout.url)
+
+ def visit_verbatimtext(self, layout):
+ """display a verbatim layout as text (so difficult ;)
+ """
+ self.writeln('::\n')
+ for line in layout.data.splitlines():
+ self.writeln(' ' + line)
+ self.writeln()
+
+ def visit_text(self, layout):
+ """add some text"""
+ self.write(layout.data)
+
+