formatting.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. import re
  2. from typing import TYPE_CHECKING, Match, Optional, Pattern
  3. from ..helpers import PREVENT_BACKSLASH
  4. if TYPE_CHECKING:
  5. from ..core import BaseRenderer, InlineState
  6. from ..inline_parser import InlineParser
  7. from ..markdown import Markdown
  8. __all__ = ["strikethrough", "mark", "insert", "superscript", "subscript"]
  9. _STRIKE_END = re.compile(r"(?:" + PREVENT_BACKSLASH + r"\\~|[^\s~])~~(?!~)")
  10. _MARK_END = re.compile(r"(?:" + PREVENT_BACKSLASH + r"\\=|[^\s=])==(?!=)")
  11. _INSERT_END = re.compile(r"(?:" + PREVENT_BACKSLASH + r"\\\^|[^\s^])\^\^(?!\^)")
  12. SUPERSCRIPT_PATTERN = r"\^(?:" + PREVENT_BACKSLASH + r"\\\^|\S|\\ )+?\^"
  13. SUBSCRIPT_PATTERN = r"~(?:" + PREVENT_BACKSLASH + r"\\~|\S|\\ )+?~"
  14. def parse_strikethrough(inline: "InlineParser", m: Match[str], state: "InlineState") -> Optional[int]:
  15. return _parse_to_end(inline, m, state, "strikethrough", _STRIKE_END)
  16. def render_strikethrough(renderer: "BaseRenderer", text: str) -> str:
  17. return "<del>" + text + "</del>"
  18. def parse_mark(inline: "InlineParser", m: Match[str], state: "InlineState") -> Optional[int]:
  19. return _parse_to_end(inline, m, state, "mark", _MARK_END)
  20. def render_mark(renderer: "BaseRenderer", text: str) -> str:
  21. return "<mark>" + text + "</mark>"
  22. def parse_insert(inline: "InlineParser", m: Match[str], state: "InlineState") -> Optional[int]:
  23. return _parse_to_end(inline, m, state, "insert", _INSERT_END)
  24. def render_insert(renderer: "BaseRenderer", text: str) -> str:
  25. return "<ins>" + text + "</ins>"
  26. def parse_superscript(inline: "InlineParser", m: Match[str], state: "InlineState") -> int:
  27. return _parse_script(inline, m, state, "superscript")
  28. def render_superscript(renderer: "BaseRenderer", text: str) -> str:
  29. return "<sup>" + text + "</sup>"
  30. def parse_subscript(inline: "InlineParser", m: Match[str], state: "InlineState") -> int:
  31. return _parse_script(inline, m, state, "subscript")
  32. def render_subscript(renderer: "BaseRenderer", text: str) -> str:
  33. return "<sub>" + text + "</sub>"
  34. def _parse_to_end(
  35. inline: "InlineParser",
  36. m: Match[str],
  37. state: "InlineState",
  38. tok_type: str,
  39. end_pattern: Pattern[str],
  40. ) -> Optional[int]:
  41. pos = m.end()
  42. m1 = end_pattern.search(state.src, pos)
  43. if not m1:
  44. return None
  45. end_pos = m1.end()
  46. text = state.src[pos : end_pos - 2]
  47. new_state = state.copy()
  48. new_state.src = text
  49. children = inline.render(new_state)
  50. state.append_token({"type": tok_type, "children": children})
  51. return end_pos
  52. def _parse_script(inline: "InlineParser", m: Match[str], state: "InlineState", tok_type: str) -> int:
  53. text = m.group(0)
  54. new_state = state.copy()
  55. new_state.src = text[1:-1].replace("\\ ", " ")
  56. children = inline.render(new_state)
  57. state.append_token({"type": tok_type, "children": children})
  58. return m.end()
  59. def strikethrough(md: "Markdown") -> None:
  60. """A mistune plugin to support strikethrough. Spec defined by
  61. GitHub flavored Markdown and commonly used by many parsers:
  62. .. code-block:: text
  63. ~~This was mistaken text~~
  64. It will be converted into HTML:
  65. .. code-block:: html
  66. <del>This was mistaken text</del>
  67. :param md: Markdown instance
  68. """
  69. md.inline.register(
  70. "strikethrough",
  71. r"~~(?=[^\s~])",
  72. parse_strikethrough,
  73. before="link",
  74. )
  75. if md.renderer and md.renderer.NAME == "html":
  76. md.renderer.register("strikethrough", render_strikethrough)
  77. def mark(md: "Markdown") -> None:
  78. """A mistune plugin to add ``<mark>`` tag. Spec defined at
  79. https://facelessuser.github.io/pymdown-extensions/extensions/mark/:
  80. .. code-block:: text
  81. ==mark me== ==mark \\=\\= equal==
  82. :param md: Markdown instance
  83. """
  84. md.inline.register(
  85. "mark",
  86. r"==(?=[^\s=])",
  87. parse_mark,
  88. before="link",
  89. )
  90. if md.renderer and md.renderer.NAME == "html":
  91. md.renderer.register("mark", render_mark)
  92. def insert(md: "Markdown") -> None:
  93. """A mistune plugin to add ``<ins>`` tag. Spec defined at
  94. https://facelessuser.github.io/pymdown-extensions/extensions/caret/#insert:
  95. .. code-block:: text
  96. ^^insert me^^
  97. :param md: Markdown instance
  98. """
  99. md.inline.register(
  100. "insert",
  101. r"\^\^(?=[^\s\^])",
  102. parse_insert,
  103. before="link",
  104. )
  105. if md.renderer and md.renderer.NAME == "html":
  106. md.renderer.register("insert", render_insert)
  107. def superscript(md: "Markdown") -> None:
  108. """A mistune plugin to add ``<sup>`` tag. Spec defined at
  109. https://pandoc.org/MANUAL.html#superscripts-and-subscripts:
  110. .. code-block:: text
  111. 2^10^ is 1024.
  112. :param md: Markdown instance
  113. """
  114. md.inline.register("superscript", SUPERSCRIPT_PATTERN, parse_superscript, before="linebreak")
  115. if md.renderer and md.renderer.NAME == "html":
  116. md.renderer.register("superscript", render_superscript)
  117. def subscript(md: "Markdown") -> None:
  118. """A mistune plugin to add ``<sub>`` tag. Spec defined at
  119. https://pandoc.org/MANUAL.html#superscripts-and-subscripts:
  120. .. code-block:: text
  121. H~2~O is a liquid.
  122. :param md: Markdown instance
  123. """
  124. md.inline.register("subscript", SUBSCRIPT_PATTERN, parse_subscript, before="linebreak")
  125. if md.renderer and md.renderer.NAME == "html":
  126. md.renderer.register("subscript", render_subscript)