Add Iso8601Highlighter

Add ability to highlight date and time strings according to ISO8601 [1]

[1] https://en.wikipedia.org/wiki/ISO_8601
This commit is contained in:
Tomer Shalev 2022-05-26 21:59:46 +03:00
parent aa7926c143
commit 2707a73435
6 changed files with 326 additions and 1 deletions

View File

@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Fixed
- Fix text wrapping edge case https://github.com/Textualize/rich/pull/2296
- Allow exceptions that are raised while a Live is rendered to be displayed and/or processed https://github.com/Textualize/rich/pull/2305
- Fix crashes that can happen with `inspect` when docstrings contain some special control codes https://github.com/Textualize/rich/pull/2294
- Fix edges used in first row of tables when `show_header=False` https://github.com/Textualize/rich/pull/2330
## [12.4.4] - 2022-05-24
### Changed

View File

@ -40,3 +40,6 @@ The following people have contributed to the development of Rich:
- [Gabriele N. Tornetta](https://github.com/p403n1x87)
- [Arian Mollik Wasi](https://github.com/wasi-master)
- [Handhika Yanuar Pratama](https://github.com/theDreamer911)
- [za](https://github.com/za)
- [Motahhar Mokfi](https://github.com/motahhar)
- [Tomer Shalev](https://github.com/tomers)

View File

@ -4,6 +4,7 @@ Highlighting
============
Rich can apply styles to patterns in text which you :meth:`~rich.console.Console.print` or :meth:`~rich.console.Console.log`. With the default settings, Rich will highlight things such as numbers, strings, collections, booleans, None, and a few more exotic patterns such as file paths, URLs and UUIDs.
Additional non-default highlighter are available, such as :class:`~rich.highlighter.ISO8601Highlighter` to highlight date and time.
You can disable highlighting either by setting ``highlight=False`` on :meth:`~rich.console.Console.print` or :meth:`~rich.console.Console.log`, or by setting ``highlight=False`` on the :class:`~rich.console.Console` constructor which disables it everywhere. If you disable highlighting on the constructor, you can still selectively *enable* highlighting with ``highlight=True`` on print/log.

View File

@ -157,6 +157,9 @@ DEFAULT_STYLES: Dict[str, Style] = {
"markdown.h7": Style(italic=True, dim=True),
"markdown.link": Style(color="bright_blue"),
"markdown.link_url": Style(color="blue"),
"iso8601.date": Style(color="blue"),
"iso8601.time": Style(color="magenta"),
"iso8601.timezone": Style(color="yellow"),
}

View File

@ -140,6 +140,64 @@ class JSONHighlighter(RegexHighlighter):
break
class ISO8601Highlighter(RegexHighlighter):
"""Highlights the ISO8601 date time strings.
Regex reference: https://www.oreilly.com/library/view/regular-expressions-cookbook/9781449327453/ch04s07.html
"""
base_style = "iso8601."
highlights = [
#
# Dates
#
# Calendar month (e.g. 2008-08). The hyphen is required
r"^(?P<year>[0-9]{4})-(?P<month>1[0-2]|0[1-9])$",
# Calendar date w/o hyphens (e.g. 20080830)
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]))$",
# Ordinal date (e.g. 2008-243). The hyphen is optional
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]))$",
#
# Weeks
#
# Week of the year (e.g., 2008-W35). The hyphen is optional
r"^(?P<date>(?P<year>[0-9]{4})-?W(?P<week>5[0-3]|[1-4][0-9]|0[1-9]))$",
# Week date (e.g., 2008-W35-6). The hyphens are optional
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]))$",
#
# Times
#
# Hours and minutes (e.g., 17:21). The colon is optional
r"^(?P<time>(?P<hour>2[0-3]|[01][0-9]):?(?P<minute>[0-5][0-9]))$",
# Hours, minutes, and seconds w/o colons (e.g., 172159)
r"^(?P<time>(?P<hour>2[0-3]|[01][0-9])(?P<minute>[0-5][0-9])(?P<second>[0-5][0-9]))$",
# Time zone designator (e.g., Z, +07 or +07:00). The colons and the minutes are optional
r"^(?P<timezone>(Z|[+-](?:2[0-3]|[01][0-9])(?::?(?:[0-5][0-9]))?))$",
# Hours, minutes, and seconds with time zone designator (e.g., 17:21:59+07:00).
# All the colons are optional. The minutes in the time zone designator are also optional
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]))?)$",
#
# Date and Time
#
# Calendar date with hours, minutes, and seconds (e.g., 2008-08-30 17:21:59 or 20080830 172159).
# A space is required between the date and the time. The hyphens and colons are optional.
# This regex matches dates and times that specify some hyphens or colons but omit others.
# This does not follow ISO 8601
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]))$",
#
# XML Schema dates and times
#
# Date, with optional time zone (e.g., 2008-08-30 or 2008-08-30+07:00).
# Hyphens are required. This is the XML Schema 'date' type
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])?$",
# Time, with optional fractional seconds and time zone (e.g., 01:45:36 or 01:45:36.123+07:00).
# There is no limit on the number of digits for the fractional seconds. This is the XML Schema 'time' type
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])?$",
# Date and time, with optional fractional seconds and time zone (e.g., 2008-08-30T01:45:36 or 2008-08-30T01:45:36.123Z).
# This is the XML Schema 'dateTime' type
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])?$",
]
if __name__ == "__main__": # pragma: no cover
from .console import Console

