Merge branch 'master' into less-regex
diff --git a/tests/data/extras/invalid/localtime/incomplete-localtime.toml b/tests/data/extras/invalid/localtime/incomplete-localtime.toml
new file mode 100644
index 0000000..348904c
--- /dev/null
+++ b/tests/data/extras/invalid/localtime/incomplete-localtime.toml
@@ -0,0 +1 @@
+t=23:59:5
diff --git a/tests/data/extras/invalid/localtime/too-many-minutes.toml b/tests/data/extras/invalid/localtime/too-many-minutes.toml
new file mode 100644
index 0000000..9aa1192
--- /dev/null
+++ b/tests/data/extras/invalid/localtime/too-many-minutes.toml
@@ -0,0 +1 @@
+t=23:60:59
diff --git a/tomli/_parser.py b/tomli/_parser.py
index d77a8f6..05d3115 100644
--- a/tomli/_parser.py
+++ b/tomli/_parser.py
@@ -5,14 +5,7 @@
from types import MappingProxyType
from typing import Any, BinaryIO, NamedTuple
-from tomli._re import (
- RE_DATETIME,
- RE_LOCALTIME,
- RE_NUMBER,
- match_to_datetime,
- match_to_localtime,
- match_to_number,
-)
+from tomli._re import match_to_datetime, match_to_localtime, match_to_number, regex
from tomli._types import Key, ParseFloat, Pos
ASCII_CTRL = frozenset(chr(i) for i in range(32)) | frozenset(chr(127))
@@ -612,21 +605,31 @@
return parse_inline_table(src, pos, parse_float)
# Dates and times
- datetime_match = RE_DATETIME.match(src, pos)
- if datetime_match:
- try:
- datetime_obj = match_to_datetime(datetime_match)
- except ValueError as e:
- raise suffixed_err(src, pos, "Invalid date or datetime") from e
- return datetime_match.end(), datetime_obj
- localtime_match = RE_LOCALTIME.match(src, pos)
- if localtime_match:
- return localtime_match.end(), match_to_localtime(localtime_match)
+ try:
+ fifth_char: str | None = src[pos + 4]
+ except IndexError:
+ fifth_char = None
+ if fifth_char == "-":
+ datetime_match = regex("datetime").match(src, pos)
+ if datetime_match:
+ try:
+ datetime_obj = match_to_datetime(datetime_match)
+ except ValueError as e:
+ raise suffixed_err(src, pos, "Invalid date or datetime") from e
+ return datetime_match.end(), datetime_obj
+ try:
+ third_char: str | None = src[pos + 2]
+ except IndexError:
+ third_char = None
+ if third_char == ":":
+ localtime_match = regex("localtime").match(src, pos)
+ if localtime_match:
+ return localtime_match.end(), match_to_localtime(localtime_match)
# Integers and "normal" floats.
# The regex will greedily match any type starting with a decimal
# char, so needs to be located after handling of dates and times.
- number_match = RE_NUMBER.match(src, pos)
+ number_match = regex("number").match(src, pos)
if number_match:
return number_match.end(), match_to_number(number_match, parse_float)
diff --git a/tomli/_re.py b/tomli/_re.py
index c13269d..bcec84c 100644
--- a/tomli/_re.py
+++ b/tomli/_re.py
@@ -12,8 +12,26 @@
# - 00:32:00
_TIME_RE_STR = r"([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])(?:\.([0-9]{1,6})[0-9]*)?"
-RE_NUMBER = re.compile(
- r"""
+
+@lru_cache(maxsize=None)
+def regex(name: str) -> re.Pattern:
+ if name == "datetime":
+ return re.compile(
+ fr"""
+([0-9]{{4}})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01]) # date, e.g. 1988-10-27
+(?:
+ [Tt ]
+ {_TIME_RE_STR}
+ (?:([Zz])|([+-])([01][0-9]|2[0-3]):([0-5][0-9]))? # optional time offset
+)?
+""",
+ flags=re.VERBOSE,
+ )
+ if name == "localtime":
+ return re.compile(_TIME_RE_STR)
+ # if name == "number":
+ return re.compile(
+ r"""
0
(?:
x[0-9A-Fa-f](?:_?[0-9A-Fa-f])* # hex
@@ -29,20 +47,8 @@
(?:[eE][+-]?[0-9](?:_?[0-9])*)? # optional exponent part
)
""",
- flags=re.VERBOSE,
-)
-RE_LOCALTIME = re.compile(_TIME_RE_STR)
-RE_DATETIME = re.compile(
- fr"""
-([0-9]{{4}})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01]) # date, e.g. 1988-10-27
-(?:
- [Tt ]
- {_TIME_RE_STR}
- (?:([Zz])|([+-])([01][0-9]|2[0-3]):([0-5][0-9]))? # optional time offset
-)?
-""",
- flags=re.VERBOSE,
-)
+ flags=re.VERBOSE,
+ )
def match_to_datetime(match: re.Match) -> datetime | date: