highlighter.py 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. from abc import ABC, abstractmethod
  2. from typing import List, Union
  3. from .text import Text
  4. def _combine_regex(*regexes: str) -> str:
  5. """Combine a number of regexes in to a single regex.
  6. Returns:
  7. str: New regex with all regexes ORed together.
  8. """
  9. return "|".join(regexes)
  10. class Highlighter(ABC):
  11. """Abstract base class for highlighters."""
  12. def __call__(self, text: Union[str, Text]) -> Text:
  13. """Highlight a str or Text instance.
  14. Args:
  15. text (Union[str, ~Text]): Text to highlight.
  16. Raises:
  17. TypeError: If not called with text or str.
  18. Returns:
  19. Text: A test instance with highlighting applied.
  20. """
  21. if isinstance(text, str):
  22. highlight_text = Text(text)
  23. elif isinstance(text, Text):
  24. highlight_text = text.copy()
  25. else:
  26. raise TypeError(f"str or Text instance required, not {text!r}")
  27. self.highlight(highlight_text)
  28. return highlight_text
  29. @abstractmethod
  30. def highlight(self, text: Text) -> None:
  31. """Apply highlighting in place to text.
  32. Args:
  33. text (~Text): A text object highlight.
  34. """
  35. class NullHighlighter(Highlighter):
  36. """A highlighter object that doesn't highlight.
  37. May be used to disable highlighting entirely.
  38. """
  39. def highlight(self, text: Text) -> None:
  40. """Nothing to do"""
  41. class RegexHighlighter(Highlighter):
  42. """Applies highlighting from a list of regular expressions."""
  43. highlights: List[str] = []
  44. base_style: str = ""
  45. def highlight(self, text: Text) -> None:
  46. """Highlight :class:`rich.text.Text` using regular expressions.
  47. Args:
  48. text (~Text): Text to highlighted.
  49. """
  50. highlight_regex = text.highlight_regex
  51. for re_highlight in self.highlights:
  52. highlight_regex(re_highlight, style_prefix=self.base_style)
  53. class ReprHighlighter(RegexHighlighter):
  54. """Highlights the text typically produced from ``__repr__`` methods."""
  55. base_style = "repr."
  56. highlights = [
  57. r"(?P<tag_start>\<)(?P<tag_name>[\w\-\.\:]*)(?P<tag_contents>[\w\W]*?)(?P<tag_end>\>)",
  58. r"(?P<attrib_name>[\w_]{1,50})=(?P<attrib_value>\"?[\w_]+\"?)?",
  59. r"(?P<brace>[\{\[\(\)\]\}])",
  60. _combine_regex(
  61. r"(?P<ipv4>[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})",
  62. r"(?P<ipv6>([A-Fa-f0-9]{1,4}::?){1,7}[A-Fa-f0-9]{1,4})",
  63. 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})",
  64. 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})",
  65. r"(?P<call>[\w\.]*?)\(",
  66. r"\b(?P<bool_true>True)\b|\b(?P<bool_false>False)\b|\b(?P<none>None)\b",
  67. r"(?P<ellipsis>\.\.\.)",
  68. r"(?P<number>(?<!\w)\-?[0-9]+\.?[0-9]*(e[\-\+]?\d+?)?\b|0x[0-9a-fA-F]*)",
  69. r"(?P<path>\B(\/[\w\.\-\_\+]+)*\/)(?P<filename>[\w\.\-\_\+]*)?",
  70. r"(?<![\\\w])(?P<str>b?\'\'\'.*?(?<!\\)\'\'\'|b?\'.*?(?<!\\)\'|b?\"\"\".*?(?<!\\)\"\"\"|b?\".*?(?<!\\)\")",
  71. 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})",
  72. r"(?P<url>(file|https|http|ws|wss):\/\/[0-9a-zA-Z\$\-\_\+\!`\(\)\,\.\?\/\;\:\&\=\%\#]*)",
  73. ),
  74. ]
  75. class JSONHighlighter(RegexHighlighter):
  76. """Highlights JSON"""
  77. base_style = "json."
  78. highlights = [
  79. _combine_regex(
  80. r"(?P<brace>[\{\[\(\)\]\}])",
  81. r"\b(?P<bool_true>true)\b|\b(?P<bool_false>false)\b|\b(?P<null>null)\b",
  82. r"(?P<number>(?<!\w)\-?[0-9]+\.?[0-9]*(e[\-\+]?\d+?)?\b|0x[0-9a-fA-F]*)",
  83. r"(?<![\\\w])(?P<str>b?\".*?(?<!\\)\")",
  84. ),
  85. r"(?<![\\\w])(?P<key>b?\".*?(?<!\\)\")\:",
  86. ]
  87. if __name__ == "__main__": # pragma: no cover
  88. from .console import Console
  89. console = Console()
  90. console.print("[bold green]hello world![/bold green]")
  91. console.print("'[bold green]hello world![/bold green]'")
  92. console.print(" /foo")
  93. console.print("/foo/")
  94. console.print("/foo/bar")
  95. console.print("foo/bar/baz")
  96. console.print("/foo/bar/baz?foo=bar+egg&egg=baz")
  97. console.print("/foo/bar/baz/")
  98. console.print("/foo/bar/baz/egg")
  99. console.print("/foo/bar/baz/egg.py")
  100. console.print("/foo/bar/baz/egg.py word")
  101. console.print(" /foo/bar/baz/egg.py word")
  102. console.print("foo /foo/bar/baz/egg.py word")
  103. console.print("foo /foo/bar/ba._++z/egg+.py word")
  104. console.print("https://example.org?foo=bar#header")
  105. console.print(1234567.34)
  106. console.print(1 / 2)
  107. console.print(-1 / 123123123123)
  108. console.print(
  109. "127.0.1.1 bar 192.168.1.4 2001:0db8:85a3:0000:0000:8a2e:0370:7334 foo"
  110. )