View File

@ -4,7 +4,12 @@ from typing import List
import pytest
from rich.highlighter import JSONHighlighter, NullHighlighter, ReprHighlighter
from rich.highlighter import (
JSONHighlighter,
NullHighlighter,
ReprHighlighter,
ISO8601Highlighter,
)
from rich.text import Span, Text
@ -179,3 +184,249 @@ def test_highlight_json_no_indent():
Span(1, 7, "json.key"),
Span(18, 25, "json.key"),
]
iso8601_highlight_tests = [
("2008-08", [Span(0, 4, "iso8601.year"), Span(5, 7, "iso8601.month")]),
(
"2008-08-30",
[
Span(0, 10, "iso8601.date"),
Span(0, 4, "iso8601.year"),
Span(5, 7, "iso8601.month"),
Span(8, 10, "iso8601.day"),
],
),
(
"20080830",
[
Span(0, 8, "iso8601.date"),
Span(0, 4, "iso8601.year"),
Span(4, 6, "iso8601.month"),
Span(6, 8, "iso8601.day"),
],
),
(
"2008-243",
[
Span(0, 8, "iso8601.date"),
Span(0, 4, "iso8601.year"),
Span(5, 8, "iso8601.day"),
],
),
(
"2008243",
[
Span(0, 7, "iso8601.date"),
Span(0, 4, "iso8601.year"),
Span(4, 7, "iso8601.day"),
],
),
(
"2008-W35",
[
Span(0, 8, "iso8601.date"),
Span(0, 4, "iso8601.year"),
Span(6, 8, "iso8601.week"),
],
),
(
"2008W35",
[
Span(0, 7, "iso8601.date"),
Span(0, 4, "iso8601.year"),
Span(5, 7, "iso8601.week"),
],
),
(
"2008-W35-6",
[
Span(0, 10, "iso8601.date"),
Span(0, 4, "iso8601.year"),
Span(6, 8, "iso8601.week"),
Span(9, 10, "iso8601.day"),
],
),
(
"2008W356",
[
Span(0, 8, "iso8601.date"),
Span(0, 4, "iso8601.year"),
Span(5, 7, "iso8601.week"),
Span(7, 8, "iso8601.day"),
],
),
(
"17:21",
[
Span(0, 5, "iso8601.time"),
Span(0, 2, "iso8601.hour"),
Span(3, 5, "iso8601.minute"),
],
),
(
"1721",
[
Span(0, 4, "iso8601.time"),
Span(0, 2, "iso8601.hour"),
Span(2, 4, "iso8601.minute"),
],
),
(
"172159",
[
Span(0, 6, "iso8601.time"),
Span(0, 2, "iso8601.hour"),
Span(2, 4, "iso8601.minute"),
Span(4, 6, "iso8601.second"),
],
),
("Z", [Span(0, 1, "iso8601.timezone")]),
("+07", [Span(0, 3, "iso8601.timezone")]),
("+07:00", [Span(0, 6, "iso8601.timezone")]),
(
"17:21:59+07:00",
[
Span(0, 8, "iso8601.time"),
Span(0, 2, "iso8601.hour"),
Span(3, 5, "iso8601.minute"),
Span(6, 8, "iso8601.second"),
Span(8, 14, "iso8601.timezone"),
],
),
(
"172159+0700",
[
Span(0, 6, "iso8601.time"),
Span(0, 2, "iso8601.hour"),
Span(2, 4, "iso8601.minute"),
Span(4, 6, "iso8601.second"),
Span(6, 11, "iso8601.timezone"),
],
),
(
"172159+07",
[
Span(0, 6, "iso8601.time"),
Span(0, 2, "iso8601.hour"),
Span(2, 4, "iso8601.minute"),
Span(4, 6, "iso8601.second"),
Span(6, 9, "iso8601.timezone"),
],
),
(
"2008-08-30 17:21:59",
[
Span(0, 10, "iso8601.date"),
Span(0, 4, "iso8601.year"),
Span(4, 5, "iso8601.hyphen"),
Span(5, 7, "iso8601.month"),
Span(8, 10, "iso8601.day"),
Span(11, 19, "iso8601.time"),
Span(11, 13, "iso8601.hour"),
Span(14, 16, "iso8601.minute"),
Span(17, 19, "iso8601.second"),
],
),
(
"20080830 172159",
[
Span(0, 8, "iso8601.date"),
Span(0, 4, "iso8601.year"),
Span(4, 6, "iso8601.month"),
Span(6, 8, "iso8601.day"),
Span(9, 15, "iso8601.time"),
Span(9, 11, "iso8601.hour"),
Span(11, 13, "iso8601.minute"),
Span(13, 15, "iso8601.second"),
],
),
(
"2008-08-30",
[
Span(0, 10, "iso8601.date"),
Span(0, 4, "iso8601.year"),
Span(5, 7, "iso8601.month"),
Span(8, 10, "iso8601.day"),
],
),
(
"2008-08-30+07:00",
[
Span(0, 10, "iso8601.date"),
Span(0, 4, "iso8601.year"),
Span(5, 7, "iso8601.month"),
Span(8, 10, "iso8601.day"),
Span(10, 16, "iso8601.timezone"),
],
),
(
"01:45:36",
[
Span(0, 8, "iso8601.time"),
Span(0, 2, "iso8601.hour"),
Span(3, 5, "iso8601.minute"),
Span(6, 8, "iso8601.second"),
],
),
(
"01:45:36.123+07:00",
[
Span(0, 12, "iso8601.time"),
Span(0, 2, "iso8601.hour"),
Span(3, 5, "iso8601.minute"),
Span(6, 8, "iso8601.second"),
Span(8, 12, "iso8601.frac"),
Span(12, 18, "iso8601.timezone"),
],
),
(
"01:45:36.123+07:00",
[
Span(0, 12, "iso8601.time"),
Span(0, 2, "iso8601.hour"),
Span(3, 5, "iso8601.minute"),
Span(6, 8, "iso8601.second"),
Span(8, 12, "iso8601.frac"),
Span(12, 18, "iso8601.timezone"),
],
),
(
"2008-08-30T01:45:36",
[
Span(0, 10, "iso8601.date"),
Span(0, 4, "iso8601.year"),
Span(5, 7, "iso8601.month"),
Span(8, 10, "iso8601.day"),
Span(11, 19, "iso8601.time"),
Span(11, 13, "iso8601.hour"),
Span(14, 16, "iso8601.minute"),
Span(17, 19, "iso8601.second"),
],
),
(
"2008-08-30T01:45:36.123Z",
[
Span(0, 10, "iso8601.date"),
Span(0, 4, "iso8601.year"),
Span(5, 7, "iso8601.month"),
Span(8, 10, "iso8601.day"),
Span(11, 23, "iso8601.time"),
Span(11, 13, "iso8601.hour"),
Span(14, 16, "iso8601.minute"),
Span(17, 19, "iso8601.second"),
Span(19, 23, "iso8601.ms"),
Span(23, 24, "iso8601.timezone"),
],
),
]
@pytest.mark.parametrize("test, spans", iso8601_highlight_tests)
def test_highlight_iso8601_regex(test: str, spans: List[Span]):
"""Tests for the regular expressions used in ISO8601Highlighter."""
text = Text(test)
highlighter = ISO8601Highlighter()
highlighter.highlight(text)
print(text.spans)
assert text.spans == spans