PageRenderTime 76ms CodeModel.GetById 26ms RepoModel.GetById 1ms app.codeStats 0ms

/pypy/tool/rest/rst.py

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