utils.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. from __future__ import annotations
  2. from collections.abc import MutableMapping as MutableMappingABC
  3. from pathlib import Path
  4. from typing import Any, Callable, Iterable, MutableMapping, TypedDict, cast
  5. EnvType = MutableMapping[str, Any] # note: could use TypeAlias in python 3.10
  6. """Type for the environment sandbox used in parsing and rendering,
  7. which stores mutable variables for use by plugins and rules.
  8. """
  9. class OptionsType(TypedDict):
  10. """Options for parsing."""
  11. maxNesting: int
  12. """Internal protection, recursion limit."""
  13. html: bool
  14. """Enable HTML tags in source."""
  15. linkify: bool
  16. """Enable autoconversion of URL-like texts to links."""
  17. typographer: bool
  18. """Enable smartquotes and replacements."""
  19. quotes: str
  20. """Quote characters."""
  21. xhtmlOut: bool
  22. """Use '/' to close single tags (<br />)."""
  23. breaks: bool
  24. """Convert newlines in paragraphs into <br>."""
  25. langPrefix: str
  26. """CSS language prefix for fenced blocks."""
  27. highlight: Callable[[str, str, str], str] | None
  28. """Highlighter function: (content, lang, attrs) -> str."""
  29. class PresetType(TypedDict):
  30. """Preset configuration for markdown-it."""
  31. options: OptionsType
  32. """Options for parsing."""
  33. components: MutableMapping[str, MutableMapping[str, list[str]]]
  34. """Components for parsing and rendering."""
  35. class OptionsDict(MutableMappingABC): # type: ignore
  36. """A dictionary, with attribute access to core markdownit configuration options."""
  37. # Note: ideally we would probably just remove attribute access entirely,
  38. # but we keep it for backwards compatibility.
  39. def __init__(self, options: OptionsType) -> None:
  40. self._options = cast(OptionsType, dict(options))
  41. def __getitem__(self, key: str) -> Any:
  42. return self._options[key] # type: ignore[literal-required]
  43. def __setitem__(self, key: str, value: Any) -> None:
  44. self._options[key] = value # type: ignore[literal-required]
  45. def __delitem__(self, key: str) -> None:
  46. del self._options[key] # type: ignore
  47. def __iter__(self) -> Iterable[str]: # type: ignore
  48. return iter(self._options)
  49. def __len__(self) -> int:
  50. return len(self._options)
  51. def __repr__(self) -> str:
  52. return repr(self._options)
  53. def __str__(self) -> str:
  54. return str(self._options)
  55. @property
  56. def maxNesting(self) -> int:
  57. """Internal protection, recursion limit."""
  58. return self._options["maxNesting"]
  59. @maxNesting.setter
  60. def maxNesting(self, value: int) -> None:
  61. self._options["maxNesting"] = value
  62. @property
  63. def html(self) -> bool:
  64. """Enable HTML tags in source."""
  65. return self._options["html"]
  66. @html.setter
  67. def html(self, value: bool) -> None:
  68. self._options["html"] = value
  69. @property
  70. def linkify(self) -> bool:
  71. """Enable autoconversion of URL-like texts to links."""
  72. return self._options["linkify"]
  73. @linkify.setter
  74. def linkify(self, value: bool) -> None:
  75. self._options["linkify"] = value
  76. @property
  77. def typographer(self) -> bool:
  78. """Enable smartquotes and replacements."""
  79. return self._options["typographer"]
  80. @typographer.setter
  81. def typographer(self, value: bool) -> None:
  82. self._options["typographer"] = value
  83. @property
  84. def quotes(self) -> str:
  85. """Quote characters."""
  86. return self._options["quotes"]
  87. @quotes.setter
  88. def quotes(self, value: str) -> None:
  89. self._options["quotes"] = value
  90. @property
  91. def xhtmlOut(self) -> bool:
  92. """Use '/' to close single tags (<br />)."""
  93. return self._options["xhtmlOut"]
  94. @xhtmlOut.setter
  95. def xhtmlOut(self, value: bool) -> None:
  96. self._options["xhtmlOut"] = value
  97. @property
  98. def breaks(self) -> bool:
  99. """Convert newlines in paragraphs into <br>."""
  100. return self._options["breaks"]
  101. @breaks.setter
  102. def breaks(self, value: bool) -> None:
  103. self._options["breaks"] = value
  104. @property
  105. def langPrefix(self) -> str:
  106. """CSS language prefix for fenced blocks."""
  107. return self._options["langPrefix"]
  108. @langPrefix.setter
  109. def langPrefix(self, value: str) -> None:
  110. self._options["langPrefix"] = value
  111. @property
  112. def highlight(self) -> Callable[[str, str, str], str] | None:
  113. """Highlighter function: (content, langName, langAttrs) -> escaped HTML."""
  114. return self._options["highlight"]
  115. @highlight.setter
  116. def highlight(self, value: Callable[[str, str, str], str] | None) -> None:
  117. self._options["highlight"] = value
  118. def read_fixture_file(path: str | Path) -> list[list[Any]]:
  119. text = Path(path).read_text(encoding="utf-8")
  120. tests = []
  121. section = 0
  122. last_pos = 0
  123. lines = text.splitlines(keepends=True)
  124. for i in range(len(lines)):
  125. if lines[i].rstrip() == ".":
  126. if section == 0:
  127. tests.append([i, lines[i - 1].strip()])
  128. section = 1
  129. elif section == 1:
  130. tests[-1].append("".join(lines[last_pos + 1 : i]))
  131. section = 2
  132. elif section == 2:
  133. tests[-1].append("".join(lines[last_pos + 1 : i]))
  134. section = 0
  135. last_pos = i
  136. return tests