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