1 # unproudly borrowed from pypy :
2 # http://codespeak.net/svn/pypy/trunk/pypy/tool/rest/rst.py
3 """ reStructuredText generation tools
5 provides an api to build a tree from nodes, which can be converted to
6 ReStructuredText on demand
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
16 """escape ReST markup"""
17 if not isinstance(txt, str) and not isinstance(txt, unicode):
19 # XXX this takes a very naive approach to escaping, but it seems to be
22 txt = txt.replace(c, '\\%s' % (c,))
25 class RestError(Exception):
26 """ raised on containment errors (wrong parent) """
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:
34 if not isinstance(parent_cls, list):
35 class_list = [parent_cls]
37 class_list = parent_cls
39 class_list.append(obj)
41 for _class in class_list:
42 if not _class.allowed_child:
43 _class.allowed_child = {obj:True}
45 _class.allowed_child[obj] = True
48 class AbstractNode(object):
49 """ Base class implementing rest generation
52 __metaclass__ = AbstractMetaclass
53 parentclass = None # this exists to allow parent to know what
59 _reg_whitespace = re.compile('\s+')
61 def __init__(self, *args, **kwargs):
67 setattr(self, arg, kwargs[arg])
69 def join(self, *children):
72 returns a reference to self
74 for child in children:
81 returns a reference to the child
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)
93 def __getitem__(self, item):
94 return self.children[item]
96 def __setitem__(self, item, value):
97 self.children[item] = value
100 """ return a ReST string representation of the node """
101 return self.sep.join([child.text() for child in self.children])
104 """ return a list of ReST strings for this node and its children """
107 class Rest(AbstractNode):
108 """ Root node of a document """
111 def __init__(self, *args, **kwargs):
112 AbstractNode.__init__(self, *args, **kwargs)
115 def render_links(self, check=False):
116 """render the link attachments of the document"""
117 assert not check, "Link checking not implemented"
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"
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 '
132 for child in self.children:
133 outcome.append(child.text())
135 # always a trailing newline
136 text = self.sep.join([i for i in outcome if i]) + "\n"
137 return text + self.render_links()
139 class Transition(AbstractNode):
140 """ a horizontal line """
143 def __init__(self, char='-', width=80, *args, **kwargs):
146 super(Transition, self).__init__(*args, **kwargs)
149 return (self.width - 1) * self.char
151 class Paragraph(AbstractNode):
152 """ simple paragraph """
160 def __init__(self, *args, **kwargs):
163 for num, arg in enumerate(args):
164 if isinstance(arg, str):
165 args[num] = Text(arg)
166 super(Paragraph, self).__init__(*args, **kwargs)
170 for child in self.children:
171 texts += child.wordlist()
175 lgt = len(self.indent)
178 outcome.append(self.indent + self.sep.join(buf))
186 if lgt + len(self.sep) + len(next) <= self.width or not buf:
188 lgt += len(next) + len(self.sep)
192 lgt = len(self.indent)
195 return "\n".join(outcome)
197 class SubParagraph(Paragraph):
198 """ indented sub paragraph """
202 class Title(Paragraph):
203 """ title element """
210 txt = self._get_text()
213 lines.append(self.abovechar * len(txt))
216 lines.append(self.belowchar * len(txt))
217 return "\n".join(lines)
221 for node in self.children:
222 txt += node.wordlist()
225 class AbstractText(AbstractNode):
226 parentclass = [Paragraph, Title]
229 def __init__(self, _text):
233 text = self.escape(self._text)
234 return self.start + text + self.end
236 def escape(self, text):
237 if not isinstance(text, str) and not isinstance(text, unicode):
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,))
245 class Text(AbstractText):
247 text = escape(self._text)
248 return self._reg_whitespace.split(text)
250 class LiteralBlock(AbstractText):
255 if not self._text.strip():
257 text = self.escape(self._text).split('\n')
258 for i, line in enumerate(text):
260 text[i] = ' %s' % (line,)
261 return self.start + '\n'.join(text)
263 class Em(AbstractText):
267 class Strong(AbstractText):
271 class Quote(AbstractText):
275 class Anchor(AbstractText):
279 class Footnote(AbstractText):
280 def __init__(self, note, symbol=False):
281 raise NotImplemented('XXX')
283 class Citation(AbstractText):
284 def __init__(self, text, cite):
285 raise NotImplemented('XXX')
287 class ListItem(Paragraph):
292 idepth = self.get_indent_depth()
293 indent = self.indent + (idepth + 1) * ' '
294 txt = '\n\n'.join(self.render_children(indent))
296 item_char = self.item_chars[idepth]
297 ret += [indent[len(item_char)+1:], item_char, ' ', txt[len(indent):]]
300 def render_children(self, indent):
303 def render_buffer(fro, to):
306 p = Paragraph(indent=indent, *fro)
307 p.parent = self.parent
309 for child in self.children:
310 if isinstance(child, AbstractText):
314 render_buffer(buffer, txt)
316 txt.append(child.text())
318 render_buffer(buffer, txt)
321 def get_indent_depth(self):
324 while (current.parent is not None and
325 isinstance(current.parent, ListItem)):
327 current = current.parent
330 class OrderedListItem(ListItem):
331 item_chars = ["#."] * 5
333 class DListItem(ListItem):
335 def __init__(self, term, definition, *args, **kwargs):
337 super(DListItem, self).__init__(definition, *args, **kwargs)
340 idepth = self.get_indent_depth()
341 indent = self.indent + (idepth + 1) * ' '
342 txt = '\n\n'.join(self.render_children(indent))
344 ret += [indent[2:], self.term, '\n', txt]
347 class Link(AbstractText):
351 def __init__(self, _text, target):
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)
366 # XXX little overkill, but who cares...
368 while next.parent is not None:
372 class InternalLink(AbstractText):
376 class LinkTarget(Paragraph):
377 def __init__(self, name, target):
382 return ".. _`%s`:%s\n" % (self.name, self.target)
384 class Substitution(AbstractText):
385 def __init__(self, text, **kwargs):
386 raise NotImplemented('XXX')
388 class Directive(Paragraph):
390 def __init__(self, name, *args, **options):
393 super(Directive, self).__init__()
394 self.options = options
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()])
402 txt += '\n%s' % (options,)
406 for item in self.content: