图片解析应用
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

232 lines
9.4 KiB

  1. import re
  2. from abc import ABC, abstractmethod
  3. from typing import List, Union
  4. from .text import Span, Text
  5. def _combine_regex(*regexes: str) -> str:
  6. """Combine a number of regexes in to a single regex.
  7. Returns:
  8. str: New regex with all regexes ORed together.
  9. """
  10. return "|".join(regexes)
  11. class Highlighter(ABC):
  12. """Abstract base class for highlighters."""
  13. def __call__(self, text: Union[str, Text]) -> Text:
  14. """Highlight a str or Text instance.
  15. Args:
  16. text (Union[str, ~Text]): Text to highlight.
  17. Raises:
  18. TypeError: If not called with text or str.
  19. Returns:
  20. Text: A test instance with highlighting applied.
  21. """
  22. if isinstance(text, str):
  23. highlight_text = Text(text)
  24. elif isinstance(text, Text):
  25. highlight_text = text.copy()
  26. else:
  27. raise TypeError(f"str or Text instance required, not {text!r}")
  28. self.highlight(highlight_text)
  29. return highlight_text
  30. @abstractmethod
  31. def highlight(self, text: Text) -> None:
  32. """Apply highlighting in place to text.
  33. Args:
  34. text (~Text): A text object highlight.
  35. """
  36. class NullHighlighter(Highlighter):
  37. """A highlighter object that doesn't highlight.
  38. May be used to disable highlighting entirely.
  39. """
  40. def highlight(self, text: Text) -> None:
  41. """Nothing to do"""
  42. class RegexHighlighter(Highlighter):
  43. """Applies highlighting from a list of regular expressions."""
  44. highlights: List[str] = []
  45. base_style: str = ""
  46. def highlight(self, text: Text) -> None:
  47. """Highlight :class:`rich.text.Text` using regular expressions.
  48. Args:
  49. text (~Text): Text to highlighted.
  50. """
  51. highlight_regex = text.highlight_regex
  52. for re_highlight in self.highlights:
  53. highlight_regex(re_highlight, style_prefix=self.base_style)
  54. class ReprHighlighter(RegexHighlighter):
  55. """Highlights the text typically produced from ``__repr__`` methods."""
  56. base_style = "repr."
  57. highlights = [
  58. r"(?P<tag_start><)(?P<tag_name>[-\w.:|]*)(?P<tag_contents>[\w\W]*)(?P<tag_end>>)",
  59. r'(?P<attrib_name>[\w_]{1,50})=(?P<attrib_value>"?[\w_]+"?)?',
  60. r"(?P<brace>[][{}()])",
  61. _combine_regex(
  62. r"(?P<ipv4>[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})",
  63. r"(?P<ipv6>([A-Fa-f0-9]{1,4}::?){1,7}[A-Fa-f0-9]{1,4})",
  64. r"(?P<eui64>(?:[0-9A-Fa-f]{1,2}-){7}[0-9A-Fa-f]{1,2}|(?:[0-9A-Fa-f]{1,2}:){7}[0-9A-Fa-f]{1,2}|(?:[0-9A-Fa-f]{4}\.){3}[0-9A-Fa-f]{4})",
  65. r"(?P<eui48>(?:[0-9A-Fa-f]{1,2}-){5}[0-9A-Fa-f]{1,2}|(?:[0-9A-Fa-f]{1,2}:){5}[0-9A-Fa-f]{1,2}|(?:[0-9A-Fa-f]{4}\.){2}[0-9A-Fa-f]{4})",
  66. r"(?P<uuid>[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12})",
  67. r"(?P<call>[\w.]*?)\(",
  68. r"\b(?P<bool_true>True)\b|\b(?P<bool_false>False)\b|\b(?P<none>None)\b",
  69. r"(?P<ellipsis>\.\.\.)",
  70. r"(?P<number_complex>(?<!\w)(?:\-?[0-9]+\.?[0-9]*(?:e[-+]?\d+?)?)(?:[-+](?:[0-9]+\.?[0-9]*(?:e[-+]?\d+)?))?j)",
  71. r"(?P<number>(?<!\w)\-?[0-9]+\.?[0-9]*(e[-+]?\d+?)?\b|0x[0-9a-fA-F]*)",
  72. r"(?P<path>\B(/[-\w._+]+)*\/)(?P<filename>[-\w._+]*)?",
  73. r"(?<![\\\w])(?P<str>b?'''.*?(?<!\\)'''|b?'.*?(?<!\\)'|b?\"\"\".*?(?<!\\)\"\"\"|b?\".*?(?<!\\)\")",
  74. r"(?P<url>(file|https|http|ws|wss)://[-0-9a-zA-Z$_+!`(),.?/;:&=%#]*)",
  75. ),
  76. ]
  77. class JSONHighlighter(RegexHighlighter):
  78. """Highlights JSON"""
  79. # Captures the start and end of JSON strings, handling escaped quotes
  80. JSON_STR = r"(?<![\\\w])(?P<str>b?\".*?(?<!\\)\")"
  81. JSON_WHITESPACE = {" ", "\n", "\r", "\t"}
  82. base_style = "json."
  83. highlights = [
  84. _combine_regex(
  85. r"(?P<brace>[\{\[\(\)\]\}])",
  86. r"\b(?P<bool_true>true)\b|\b(?P<bool_false>false)\b|\b(?P<null>null)\b",
  87. r"(?P<number>(?<!\w)\-?[0-9]+\.?[0-9]*(e[\-\+]?\d+?)?\b|0x[0-9a-fA-F]*)",
  88. JSON_STR,
  89. ),
  90. ]
  91. def highlight(self, text: Text) -> None:
  92. super().highlight(text)
  93. # Additional work to handle highlighting JSON keys
  94. plain = text.plain
  95. append = text.spans.append
  96. whitespace = self.JSON_WHITESPACE
  97. for match in re.finditer(self.JSON_STR, plain):
  98. start, end = match.span()
  99. cursor = end
  100. while cursor < len(plain):
  101. char = plain[cursor]
  102. cursor += 1
  103. if char == ":":
  104. append(Span(start, end, "json.key"))
  105. elif char in whitespace:
  106. continue
  107. break
  108. class ISO8601Highlighter(RegexHighlighter):
  109. """Highlights the ISO8601 date time strings.
  110. Regex reference: https://www.oreilly.com/library/view/regular-expressions-cookbook/9781449327453/ch04s07.html
  111. """
  112. base_style = "iso8601."
  113. highlights = [
  114. #
  115. # Dates
  116. #
  117. # Calendar month (e.g. 2008-08). The hyphen is required
  118. r"^(?P<year>[0-9]{4})-(?P<month>1[0-2]|0[1-9])$",
  119. # Calendar date w/o hyphens (e.g. 20080830)
  120. r"^(?P<date>(?P<year>[0-9]{4})(?P<month>1[0-2]|0[1-9])(?P<day>3[01]|0[1-9]|[12][0-9]))$",
  121. # Ordinal date (e.g. 2008-243). The hyphen is optional
  122. r"^(?P<date>(?P<year>[0-9]{4})-?(?P<day>36[0-6]|3[0-5][0-9]|[12][0-9]{2}|0[1-9][0-9]|00[1-9]))$",
  123. #
  124. # Weeks
  125. #
  126. # Week of the year (e.g., 2008-W35). The hyphen is optional
  127. r"^(?P<date>(?P<year>[0-9]{4})-?W(?P<week>5[0-3]|[1-4][0-9]|0[1-9]))$",
  128. # Week date (e.g., 2008-W35-6). The hyphens are optional
  129. r"^(?P<date>(?P<year>[0-9]{4})-?W(?P<week>5[0-3]|[1-4][0-9]|0[1-9])-?(?P<day>[1-7]))$",
  130. #
  131. # Times
  132. #
  133. # Hours and minutes (e.g., 17:21). The colon is optional
  134. r"^(?P<time>(?P<hour>2[0-3]|[01][0-9]):?(?P<minute>[0-5][0-9]))$",
  135. # Hours, minutes, and seconds w/o colons (e.g., 172159)
  136. r"^(?P<time>(?P<hour>2[0-3]|[01][0-9])(?P<minute>[0-5][0-9])(?P<second>[0-5][0-9]))$",
  137. # Time zone designator (e.g., Z, +07 or +07:00). The colons and the minutes are optional
  138. r"^(?P<timezone>(Z|[+-](?:2[0-3]|[01][0-9])(?::?(?:[0-5][0-9]))?))$",
  139. # Hours, minutes, and seconds with time zone designator (e.g., 17:21:59+07:00).
  140. # All the colons are optional. The minutes in the time zone designator are also optional
  141. r"^(?P<time>(?P<hour>2[0-3]|[01][0-9])(?P<minute>[0-5][0-9])(?P<second>[0-5][0-9]))(?P<timezone>Z|[+-](?:2[0-3]|[01][0-9])(?::?(?:[0-5][0-9]))?)$",
  142. #
  143. # Date and Time
  144. #
  145. # Calendar date with hours, minutes, and seconds (e.g., 2008-08-30 17:21:59 or 20080830 172159).
  146. # A space is required between the date and the time. The hyphens and colons are optional.
  147. # This regex matches dates and times that specify some hyphens or colons but omit others.
  148. # This does not follow ISO 8601
  149. r"^(?P<date>(?P<year>[0-9]{4})(?P<hyphen>-)?(?P<month>1[0-2]|0[1-9])(?(hyphen)-)(?P<day>3[01]|0[1-9]|[12][0-9])) (?P<time>(?P<hour>2[0-3]|[01][0-9])(?(hyphen):)(?P<minute>[0-5][0-9])(?(hyphen):)(?P<second>[0-5][0-9]))$",
  150. #
  151. # XML Schema dates and times
  152. #
  153. # Date, with optional time zone (e.g., 2008-08-30 or 2008-08-30+07:00).
  154. # Hyphens are required. This is the XML Schema 'date' type
  155. r"^(?P<date>(?P<year>-?(?:[1-9][0-9]*)?[0-9]{4})-(?P<month>1[0-2]|0[1-9])-(?P<day>3[01]|0[1-9]|[12][0-9]))(?P<timezone>Z|[+-](?:2[0-3]|[01][0-9]):[0-5][0-9])?$",
  156. # Time, with optional fractional seconds and time zone (e.g., 01:45:36 or 01:45:36.123+07:00).
  157. # There is no limit on the number of digits for the fractional seconds. This is the XML Schema 'time' type
  158. r"^(?P<time>(?P<hour>2[0-3]|[01][0-9]):(?P<minute>[0-5][0-9]):(?P<second>[0-5][0-9])(?P<frac>\.[0-9]+)?)(?P<timezone>Z|[+-](?:2[0-3]|[01][0-9]):[0-5][0-9])?$",
  159. # Date and time, with optional fractional seconds and time zone (e.g., 2008-08-30T01:45:36 or 2008-08-30T01:45:36.123Z).
  160. # This is the XML Schema 'dateTime' type
  161. r"^(?P<date>(?P<year>-?(?:[1-9][0-9]*)?[0-9]{4})-(?P<month>1[0-2]|0[1-9])-(?P<day>3[01]|0[1-9]|[12][0-9]))T(?P<time>(?P<hour>2[0-3]|[01][0-9]):(?P<minute>[0-5][0-9]):(?P<second>[0-5][0-9])(?P<ms>\.[0-9]+)?)(?P<timezone>Z|[+-](?:2[0-3]|[01][0-9]):[0-5][0-9])?$",
  162. ]
  163. if __name__ == "__main__": # pragma: no cover
  164. from .console import Console
  165. console = Console()
  166. console.print("[bold green]hello world![/bold green]")
  167. console.print("'[bold green]hello world![/bold green]'")
  168. console.print(" /foo")
  169. console.print("/foo/")
  170. console.print("/foo/bar")
  171. console.print("foo/bar/baz")
  172. console.print("/foo/bar/baz?foo=bar+egg&egg=baz")
  173. console.print("/foo/bar/baz/")
  174. console.print("/foo/bar/baz/egg")
  175. console.print("/foo/bar/baz/egg.py")
  176. console.print("/foo/bar/baz/egg.py word")
  177. console.print(" /foo/bar/baz/egg.py word")
  178. console.print("foo /foo/bar/baz/egg.py word")
  179. console.print("foo /foo/bar/ba._++z/egg+.py word")
  180. console.print("https://example.org?foo=bar#header")
  181. console.print(1234567.34)
  182. console.print(1 / 2)
  183. console.print(-1 / 123123123123)
  184. console.print(
  185. "127.0.1.1 bar 192.168.1.4 2001:0db8:85a3:0000:0000:8a2e:0370:7334 foo"
  186. )
  187. import json
  188. console.print_json(json.dumps(obj={"name": "apple", "count": 1}), indent=None)