7548cdd8fd2fbeff9e88240937cf2211c21f8647
[tiramisu.git] / doc / eole-report / eolreport / build / pdfreport / rst.py
1 # unproudly borrowed from pypy : 
2 # http://codespeak.net/svn/pypy/trunk/pypy/tool/rest/rst.py
3 """ reStructuredText generation tools
4
5     provides an api to build a tree from nodes, which can be converted to
6     ReStructuredText on demand
7
8     note that not all of ReST is supported, a usable subset is offered, but
9     certain features aren't supported, and also certain details (like how links
10     are generated, or how escaping is done) can not be controlled
11 """
12
13 import re
14
15 def escape(txt):
16     """escape ReST markup"""
17     if not isinstance(txt, str) and not isinstance(txt, unicode):
18         txt = str(txt)
19     # XXX this takes a very naive approach to escaping, but it seems to be
20     # sufficient...
21     for c in '\\*`|:_':
22         txt = txt.replace(c, '\\%s' % (c,))
23     return txt
24
25 class RestError(Exception):
26     """ raised on containment errors (wrong parent) """
27
28 class AbstractMetaclass(type):
29     def __new__(cls, *args):
30         obj = super(AbstractMetaclass, cls).__new__(cls, *args)
31         parent_cls = obj.parentclass
32         if parent_cls is None:
33             return obj
34         if not isinstance(parent_cls, list):
35             class_list = [parent_cls]
36         else:
37             class_list = parent_cls
38         if obj.allow_nesting:
39             class_list.append(obj)
40         
41         for _class in class_list:
42             if not _class.allowed_child:
43                 _class.allowed_child = {obj:True}
44             else:
45                 _class.allowed_child[obj] = True
46         return obj
47
48 class AbstractNode(object):
49     """ Base class implementing rest generation
50     """
51     sep = ''
52     __metaclass__ = AbstractMetaclass
53     parentclass = None # this exists to allow parent to know what
54         # children can exist
55     allow_nesting = False
56     allowed_child = {}
57     defaults = {}
58     
59     _reg_whitespace = re.compile('\s+')
60
61     def __init__(self, *args, **kwargs):
62         self.parent = None
63         self.children = []
64         for child in args:
65             self._add(child)
66         for arg in kwargs:
67             setattr(self, arg, kwargs[arg])
68     
69     def join(self, *children):
70         """ add child nodes
71         
72             returns a reference to self
73         """
74         for child in children:
75             self._add(child)
76         return self
77     
78     def add(self, child):
79         """ adds a child node
80             
81             returns a reference to the child
82         """
83         self._add(child)
84         return child
85         
86     def _add(self, child):
87         if child.__class__ not in self.allowed_child:
88             raise RestError("%r cannot be child of %r" % \
89                 (child.__class__, self.__class__))
90         self.children.append(child)
91         child.parent = self
92     
93     def __getitem__(self, item):
94         return self.children[item]
95     
96     def __setitem__(self, item, value):
97         self.children[item] = value
98
99     def text(self):
100         """ return a ReST string representation of the node """
101         return self.sep.join([child.text() for child in self.children])
102     
103     def wordlist(self):
104         """ return a list of ReST strings for this node and its children """ 
105         return [self.text()]
106
107 class Rest(AbstractNode):
108     """ Root node of a document """
109     
110     sep = "\n\n"
111     def __init__(self, *args, **kwargs):
112         AbstractNode.__init__(self, *args, **kwargs)
113         self.links = {}
114     
115     def render_links(self, check=False):
116         """render the link attachments of the document"""
117         assert not check, "Link checking not implemented"
118         if not self.links:
119             return ""
120         link_texts = []
121         # XXX this could check for duplicates and remove them...
122         for link, target in self.links.iteritems():
123             link_texts.append(".. _`%s`: %s" % (escape(link), target))
124         return "\n" + "\n".join(link_texts) + "\n\n"
125
126     def text(self):
127         outcome = []
128         if (isinstance(self.children[0], Transition) or
129                 isinstance(self.children[-1], Transition)):
130             raise ValueError, ('document must not begin or end with a '
131                                'transition')
132         for child in self.children:
133             outcome.append(child.text())
134         
135         # always a trailing newline
136         text = self.sep.join([i for i in outcome if i]) + "\n"
137         return text + self.render_links()
138
139 class Transition(AbstractNode):
140     """ a horizontal line """
141     parentclass = Rest
142
143     def __init__(self, char='-', width=80, *args, **kwargs):
144         self.char = char
145         self.width = width
146         super(Transition, self).__init__(*args, **kwargs)
147         
148     def text(self):
149         return (self.width - 1) * self.char
150
151 class Paragraph(AbstractNode):
152     """ simple paragraph """
153
154     parentclass = Rest
155     sep = " "
156     indent = ""
157     # FIXME
158     width = 880
159     
160     def __init__(self, *args, **kwargs):
161         # make shortcut
162         args = list(args)
163         for num, arg in enumerate(args):
164             if isinstance(arg, str):
165                 args[num] = Text(arg)
166         super(Paragraph, self).__init__(*args, **kwargs)
167     
168     def text(self):
169         texts = []
170         for child in self.children:
171             texts += child.wordlist()
172         
173         buf = []
174         outcome = []
175         lgt = len(self.indent)
176         
177         def grab(buf):
178             outcome.append(self.indent + self.sep.join(buf))
179         
180         texts.reverse()
181         while texts:
182             next = texts[-1]
183             if not next:
184                 texts.pop()
185                 continue
186             if lgt + len(self.sep) + len(next) <= self.width or not buf:
187                 buf.append(next)
188                 lgt += len(next) + len(self.sep)
189                 texts.pop()
190             else:
191                 grab(buf)
192                 lgt = len(self.indent)
193                 buf = []
194         grab(buf)
195         return "\n".join(outcome)
196     
197 class SubParagraph(Paragraph):
198     """ indented sub paragraph """
199
200     indent = " "
201     
202 class Title(Paragraph):
203     """ title element """
204
205     parentclass = Rest
206     belowchar = "="
207     abovechar = ""
208     
209     def text(self):
210         txt = self._get_text()
211         lines = []
212         if self.abovechar:
213             lines.append(self.abovechar * len(txt))
214         lines.append(txt)
215         if self.belowchar:
216             lines.append(self.belowchar * len(txt))
217         return "\n".join(lines)
218
219     def _get_text(self):
220         txt = []
221         for node in self.children:
222             txt += node.wordlist()
223         return ' '.join(txt)
224
225 class AbstractText(AbstractNode):
226     parentclass = [Paragraph, Title]
227     start = ""
228     end = ""
229     def __init__(self, _text):
230         self._text = _text
231     
232     def text(self):
233         text = self.escape(self._text)
234         return self.start + text + self.end
235
236     def escape(self, text):
237         if not isinstance(text, str) and not isinstance(text, unicode):
238             text = str(text)
239         if self.start:
240             text = text.replace(self.start, '\\%s' % (self.start,))
241         if self.end and self.end != self.start:
242             text = text.replace(self.end, '\\%s' % (self.end,))
243         return text
244     
245 class Text(AbstractText):
246     def wordlist(self):
247         text = escape(self._text)
248         return self._reg_whitespace.split(text)
249
250 class LiteralBlock(AbstractText):
251     parentclass = Rest
252     start = '::\n\n'
253
254     def text(self):
255         if not self._text.strip():
256             return ''
257         text = self.escape(self._text).split('\n')
258         for i, line in enumerate(text):
259             if line.strip():
260                 text[i] = '  %s' % (line,)
261         return self.start + '\n'.join(text)
262
263 class Em(AbstractText):
264     start = "*"
265     end = "*"
266
267 class Strong(AbstractText):
268     start = "**"
269     end = "**"
270
271 class Quote(AbstractText):
272     start = '``'
273     end = '``'
274
275 class Anchor(AbstractText):
276     start = '_`'
277     end = '`'
278
279 class Footnote(AbstractText):
280     def __init__(self, note, symbol=False):
281         raise NotImplemented('XXX')
282
283 class Citation(AbstractText):
284     def __init__(self, text, cite):
285         raise NotImplemented('XXX')
286
287 class ListItem(Paragraph):
288     allow_nesting = True
289     item_chars = '*+-'
290     
291     def text(self):
292         idepth = self.get_indent_depth()
293         indent = self.indent + (idepth + 1) * '  '
294         txt = '\n\n'.join(self.render_children(indent))
295         ret = []
296         item_char = self.item_chars[idepth]
297         ret += [indent[len(item_char)+1:], item_char, ' ', txt[len(indent):]]
298         return ''.join(ret)
299     
300     def render_children(self, indent):
301         txt = []
302         buffer = []
303         def render_buffer(fro, to):
304             if not fro:
305                 return
306             p = Paragraph(indent=indent, *fro)
307             p.parent = self.parent
308             to.append(p.text())
309         for child in self.children:
310             if isinstance(child, AbstractText):
311                 buffer.append(child)
312             else:
313                 if buffer:
314                     render_buffer(buffer, txt)
315                     buffer = []
316                 txt.append(child.text())
317
318         render_buffer(buffer, txt)
319         return txt
320
321     def get_indent_depth(self):
322         depth = 0
323         current = self
324         while (current.parent is not None and
325                 isinstance(current.parent, ListItem)):
326             depth += 1
327             current = current.parent
328         return depth
329
330 class OrderedListItem(ListItem):
331     item_chars = ["#."] * 5
332
333 class DListItem(ListItem):
334     item_chars = None
335     def __init__(self, term, definition, *args, **kwargs):
336         self.term = term
337         super(DListItem, self).__init__(definition, *args, **kwargs)
338
339     def text(self):
340         idepth = self.get_indent_depth()
341         indent = self.indent + (idepth + 1) * '  '
342         txt = '\n\n'.join(self.render_children(indent))
343         ret = []
344         ret += [indent[2:], self.term, '\n', txt]
345         return ''.join(ret)
346
347 class Link(AbstractText):
348     start = '`'
349     end = '`_'
350
351     def __init__(self, _text, target):
352         self._text = _text
353         self.target = target
354         self.rest = None
355     
356     def text(self):
357         if self.rest is None:
358             self.rest = self.find_rest()
359         if self.rest.links.get(self._text, self.target) != self.target:
360             raise ValueError('link name %r already in use for a different '
361                              'target' % (self.target,))
362         self.rest.links[self._text] = self.target
363         return AbstractText.text(self)
364
365     def find_rest(self):
366         # XXX little overkill, but who cares...
367         next = self
368         while next.parent is not None:
369             next = next.parent
370         return next
371
372 class InternalLink(AbstractText):
373     start = '`'
374     end = '`_'
375     
376 class LinkTarget(Paragraph):
377     def __init__(self, name, target):
378         self.name = name
379         self.target = target
380     
381     def text(self):
382         return ".. _`%s`:%s\n" % (self.name, self.target)
383
384 class Substitution(AbstractText):
385     def __init__(self, text, **kwargs):
386         raise NotImplemented('XXX')
387
388 class Directive(Paragraph):
389     indent = '   '
390     def __init__(self, name, *args, **options):
391         self.name = name
392         self.content = args
393         super(Directive, self).__init__()
394         self.options = options
395         
396     def text(self):
397         # XXX not very pretty...
398         txt = '.. %s::' % (self.name,)
399         options = '\n'.join(['    :%s: %s' % (k, v) for (k, v) in
400                              self.options.iteritems()])
401         if options:
402             txt += '\n%s' % (options,)
403
404         if self.content:
405             txt += '\n'
406             for item in self.content:
407                 txt += '\n    ' + item
408         
409         return txt
410