rst.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. from textwrap import indent
  2. from typing import Any, Dict, Iterable, List, cast
  3. from ..core import BaseRenderer, BlockState
  4. from ..util import strip_end
  5. from ._list import render_list
  6. class RSTRenderer(BaseRenderer):
  7. """A renderer for converting Markdown to ReST."""
  8. NAME = "rst"
  9. #: marker symbols for heading
  10. HEADING_MARKERS = {
  11. 1: "=",
  12. 2: "-",
  13. 3: "~",
  14. 4: "^",
  15. 5: '"',
  16. 6: "'",
  17. }
  18. INLINE_IMAGE_PREFIX = "img-"
  19. def iter_tokens(self, tokens: Iterable[Dict[str, Any]], state: BlockState) -> Iterable[str]:
  20. prev = None
  21. for tok in tokens:
  22. # ignore blank line
  23. if tok["type"] == "blank_line":
  24. continue
  25. tok["prev"] = prev
  26. prev = tok
  27. yield self.render_token(tok, state)
  28. def __call__(self, tokens: Iterable[Dict[str, Any]], state: BlockState) -> str:
  29. state.env["inline_images"] = []
  30. out = self.render_tokens(tokens, state)
  31. # special handle for line breaks
  32. out += "\n\n".join(self.render_referrences(state)) + "\n"
  33. return strip_end(out)
  34. def render_referrences(self, state: BlockState) -> Iterable[str]:
  35. images = state.env["inline_images"]
  36. for index, token in enumerate(images):
  37. attrs = token["attrs"]
  38. alt = self.render_children(token, state)
  39. ident = self.INLINE_IMAGE_PREFIX + str(index)
  40. yield ".. |" + ident + "| image:: " + attrs["url"] + "\n :alt: " + alt
  41. def render_children(self, token: Dict[str, Any], state: BlockState) -> str:
  42. children = token["children"]
  43. return self.render_tokens(children, state)
  44. def text(self, token: Dict[str, Any], state: BlockState) -> str:
  45. text = cast(str, token["raw"])
  46. return text.replace("|", r"\|")
  47. def emphasis(self, token: Dict[str, Any], state: BlockState) -> str:
  48. return "*" + self.render_children(token, state) + "*"
  49. def strong(self, token: Dict[str, Any], state: BlockState) -> str:
  50. return "**" + self.render_children(token, state) + "**"
  51. def link(self, token: Dict[str, Any], state: BlockState) -> str:
  52. attrs = token["attrs"]
  53. text = self.render_children(token, state)
  54. return "`" + text + " <" + cast(str, attrs["url"]) + ">`__"
  55. def image(self, token: Dict[str, Any], state: BlockState) -> str:
  56. refs: List[Dict[str, Any]] = state.env["inline_images"]
  57. index = len(refs)
  58. refs.append(token)
  59. return "|" + self.INLINE_IMAGE_PREFIX + str(index) + "|"
  60. def codespan(self, token: Dict[str, Any], state: BlockState) -> str:
  61. return "``" + cast(str, token["raw"]) + "``"
  62. def linebreak(self, token: Dict[str, Any], state: BlockState) -> str:
  63. return "<linebreak>"
  64. def softbreak(self, token: Dict[str, Any], state: BlockState) -> str:
  65. return " "
  66. def inline_html(self, token: Dict[str, Any], state: BlockState) -> str:
  67. # rst does not support inline html
  68. return ""
  69. def paragraph(self, token: Dict[str, Any], state: BlockState) -> str:
  70. children = token["children"]
  71. if len(children) == 1 and children[0]["type"] == "image":
  72. image = children[0]
  73. attrs = image["attrs"]
  74. title = cast(str, attrs.get("title"))
  75. alt = self.render_children(image, state)
  76. text = ".. figure:: " + cast(str, attrs["url"])
  77. if title:
  78. text += "\n :alt: " + title
  79. text += "\n\n" + indent(alt, " ")
  80. else:
  81. text = self.render_tokens(children, state)
  82. lines = text.split("<linebreak>")
  83. if len(lines) > 1:
  84. text = "\n".join("| " + line for line in lines)
  85. return text + "\n\n"
  86. def heading(self, token: Dict[str, Any], state: BlockState) -> str:
  87. attrs = token["attrs"]
  88. text = self.render_children(token, state)
  89. marker = self.HEADING_MARKERS[attrs["level"]]
  90. return text + "\n" + marker * len(text) + "\n\n"
  91. def thematic_break(self, token: Dict[str, Any], state: BlockState) -> str:
  92. return "--------------\n\n"
  93. def block_text(self, token: Dict[str, Any], state: BlockState) -> str:
  94. return self.render_children(token, state) + "\n"
  95. def block_code(self, token: Dict[str, Any], state: BlockState) -> str:
  96. attrs = token.get("attrs", {})
  97. info = cast(str, attrs.get("info"))
  98. code = indent(cast(str, token["raw"]), " ")
  99. if info:
  100. lang = info.split()[0]
  101. return ".. code:: " + lang + "\n\n" + code + "\n"
  102. else:
  103. return "::\n\n" + code + "\n\n"
  104. def block_quote(self, token: Dict[str, Any], state: BlockState) -> str:
  105. text = indent(self.render_children(token, state), " ")
  106. prev = token["prev"]
  107. ignore_blocks = (
  108. "paragraph",
  109. "thematic_break",
  110. "linebreak",
  111. "heading",
  112. )
  113. if prev and prev["type"] not in ignore_blocks:
  114. text = "..\n\n" + text
  115. return text
  116. def block_html(self, token: Dict[str, Any], state: BlockState) -> str:
  117. raw = token["raw"]
  118. return ".. raw:: html\n\n" + indent(raw, " ") + "\n\n"
  119. def block_error(self, token: Dict[str, Any], state: BlockState) -> str:
  120. return ""
  121. def list(self, token: Dict[str, Any], state: BlockState) -> str:
  122. return render_list(self, token, state)