omdev 0.0.0.dev267__py3-none-any.whl → 0.0.0.dev269__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- omdev/scripts/pyproject.py +582 -583
- {omdev-0.0.0.dev267.dist-info → omdev-0.0.0.dev269.dist-info}/METADATA +2 -2
- {omdev-0.0.0.dev267.dist-info → omdev-0.0.0.dev269.dist-info}/RECORD +7 -7
- {omdev-0.0.0.dev267.dist-info → omdev-0.0.0.dev269.dist-info}/WHEEL +0 -0
- {omdev-0.0.0.dev267.dist-info → omdev-0.0.0.dev269.dist-info}/entry_points.txt +0 -0
- {omdev-0.0.0.dev267.dist-info → omdev-0.0.0.dev269.dist-info}/licenses/LICENSE +0 -0
- {omdev-0.0.0.dev267.dist-info → omdev-0.0.0.dev269.dist-info}/top_level.txt +0 -0
omdev/scripts/pyproject.py
CHANGED
@@ -911,75 +911,6 @@ class WheelFile(zipfile.ZipFile):
|
|
911
911
|
##
|
912
912
|
|
913
913
|
|
914
|
-
_TOML_TIME_RE_STR = r'([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])(?:\.([0-9]{1,6})[0-9]*)?'
|
915
|
-
|
916
|
-
TOML_RE_NUMBER = re.compile(
|
917
|
-
r"""
|
918
|
-
0
|
919
|
-
(?:
|
920
|
-
x[0-9A-Fa-f](?:_?[0-9A-Fa-f])* # hex
|
921
|
-
|
|
922
|
-
b[01](?:_?[01])* # bin
|
923
|
-
|
|
924
|
-
o[0-7](?:_?[0-7])* # oct
|
925
|
-
)
|
926
|
-
|
|
927
|
-
[+-]?(?:0|[1-9](?:_?[0-9])*) # dec, integer part
|
928
|
-
(?P<floatpart>
|
929
|
-
(?:\.[0-9](?:_?[0-9])*)? # optional fractional part
|
930
|
-
(?:[eE][+-]?[0-9](?:_?[0-9])*)? # optional exponent part
|
931
|
-
)
|
932
|
-
""",
|
933
|
-
flags=re.VERBOSE,
|
934
|
-
)
|
935
|
-
TOML_RE_LOCALTIME = re.compile(_TOML_TIME_RE_STR)
|
936
|
-
TOML_RE_DATETIME = re.compile(
|
937
|
-
rf"""
|
938
|
-
([0-9]{{4}})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01]) # date, e.g. 1988-10-27
|
939
|
-
(?:
|
940
|
-
[Tt ]
|
941
|
-
{_TOML_TIME_RE_STR}
|
942
|
-
(?:([Zz])|([+-])([01][0-9]|2[0-3]):([0-5][0-9]))? # optional time offset
|
943
|
-
)?
|
944
|
-
""",
|
945
|
-
flags=re.VERBOSE,
|
946
|
-
)
|
947
|
-
|
948
|
-
|
949
|
-
def toml_match_to_datetime(match: re.Match) -> ta.Union[datetime.datetime, datetime.date]:
|
950
|
-
"""Convert a `RE_DATETIME` match to `datetime.datetime` or `datetime.date`.
|
951
|
-
|
952
|
-
Raises ValueError if the match does not correspond to a valid date or datetime.
|
953
|
-
"""
|
954
|
-
(
|
955
|
-
year_str,
|
956
|
-
month_str,
|
957
|
-
day_str,
|
958
|
-
hour_str,
|
959
|
-
minute_str,
|
960
|
-
sec_str,
|
961
|
-
micros_str,
|
962
|
-
zulu_time,
|
963
|
-
offset_sign_str,
|
964
|
-
offset_hour_str,
|
965
|
-
offset_minute_str,
|
966
|
-
) = match.groups()
|
967
|
-
year, month, day = int(year_str), int(month_str), int(day_str)
|
968
|
-
if hour_str is None:
|
969
|
-
return datetime.date(year, month, day)
|
970
|
-
hour, minute, sec = int(hour_str), int(minute_str), int(sec_str)
|
971
|
-
micros = int(micros_str.ljust(6, '0')) if micros_str else 0
|
972
|
-
if offset_sign_str:
|
973
|
-
tz: ta.Optional[datetime.tzinfo] = toml_cached_tz(
|
974
|
-
offset_hour_str, offset_minute_str, offset_sign_str,
|
975
|
-
)
|
976
|
-
elif zulu_time:
|
977
|
-
tz = datetime.UTC
|
978
|
-
else: # local date-time
|
979
|
-
tz = None
|
980
|
-
return datetime.datetime(year, month, day, hour, minute, sec, micros, tzinfo=tz)
|
981
|
-
|
982
|
-
|
983
914
|
@functools.lru_cache() # noqa
|
984
915
|
def toml_cached_tz(hour_str: str, minute_str: str, sign_str: str) -> datetime.timezone:
|
985
916
|
sign = 1 if sign_str == '+' else -1
|
@@ -991,47 +922,25 @@ def toml_cached_tz(hour_str: str, minute_str: str, sign_str: str) -> datetime.ti
|
|
991
922
|
)
|
992
923
|
|
993
924
|
|
994
|
-
def
|
995
|
-
|
996
|
-
|
997
|
-
return datetime.time(int(hour_str), int(minute_str), int(sec_str), micros)
|
998
|
-
|
999
|
-
|
1000
|
-
def toml_match_to_number(match: re.Match, parse_float: TomlParseFloat) -> ta.Any:
|
1001
|
-
if match.group('floatpart'):
|
1002
|
-
return parse_float(match.group())
|
1003
|
-
return int(match.group(), 0)
|
1004
|
-
|
1005
|
-
|
1006
|
-
TOML_ASCII_CTRL = frozenset(chr(i) for i in range(32)) | frozenset(chr(127))
|
1007
|
-
|
1008
|
-
# Neither of these sets include quotation mark or backslash. They are currently handled as separate cases in the parser
|
1009
|
-
# functions.
|
1010
|
-
TOML_ILLEGAL_BASIC_STR_CHARS = TOML_ASCII_CTRL - frozenset('\t')
|
1011
|
-
TOML_ILLEGAL_MULTILINE_BASIC_STR_CHARS = TOML_ASCII_CTRL - frozenset('\t\n')
|
925
|
+
def toml_make_safe_parse_float(parse_float: TomlParseFloat) -> TomlParseFloat:
|
926
|
+
"""
|
927
|
+
A decorator to make `parse_float` safe.
|
1012
928
|
|
1013
|
-
|
1014
|
-
|
929
|
+
`parse_float` must not return dicts or lists, because these types would be mixed with parsed TOML tables and arrays,
|
930
|
+
thus confusing the parser. The returned decorated callable raises `ValueError` instead of returning illegal types.
|
931
|
+
"""
|
1015
932
|
|
1016
|
-
|
933
|
+
# The default `float` callable never returns illegal types. Optimize it.
|
934
|
+
if parse_float is float:
|
935
|
+
return float
|
1017
936
|
|
1018
|
-
|
1019
|
-
|
1020
|
-
|
1021
|
-
|
1022
|
-
|
937
|
+
def safe_parse_float(float_str: str) -> ta.Any:
|
938
|
+
float_value = parse_float(float_str)
|
939
|
+
if isinstance(float_value, (dict, list)):
|
940
|
+
raise ValueError('parse_float must not return dicts or lists') # noqa
|
941
|
+
return float_value
|
1023
942
|
|
1024
|
-
|
1025
|
-
{
|
1026
|
-
'\\b': '\u0008', # backspace
|
1027
|
-
'\\t': '\u0009', # tab
|
1028
|
-
'\\n': '\u000A', # linefeed
|
1029
|
-
'\\f': '\u000C', # form feed
|
1030
|
-
'\\r': '\u000D', # carriage return
|
1031
|
-
'\\"': '\u0022', # quote
|
1032
|
-
'\\\\': '\u005C', # backslash
|
1033
|
-
},
|
1034
|
-
)
|
943
|
+
return safe_parse_float
|
1035
944
|
|
1036
945
|
|
1037
946
|
class TomlDecodeError(ValueError):
|
@@ -1056,63 +965,15 @@ def toml_loads(s: str, /, *, parse_float: TomlParseFloat = float) -> ta.Dict[str
|
|
1056
965
|
src = s.replace('\r\n', '\n')
|
1057
966
|
except (AttributeError, TypeError):
|
1058
967
|
raise TypeError(f"Expected str object, not '{type(s).__qualname__}'") from None
|
1059
|
-
pos = 0
|
1060
|
-
out = TomlOutput(TomlNestedDict(), TomlFlags())
|
1061
|
-
header: TomlKey = ()
|
1062
|
-
parse_float = toml_make_safe_parse_float(parse_float)
|
1063
|
-
|
1064
|
-
# Parse one statement at a time (typically means one line in TOML source)
|
1065
|
-
while True:
|
1066
|
-
# 1. Skip line leading whitespace
|
1067
|
-
pos = toml_skip_chars(src, pos, TOML_WS)
|
1068
|
-
|
1069
|
-
# 2. Parse rules. Expect one of the following:
|
1070
|
-
# - end of file
|
1071
|
-
# - end of line
|
1072
|
-
# - comment
|
1073
|
-
# - key/value pair
|
1074
|
-
# - append dict to list (and move to its namespace)
|
1075
|
-
# - create dict (and move to its namespace)
|
1076
|
-
# Skip trailing whitespace when applicable.
|
1077
|
-
try:
|
1078
|
-
char = src[pos]
|
1079
|
-
except IndexError:
|
1080
|
-
break
|
1081
|
-
if char == '\n':
|
1082
|
-
pos += 1
|
1083
|
-
continue
|
1084
|
-
if char in TOML_KEY_INITIAL_CHARS:
|
1085
|
-
pos = toml_key_value_rule(src, pos, out, header, parse_float)
|
1086
|
-
pos = toml_skip_chars(src, pos, TOML_WS)
|
1087
|
-
elif char == '[':
|
1088
|
-
try:
|
1089
|
-
second_char: ta.Optional[str] = src[pos + 1]
|
1090
|
-
except IndexError:
|
1091
|
-
second_char = None
|
1092
|
-
out.flags.finalize_pending()
|
1093
|
-
if second_char == '[':
|
1094
|
-
pos, header = toml_create_list_rule(src, pos, out)
|
1095
|
-
else:
|
1096
|
-
pos, header = toml_create_dict_rule(src, pos, out)
|
1097
|
-
pos = toml_skip_chars(src, pos, TOML_WS)
|
1098
|
-
elif char != '#':
|
1099
|
-
raise toml_suffixed_err(src, pos, 'Invalid statement')
|
1100
968
|
|
1101
|
-
|
1102
|
-
pos = toml_skip_comment(src, pos)
|
969
|
+
parse_float = toml_make_safe_parse_float(parse_float)
|
1103
970
|
|
1104
|
-
|
1105
|
-
|
1106
|
-
|
1107
|
-
|
1108
|
-
break
|
1109
|
-
if char != '\n':
|
1110
|
-
raise toml_suffixed_err(
|
1111
|
-
src, pos, 'Expected newline or end of document after a statement',
|
1112
|
-
)
|
1113
|
-
pos += 1
|
971
|
+
parser = TomlParser(
|
972
|
+
src,
|
973
|
+
parse_float=parse_float,
|
974
|
+
)
|
1114
975
|
|
1115
|
-
return
|
976
|
+
return parser.parse()
|
1116
977
|
|
1117
978
|
|
1118
979
|
class TomlFlags:
|
@@ -1124,6 +985,8 @@ class TomlFlags:
|
|
1124
985
|
EXPLICIT_NEST = 1
|
1125
986
|
|
1126
987
|
def __init__(self) -> None:
|
988
|
+
super().__init__()
|
989
|
+
|
1127
990
|
self._flags: ta.Dict[str, dict] = {}
|
1128
991
|
self._pending_flags: ta.Set[ta.Tuple[TomlKey, int]] = set()
|
1129
992
|
|
@@ -1174,6 +1037,8 @@ class TomlFlags:
|
|
1174
1037
|
|
1175
1038
|
class TomlNestedDict:
|
1176
1039
|
def __init__(self) -> None:
|
1040
|
+
super().__init__()
|
1041
|
+
|
1177
1042
|
# The parsed content of the TOML document
|
1178
1043
|
self.dict: ta.Dict[str, ta.Any] = {}
|
1179
1044
|
|
@@ -1206,479 +1071,613 @@ class TomlNestedDict:
|
|
1206
1071
|
cont[last_key] = [{}]
|
1207
1072
|
|
1208
1073
|
|
1209
|
-
class
|
1210
|
-
|
1211
|
-
|
1212
|
-
|
1074
|
+
class TomlParser:
|
1075
|
+
def __init__(
|
1076
|
+
self,
|
1077
|
+
src: str,
|
1078
|
+
*,
|
1079
|
+
parse_float: TomlParseFloat = float,
|
1080
|
+
) -> None:
|
1081
|
+
super().__init__()
|
1213
1082
|
|
1214
|
-
|
1215
|
-
try:
|
1216
|
-
while src[pos] in chars:
|
1217
|
-
pos += 1
|
1218
|
-
except IndexError:
|
1219
|
-
pass
|
1220
|
-
return pos
|
1083
|
+
self.src = src
|
1221
1084
|
|
1085
|
+
self.parse_float = parse_float
|
1222
1086
|
|
1223
|
-
|
1224
|
-
|
1225
|
-
pos
|
1226
|
-
expect: str,
|
1227
|
-
*,
|
1228
|
-
error_on: ta.FrozenSet[str],
|
1229
|
-
error_on_eof: bool,
|
1230
|
-
) -> TomlPos:
|
1231
|
-
try:
|
1232
|
-
new_pos = src.index(expect, pos)
|
1233
|
-
except ValueError:
|
1234
|
-
new_pos = len(src)
|
1235
|
-
if error_on_eof:
|
1236
|
-
raise toml_suffixed_err(src, new_pos, f'Expected {expect!r}') from None
|
1087
|
+
self.data = TomlNestedDict()
|
1088
|
+
self.flags = TomlFlags()
|
1089
|
+
self.pos = 0
|
1237
1090
|
|
1238
|
-
|
1239
|
-
while src[pos] not in error_on:
|
1240
|
-
pos += 1
|
1241
|
-
raise toml_suffixed_err(src, pos, f'Found invalid character {src[pos]!r}')
|
1242
|
-
return new_pos
|
1091
|
+
ASCII_CTRL = frozenset(chr(i) for i in range(32)) | frozenset(chr(127))
|
1243
1092
|
|
1093
|
+
# Neither of these sets include quotation mark or backslash. They are currently handled as separate cases in the
|
1094
|
+
# parser functions.
|
1095
|
+
ILLEGAL_BASIC_STR_CHARS = ASCII_CTRL - frozenset('\t')
|
1096
|
+
ILLEGAL_MULTILINE_BASIC_STR_CHARS = ASCII_CTRL - frozenset('\t\n')
|
1244
1097
|
|
1245
|
-
|
1246
|
-
|
1247
|
-
char: ta.Optional[str] = src[pos]
|
1248
|
-
except IndexError:
|
1249
|
-
char = None
|
1250
|
-
if char == '#':
|
1251
|
-
return toml_skip_until(
|
1252
|
-
src, pos + 1, '\n', error_on=TOML_ILLEGAL_COMMENT_CHARS, error_on_eof=False,
|
1253
|
-
)
|
1254
|
-
return pos
|
1098
|
+
ILLEGAL_LITERAL_STR_CHARS = ILLEGAL_BASIC_STR_CHARS
|
1099
|
+
ILLEGAL_MULTILINE_LITERAL_STR_CHARS = ILLEGAL_MULTILINE_BASIC_STR_CHARS
|
1255
1100
|
|
1101
|
+
ILLEGAL_COMMENT_CHARS = ILLEGAL_BASIC_STR_CHARS
|
1256
1102
|
|
1257
|
-
|
1258
|
-
|
1259
|
-
|
1260
|
-
|
1261
|
-
|
1262
|
-
if pos == pos_before_skip:
|
1263
|
-
return pos
|
1103
|
+
WS = frozenset(' \t')
|
1104
|
+
WS_AND_NEWLINE = WS | frozenset('\n')
|
1105
|
+
BARE_KEY_CHARS = frozenset(string.ascii_letters + string.digits + '-_')
|
1106
|
+
KEY_INITIAL_CHARS = BARE_KEY_CHARS | frozenset("\"'")
|
1107
|
+
HEXDIGIT_CHARS = frozenset(string.hexdigits)
|
1264
1108
|
|
1109
|
+
BASIC_STR_ESCAPE_REPLACEMENTS = types.MappingProxyType({
|
1110
|
+
'\\b': '\u0008', # backspace
|
1111
|
+
'\\t': '\u0009', # tab
|
1112
|
+
'\\n': '\u000A', # linefeed
|
1113
|
+
'\\f': '\u000C', # form feed
|
1114
|
+
'\\r': '\u000D', # carriage return
|
1115
|
+
'\\"': '\u0022', # quote
|
1116
|
+
'\\\\': '\u005C', # backslash
|
1117
|
+
})
|
1118
|
+
|
1119
|
+
def parse(self) -> ta.Dict[str, ta.Any]: # noqa: C901
|
1120
|
+
header: TomlKey = ()
|
1121
|
+
|
1122
|
+
# Parse one statement at a time (typically means one line in TOML source)
|
1123
|
+
while True:
|
1124
|
+
# 1. Skip line leading whitespace
|
1125
|
+
self.skip_chars(self.WS)
|
1126
|
+
|
1127
|
+
# 2. Parse rules. Expect one of the following:
|
1128
|
+
# - end of file
|
1129
|
+
# - end of line
|
1130
|
+
# - comment
|
1131
|
+
# - key/value pair
|
1132
|
+
# - append dict to list (and move to its namespace)
|
1133
|
+
# - create dict (and move to its namespace)
|
1134
|
+
# Skip trailing whitespace when applicable.
|
1135
|
+
try:
|
1136
|
+
char = self.src[self.pos]
|
1137
|
+
except IndexError:
|
1138
|
+
break
|
1139
|
+
if char == '\n':
|
1140
|
+
self.pos += 1
|
1141
|
+
continue
|
1142
|
+
if char in self.KEY_INITIAL_CHARS:
|
1143
|
+
self.key_value_rule(header)
|
1144
|
+
self.skip_chars(self.WS)
|
1145
|
+
elif char == '[':
|
1146
|
+
try:
|
1147
|
+
second_char: ta.Optional[str] = self.src[self.pos + 1]
|
1148
|
+
except IndexError:
|
1149
|
+
second_char = None
|
1150
|
+
self.flags.finalize_pending()
|
1151
|
+
if second_char == '[':
|
1152
|
+
header = self.create_list_rule()
|
1153
|
+
else:
|
1154
|
+
header = self.create_dict_rule()
|
1155
|
+
self.skip_chars(self.WS)
|
1156
|
+
elif char != '#':
|
1157
|
+
raise self.suffixed_err('Invalid statement')
|
1265
1158
|
|
1266
|
-
|
1267
|
-
|
1268
|
-
pos = toml_skip_chars(src, pos, TOML_WS)
|
1269
|
-
pos, key = toml_parse_key(src, pos)
|
1159
|
+
# 3. Skip comment
|
1160
|
+
self.skip_comment()
|
1270
1161
|
|
1271
|
-
|
1272
|
-
|
1273
|
-
|
1274
|
-
|
1275
|
-
|
1276
|
-
|
1277
|
-
|
1162
|
+
# 4. Expect end of line or end of file
|
1163
|
+
try:
|
1164
|
+
char = self.src[self.pos]
|
1165
|
+
except IndexError:
|
1166
|
+
break
|
1167
|
+
if char != '\n':
|
1168
|
+
raise self.suffixed_err('Expected newline or end of document after a statement')
|
1169
|
+
self.pos += 1
|
1278
1170
|
|
1279
|
-
|
1280
|
-
raise toml_suffixed_err(src, pos, "Expected ']' at the end of a table declaration")
|
1281
|
-
return pos + 1, key
|
1171
|
+
return self.data.dict
|
1282
1172
|
|
1173
|
+
def skip_chars(self, chars: ta.Iterable[str]) -> None:
|
1174
|
+
try:
|
1175
|
+
while self.src[self.pos] in chars:
|
1176
|
+
self.pos += 1
|
1177
|
+
except IndexError:
|
1178
|
+
pass
|
1283
1179
|
|
1284
|
-
def
|
1285
|
-
|
1286
|
-
|
1287
|
-
|
1180
|
+
def skip_until(
|
1181
|
+
self,
|
1182
|
+
expect: str,
|
1183
|
+
*,
|
1184
|
+
error_on: ta.FrozenSet[str],
|
1185
|
+
error_on_eof: bool,
|
1186
|
+
) -> None:
|
1187
|
+
try:
|
1188
|
+
new_pos = self.src.index(expect, self.pos)
|
1189
|
+
except ValueError:
|
1190
|
+
new_pos = len(self.src)
|
1191
|
+
if error_on_eof:
|
1192
|
+
raise self.suffixed_err(f'Expected {expect!r}', pos=new_pos) from None
|
1288
1193
|
|
1289
|
-
|
1290
|
-
|
1291
|
-
|
1292
|
-
|
1293
|
-
|
1294
|
-
out.flags.set(key, TomlFlags.EXPLICIT_NEST, recursive=False)
|
1295
|
-
try:
|
1296
|
-
out.data.append_nest_to_list(key)
|
1297
|
-
except KeyError:
|
1298
|
-
raise toml_suffixed_err(src, pos, 'Cannot overwrite a value') from None
|
1299
|
-
|
1300
|
-
if not src.startswith(']]', pos):
|
1301
|
-
raise toml_suffixed_err(src, pos, "Expected ']]' at the end of an array declaration")
|
1302
|
-
return pos + 2, key
|
1303
|
-
|
1304
|
-
|
1305
|
-
def toml_key_value_rule(
|
1306
|
-
src: str,
|
1307
|
-
pos: TomlPos,
|
1308
|
-
out: TomlOutput,
|
1309
|
-
header: TomlKey,
|
1310
|
-
parse_float: TomlParseFloat,
|
1311
|
-
) -> TomlPos:
|
1312
|
-
pos, key, value = toml_parse_key_value_pair(src, pos, parse_float)
|
1313
|
-
key_parent, key_stem = key[:-1], key[-1]
|
1314
|
-
abs_key_parent = header + key_parent
|
1315
|
-
|
1316
|
-
relative_path_cont_keys = (header + key[:i] for i in range(1, len(key)))
|
1317
|
-
for cont_key in relative_path_cont_keys:
|
1318
|
-
# Check that dotted key syntax does not redefine an existing table
|
1319
|
-
if out.flags.is_(cont_key, TomlFlags.EXPLICIT_NEST):
|
1320
|
-
raise toml_suffixed_err(src, pos, f'Cannot redefine namespace {cont_key}')
|
1321
|
-
# Containers in the relative path can't be opened with the table syntax or dotted key/value syntax in following
|
1322
|
-
# table sections.
|
1323
|
-
out.flags.add_pending(cont_key, TomlFlags.EXPLICIT_NEST)
|
1324
|
-
|
1325
|
-
if out.flags.is_(abs_key_parent, TomlFlags.FROZEN):
|
1326
|
-
raise toml_suffixed_err(
|
1327
|
-
src,
|
1328
|
-
pos,
|
1329
|
-
f'Cannot mutate immutable namespace {abs_key_parent}',
|
1330
|
-
)
|
1194
|
+
if not error_on.isdisjoint(self.src[self.pos:new_pos]):
|
1195
|
+
while self.src[self.pos] not in error_on:
|
1196
|
+
self.pos += 1
|
1197
|
+
raise self.suffixed_err(f'Found invalid character {self.src[self.pos]!r}')
|
1198
|
+
self.pos = new_pos
|
1331
1199
|
|
1332
|
-
|
1333
|
-
nest = out.data.get_or_create_nest(abs_key_parent)
|
1334
|
-
except KeyError:
|
1335
|
-
raise toml_suffixed_err(src, pos, 'Cannot overwrite a value') from None
|
1336
|
-
if key_stem in nest:
|
1337
|
-
raise toml_suffixed_err(src, pos, 'Cannot overwrite a value')
|
1338
|
-
# Mark inline table and array namespaces recursively immutable
|
1339
|
-
if isinstance(value, (dict, list)):
|
1340
|
-
out.flags.set(header + key, TomlFlags.FROZEN, recursive=True)
|
1341
|
-
nest[key_stem] = value
|
1342
|
-
return pos
|
1343
|
-
|
1344
|
-
|
1345
|
-
def toml_parse_key_value_pair(
|
1346
|
-
src: str,
|
1347
|
-
pos: TomlPos,
|
1348
|
-
parse_float: TomlParseFloat,
|
1349
|
-
) -> ta.Tuple[TomlPos, TomlKey, ta.Any]:
|
1350
|
-
pos, key = toml_parse_key(src, pos)
|
1351
|
-
try:
|
1352
|
-
char: ta.Optional[str] = src[pos]
|
1353
|
-
except IndexError:
|
1354
|
-
char = None
|
1355
|
-
if char != '=':
|
1356
|
-
raise toml_suffixed_err(src, pos, "Expected '=' after a key in a key/value pair")
|
1357
|
-
pos += 1
|
1358
|
-
pos = toml_skip_chars(src, pos, TOML_WS)
|
1359
|
-
pos, value = toml_parse_value(src, pos, parse_float)
|
1360
|
-
return pos, key, value
|
1361
|
-
|
1362
|
-
|
1363
|
-
def toml_parse_key(src: str, pos: TomlPos) -> ta.Tuple[TomlPos, TomlKey]:
|
1364
|
-
pos, key_part = toml_parse_key_part(src, pos)
|
1365
|
-
key: TomlKey = (key_part,)
|
1366
|
-
pos = toml_skip_chars(src, pos, TOML_WS)
|
1367
|
-
while True:
|
1200
|
+
def skip_comment(self) -> None:
|
1368
1201
|
try:
|
1369
|
-
char: ta.Optional[str] = src[pos]
|
1202
|
+
char: ta.Optional[str] = self.src[self.pos]
|
1370
1203
|
except IndexError:
|
1371
1204
|
char = None
|
1372
|
-
if char
|
1373
|
-
|
1374
|
-
|
1375
|
-
|
1376
|
-
|
1377
|
-
|
1378
|
-
|
1205
|
+
if char == '#':
|
1206
|
+
self.pos += 1
|
1207
|
+
self.skip_until(
|
1208
|
+
'\n',
|
1209
|
+
error_on=self.ILLEGAL_COMMENT_CHARS,
|
1210
|
+
error_on_eof=False,
|
1211
|
+
)
|
1379
1212
|
|
1213
|
+
def skip_comments_and_array_ws(self) -> None:
|
1214
|
+
while True:
|
1215
|
+
pos_before_skip = self.pos
|
1216
|
+
self.skip_chars(self.WS_AND_NEWLINE)
|
1217
|
+
self.skip_comment()
|
1218
|
+
if self.pos == pos_before_skip:
|
1219
|
+
return
|
1380
1220
|
|
1381
|
-
def
|
1382
|
-
|
1383
|
-
|
1384
|
-
|
1385
|
-
|
1386
|
-
|
1387
|
-
|
1388
|
-
|
1389
|
-
return pos, src[start_pos:pos]
|
1390
|
-
if char == "'":
|
1391
|
-
return toml_parse_literal_str(src, pos)
|
1392
|
-
if char == '"':
|
1393
|
-
return toml_parse_one_line_basic_str(src, pos)
|
1394
|
-
raise toml_suffixed_err(src, pos, 'Invalid initial character for a key part')
|
1395
|
-
|
1396
|
-
|
1397
|
-
def toml_parse_one_line_basic_str(src: str, pos: TomlPos) -> ta.Tuple[TomlPos, str]:
|
1398
|
-
pos += 1
|
1399
|
-
return toml_parse_basic_str(src, pos, multiline=False)
|
1400
|
-
|
1401
|
-
|
1402
|
-
def toml_parse_array(src: str, pos: TomlPos, parse_float: TomlParseFloat) -> ta.Tuple[TomlPos, list]:
|
1403
|
-
pos += 1
|
1404
|
-
array: list = []
|
1405
|
-
|
1406
|
-
pos = toml_skip_comments_and_array_ws(src, pos)
|
1407
|
-
if src.startswith(']', pos):
|
1408
|
-
return pos + 1, array
|
1409
|
-
while True:
|
1410
|
-
pos, val = toml_parse_value(src, pos, parse_float)
|
1411
|
-
array.append(val)
|
1412
|
-
pos = toml_skip_comments_and_array_ws(src, pos)
|
1413
|
-
|
1414
|
-
c = src[pos:pos + 1]
|
1415
|
-
if c == ']':
|
1416
|
-
return pos + 1, array
|
1417
|
-
if c != ',':
|
1418
|
-
raise toml_suffixed_err(src, pos, 'Unclosed array')
|
1419
|
-
pos += 1
|
1420
|
-
|
1421
|
-
pos = toml_skip_comments_and_array_ws(src, pos)
|
1422
|
-
if src.startswith(']', pos):
|
1423
|
-
return pos + 1, array
|
1424
|
-
|
1425
|
-
|
1426
|
-
def toml_parse_inline_table(src: str, pos: TomlPos, parse_float: TomlParseFloat) -> ta.Tuple[TomlPos, dict]:
|
1427
|
-
pos += 1
|
1428
|
-
nested_dict = TomlNestedDict()
|
1429
|
-
flags = TomlFlags()
|
1430
|
-
|
1431
|
-
pos = toml_skip_chars(src, pos, TOML_WS)
|
1432
|
-
if src.startswith('}', pos):
|
1433
|
-
return pos + 1, nested_dict.dict
|
1434
|
-
while True:
|
1435
|
-
pos, key, value = toml_parse_key_value_pair(src, pos, parse_float)
|
1436
|
-
key_parent, key_stem = key[:-1], key[-1]
|
1437
|
-
if flags.is_(key, TomlFlags.FROZEN):
|
1438
|
-
raise toml_suffixed_err(src, pos, f'Cannot mutate immutable namespace {key}')
|
1221
|
+
def create_dict_rule(self) -> TomlKey:
|
1222
|
+
self.pos += 1 # Skip "["
|
1223
|
+
self.skip_chars(self.WS)
|
1224
|
+
key = self.parse_key()
|
1225
|
+
|
1226
|
+
if self.flags.is_(key, TomlFlags.EXPLICIT_NEST) or self.flags.is_(key, TomlFlags.FROZEN):
|
1227
|
+
raise self.suffixed_err(f'Cannot declare {key} twice')
|
1228
|
+
self.flags.set(key, TomlFlags.EXPLICIT_NEST, recursive=False)
|
1439
1229
|
try:
|
1440
|
-
|
1230
|
+
self.data.get_or_create_nest(key)
|
1441
1231
|
except KeyError:
|
1442
|
-
raise
|
1443
|
-
|
1444
|
-
|
1445
|
-
|
1446
|
-
pos
|
1447
|
-
|
1448
|
-
|
1449
|
-
|
1450
|
-
|
1451
|
-
|
1452
|
-
|
1453
|
-
|
1454
|
-
|
1455
|
-
|
1456
|
-
|
1457
|
-
|
1458
|
-
|
1459
|
-
|
1460
|
-
|
1461
|
-
|
1462
|
-
|
1463
|
-
)
|
1464
|
-
escape_id = src[pos:pos + 2]
|
1465
|
-
pos += 2
|
1466
|
-
if multiline and escape_id in {'\\ ', '\\\t', '\\\n'}:
|
1467
|
-
# Skip whitespace until next non-whitespace character or end of the doc. Error if non-whitespace is found before
|
1468
|
-
# newline.
|
1469
|
-
if escape_id != '\\\n':
|
1470
|
-
pos = toml_skip_chars(src, pos, TOML_WS)
|
1471
|
-
try:
|
1472
|
-
char = src[pos]
|
1473
|
-
except IndexError:
|
1474
|
-
return pos, ''
|
1475
|
-
if char != '\n':
|
1476
|
-
raise toml_suffixed_err(src, pos, "Unescaped '\\' in a string")
|
1477
|
-
pos += 1
|
1478
|
-
pos = toml_skip_chars(src, pos, TOML_WS_AND_NEWLINE)
|
1479
|
-
return pos, ''
|
1480
|
-
if escape_id == '\\u':
|
1481
|
-
return toml_parse_hex_char(src, pos, 4)
|
1482
|
-
if escape_id == '\\U':
|
1483
|
-
return toml_parse_hex_char(src, pos, 8)
|
1484
|
-
try:
|
1485
|
-
return pos, TOML_BASIC_STR_ESCAPE_REPLACEMENTS[escape_id]
|
1486
|
-
except KeyError:
|
1487
|
-
raise toml_suffixed_err(src, pos, "Unescaped '\\' in a string") from None
|
1232
|
+
raise self.suffixed_err('Cannot overwrite a value') from None
|
1233
|
+
|
1234
|
+
if not self.src.startswith(']', self.pos):
|
1235
|
+
raise self.suffixed_err("Expected ']' at the end of a table declaration")
|
1236
|
+
self.pos += 1
|
1237
|
+
return key
|
1238
|
+
|
1239
|
+
def create_list_rule(self) -> TomlKey:
|
1240
|
+
self.pos += 2 # Skip "[["
|
1241
|
+
self.skip_chars(self.WS)
|
1242
|
+
key = self.parse_key()
|
1243
|
+
|
1244
|
+
if self.flags.is_(key, TomlFlags.FROZEN):
|
1245
|
+
raise self.suffixed_err(f'Cannot mutate immutable namespace {key}')
|
1246
|
+
# Free the namespace now that it points to another empty list item...
|
1247
|
+
self.flags.unset_all(key)
|
1248
|
+
# ...but this key precisely is still prohibited from table declaration
|
1249
|
+
self.flags.set(key, TomlFlags.EXPLICIT_NEST, recursive=False)
|
1250
|
+
try:
|
1251
|
+
self.data.append_nest_to_list(key)
|
1252
|
+
except KeyError:
|
1253
|
+
raise self.suffixed_err('Cannot overwrite a value') from None
|
1488
1254
|
|
1255
|
+
if not self.src.startswith(']]', self.pos):
|
1256
|
+
raise self.suffixed_err("Expected ']]' at the end of an array declaration")
|
1257
|
+
self.pos += 2
|
1258
|
+
return key
|
1489
1259
|
|
1490
|
-
def
|
1491
|
-
|
1260
|
+
def key_value_rule(self, header: TomlKey) -> None:
|
1261
|
+
key, value = self.parse_key_value_pair()
|
1262
|
+
key_parent, key_stem = key[:-1], key[-1]
|
1263
|
+
abs_key_parent = header + key_parent
|
1492
1264
|
|
1265
|
+
relative_path_cont_keys = (header + key[:i] for i in range(1, len(key)))
|
1266
|
+
for cont_key in relative_path_cont_keys:
|
1267
|
+
# Check that dotted key syntax does not redefine an existing table
|
1268
|
+
if self.flags.is_(cont_key, TomlFlags.EXPLICIT_NEST):
|
1269
|
+
raise self.suffixed_err(f'Cannot redefine namespace {cont_key}')
|
1270
|
+
# Containers in the relative path can't be opened with the table syntax or dotted key/value syntax in
|
1271
|
+
# following table sections.
|
1272
|
+
self.flags.add_pending(cont_key, TomlFlags.EXPLICIT_NEST)
|
1493
1273
|
|
1494
|
-
|
1495
|
-
|
1496
|
-
if len(hex_str) != hex_len or not TOML_HEXDIGIT_CHARS.issuperset(hex_str):
|
1497
|
-
raise toml_suffixed_err(src, pos, 'Invalid hex value')
|
1498
|
-
pos += hex_len
|
1499
|
-
hex_int = int(hex_str, 16)
|
1500
|
-
if not toml_is_unicode_scalar_value(hex_int):
|
1501
|
-
raise toml_suffixed_err(src, pos, 'Escaped character is not a Unicode scalar value')
|
1502
|
-
return pos, chr(hex_int)
|
1274
|
+
if self.flags.is_(abs_key_parent, TomlFlags.FROZEN):
|
1275
|
+
raise self.suffixed_err(f'Cannot mutate immutable namespace {abs_key_parent}')
|
1503
1276
|
|
1277
|
+
try:
|
1278
|
+
nest = self.data.get_or_create_nest(abs_key_parent)
|
1279
|
+
except KeyError:
|
1280
|
+
raise self.suffixed_err('Cannot overwrite a value') from None
|
1281
|
+
if key_stem in nest:
|
1282
|
+
raise self.suffixed_err('Cannot overwrite a value')
|
1283
|
+
# Mark inline table and array namespaces recursively immutable
|
1284
|
+
if isinstance(value, (dict, list)):
|
1285
|
+
self.flags.set(header + key, TomlFlags.FROZEN, recursive=True)
|
1286
|
+
nest[key_stem] = value
|
1504
1287
|
|
1505
|
-
def
|
1506
|
-
|
1507
|
-
|
1508
|
-
|
1509
|
-
|
1510
|
-
|
1511
|
-
|
1288
|
+
def parse_key_value_pair(self) -> ta.Tuple[TomlKey, ta.Any]:
|
1289
|
+
key = self.parse_key()
|
1290
|
+
try:
|
1291
|
+
char: ta.Optional[str] = self.src[self.pos]
|
1292
|
+
except IndexError:
|
1293
|
+
char = None
|
1294
|
+
if char != '=':
|
1295
|
+
raise self.suffixed_err("Expected '=' after a key in a key/value pair")
|
1296
|
+
self.pos += 1
|
1297
|
+
self.skip_chars(self.WS)
|
1298
|
+
value = self.parse_value()
|
1299
|
+
return key, value
|
1300
|
+
|
1301
|
+
def parse_key(self) -> TomlKey:
|
1302
|
+
key_part = self.parse_key_part()
|
1303
|
+
key: TomlKey = (key_part,)
|
1304
|
+
self.skip_chars(self.WS)
|
1305
|
+
while True:
|
1306
|
+
try:
|
1307
|
+
char: ta.Optional[str] = self.src[self.pos]
|
1308
|
+
except IndexError:
|
1309
|
+
char = None
|
1310
|
+
if char != '.':
|
1311
|
+
return key
|
1312
|
+
self.pos += 1
|
1313
|
+
self.skip_chars(self.WS)
|
1314
|
+
key_part = self.parse_key_part()
|
1315
|
+
key += (key_part,)
|
1316
|
+
self.skip_chars(self.WS)
|
1317
|
+
|
1318
|
+
def parse_key_part(self) -> str:
|
1319
|
+
try:
|
1320
|
+
char: ta.Optional[str] = self.src[self.pos]
|
1321
|
+
except IndexError:
|
1322
|
+
char = None
|
1323
|
+
if char in self.BARE_KEY_CHARS:
|
1324
|
+
start_pos = self.pos
|
1325
|
+
self.skip_chars(self.BARE_KEY_CHARS)
|
1326
|
+
return self.src[start_pos:self.pos]
|
1327
|
+
if char == "'":
|
1328
|
+
return self.parse_literal_str()
|
1329
|
+
if char == '"':
|
1330
|
+
return self.parse_one_line_basic_str()
|
1331
|
+
raise self.suffixed_err('Invalid initial character for a key part')
|
1332
|
+
|
1333
|
+
def parse_one_line_basic_str(self) -> str:
|
1334
|
+
self.pos += 1
|
1335
|
+
return self.parse_basic_str(multiline=False)
|
1336
|
+
|
1337
|
+
def parse_array(self) -> list:
|
1338
|
+
self.pos += 1
|
1339
|
+
array: list = []
|
1340
|
+
|
1341
|
+
self.skip_comments_and_array_ws()
|
1342
|
+
if self.src.startswith(']', self.pos):
|
1343
|
+
self.pos += 1
|
1344
|
+
return array
|
1345
|
+
while True:
|
1346
|
+
val = self.parse_value()
|
1347
|
+
array.append(val)
|
1348
|
+
self.skip_comments_and_array_ws()
|
1349
|
+
|
1350
|
+
c = self.src[self.pos:self.pos + 1]
|
1351
|
+
if c == ']':
|
1352
|
+
self.pos += 1
|
1353
|
+
return array
|
1354
|
+
if c != ',':
|
1355
|
+
raise self.suffixed_err('Unclosed array')
|
1356
|
+
self.pos += 1
|
1357
|
+
|
1358
|
+
self.skip_comments_and_array_ws()
|
1359
|
+
if self.src.startswith(']', self.pos):
|
1360
|
+
self.pos += 1
|
1361
|
+
return array
|
1362
|
+
|
1363
|
+
def parse_inline_table(self) -> dict:
|
1364
|
+
self.pos += 1
|
1365
|
+
nested_dict = TomlNestedDict()
|
1366
|
+
flags = TomlFlags()
|
1367
|
+
|
1368
|
+
self.skip_chars(self.WS)
|
1369
|
+
if self.src.startswith('}', self.pos):
|
1370
|
+
self.pos += 1
|
1371
|
+
return nested_dict.dict
|
1372
|
+
while True:
|
1373
|
+
key, value = self.parse_key_value_pair()
|
1374
|
+
key_parent, key_stem = key[:-1], key[-1]
|
1375
|
+
if flags.is_(key, TomlFlags.FROZEN):
|
1376
|
+
raise self.suffixed_err(f'Cannot mutate immutable namespace {key}')
|
1377
|
+
try:
|
1378
|
+
nest = nested_dict.get_or_create_nest(key_parent, access_lists=False)
|
1379
|
+
except KeyError:
|
1380
|
+
raise self.suffixed_err('Cannot overwrite a value') from None
|
1381
|
+
if key_stem in nest:
|
1382
|
+
raise self.suffixed_err(f'Duplicate inline table key {key_stem!r}')
|
1383
|
+
nest[key_stem] = value
|
1384
|
+
self.skip_chars(self.WS)
|
1385
|
+
c = self.src[self.pos:self.pos + 1]
|
1386
|
+
if c == '}':
|
1387
|
+
self.pos += 1
|
1388
|
+
return nested_dict.dict
|
1389
|
+
if c != ',':
|
1390
|
+
raise self.suffixed_err('Unclosed inline table')
|
1391
|
+
if isinstance(value, (dict, list)):
|
1392
|
+
flags.set(key, TomlFlags.FROZEN, recursive=True)
|
1393
|
+
self.pos += 1
|
1394
|
+
self.skip_chars(self.WS)
|
1395
|
+
|
1396
|
+
def parse_basic_str_escape(self, multiline: bool = False) -> str:
|
1397
|
+
escape_id = self.src[self.pos:self.pos + 2]
|
1398
|
+
self.pos += 2
|
1399
|
+
if multiline and escape_id in {'\\ ', '\\\t', '\\\n'}:
|
1400
|
+
# Skip whitespace until next non-whitespace character or end of the doc. Error if non-whitespace is found
|
1401
|
+
# before newline.
|
1402
|
+
if escape_id != '\\\n':
|
1403
|
+
self.skip_chars(self.WS)
|
1404
|
+
try:
|
1405
|
+
char = self.src[self.pos]
|
1406
|
+
except IndexError:
|
1407
|
+
return ''
|
1408
|
+
if char != '\n':
|
1409
|
+
raise self.suffixed_err("Unescaped '\\' in a string")
|
1410
|
+
self.pos += 1
|
1411
|
+
self.skip_chars(self.WS_AND_NEWLINE)
|
1412
|
+
return ''
|
1413
|
+
if escape_id == '\\u':
|
1414
|
+
return self.parse_hex_char(4)
|
1415
|
+
if escape_id == '\\U':
|
1416
|
+
return self.parse_hex_char(8)
|
1417
|
+
try:
|
1418
|
+
return self.BASIC_STR_ESCAPE_REPLACEMENTS[escape_id]
|
1419
|
+
except KeyError:
|
1420
|
+
raise self.suffixed_err("Unescaped '\\' in a string") from None
|
1512
1421
|
|
1422
|
+
def parse_basic_str_escape_multiline(self) -> str:
|
1423
|
+
return self.parse_basic_str_escape(multiline=True)
|
1513
1424
|
|
1514
|
-
|
1515
|
-
|
1516
|
-
|
1517
|
-
|
1425
|
+
@classmethod
|
1426
|
+
def is_unicode_scalar_value(cls, codepoint: int) -> bool:
|
1427
|
+
return (0 <= codepoint <= 55295) or (57344 <= codepoint <= 1114111)
|
1428
|
+
|
1429
|
+
def parse_hex_char(self, hex_len: int) -> str:
|
1430
|
+
hex_str = self.src[self.pos:self.pos + hex_len]
|
1431
|
+
if len(hex_str) != hex_len or not self.HEXDIGIT_CHARS.issuperset(hex_str):
|
1432
|
+
raise self.suffixed_err('Invalid hex value')
|
1433
|
+
self.pos += hex_len
|
1434
|
+
hex_int = int(hex_str, 16)
|
1435
|
+
if not self.is_unicode_scalar_value(hex_int):
|
1436
|
+
raise self.suffixed_err('Escaped character is not a Unicode scalar value')
|
1437
|
+
return chr(hex_int)
|
1438
|
+
|
1439
|
+
def parse_literal_str(self) -> str:
|
1440
|
+
self.pos += 1 # Skip starting apostrophe
|
1441
|
+
start_pos = self.pos
|
1442
|
+
self.skip_until("'", error_on=self.ILLEGAL_LITERAL_STR_CHARS, error_on_eof=True)
|
1443
|
+
end_pos = self.pos
|
1444
|
+
self.pos += 1
|
1445
|
+
return self.src[start_pos:end_pos] # Skip ending apostrophe
|
1446
|
+
|
1447
|
+
def parse_multiline_str(self, *, literal: bool) -> str:
|
1448
|
+
self.pos += 3
|
1449
|
+
if self.src.startswith('\n', self.pos):
|
1450
|
+
self.pos += 1
|
1451
|
+
|
1452
|
+
if literal:
|
1453
|
+
delim = "'"
|
1454
|
+
start_pos = self.pos
|
1455
|
+
self.skip_until(
|
1456
|
+
"'''",
|
1457
|
+
error_on=self.ILLEGAL_MULTILINE_LITERAL_STR_CHARS,
|
1458
|
+
error_on_eof=True,
|
1459
|
+
)
|
1460
|
+
result = self.src[start_pos:self.pos]
|
1461
|
+
self.pos += 3
|
1462
|
+
else:
|
1463
|
+
delim = '"'
|
1464
|
+
result = self.parse_basic_str(multiline=True)
|
1465
|
+
|
1466
|
+
# Add at maximum two extra apostrophes/quotes if the end sequence is 4 or 5 chars long instead of just 3.
|
1467
|
+
if not self.src.startswith(delim, self.pos):
|
1468
|
+
return result
|
1469
|
+
self.pos += 1
|
1470
|
+
if not self.src.startswith(delim, self.pos):
|
1471
|
+
return result + delim
|
1472
|
+
self.pos += 1
|
1473
|
+
return result + (delim * 2)
|
1474
|
+
|
1475
|
+
def parse_basic_str(self, *, multiline: bool) -> str:
|
1476
|
+
if multiline:
|
1477
|
+
error_on = self.ILLEGAL_MULTILINE_BASIC_STR_CHARS
|
1478
|
+
parse_escapes = self.parse_basic_str_escape_multiline
|
1479
|
+
else:
|
1480
|
+
error_on = self.ILLEGAL_BASIC_STR_CHARS
|
1481
|
+
parse_escapes = self.parse_basic_str_escape
|
1482
|
+
result = ''
|
1483
|
+
start_pos = self.pos
|
1484
|
+
while True:
|
1485
|
+
try:
|
1486
|
+
char = self.src[self.pos]
|
1487
|
+
except IndexError:
|
1488
|
+
raise self.suffixed_err('Unterminated string') from None
|
1489
|
+
if char == '"':
|
1490
|
+
if not multiline:
|
1491
|
+
end_pos = self.pos
|
1492
|
+
self.pos += 1
|
1493
|
+
return result + self.src[start_pos:end_pos]
|
1494
|
+
if self.src.startswith('"""', self.pos):
|
1495
|
+
end_pos = self.pos
|
1496
|
+
self.pos += 3
|
1497
|
+
return result + self.src[start_pos:end_pos]
|
1498
|
+
self.pos += 1
|
1499
|
+
continue
|
1500
|
+
if char == '\\':
|
1501
|
+
result += self.src[start_pos:self.pos]
|
1502
|
+
parsed_escape = parse_escapes()
|
1503
|
+
result += parsed_escape
|
1504
|
+
start_pos = self.pos
|
1505
|
+
continue
|
1506
|
+
if char in error_on:
|
1507
|
+
raise self.suffixed_err(f'Illegal character {char!r}')
|
1508
|
+
self.pos += 1
|
1518
1509
|
|
1519
|
-
|
1520
|
-
delim = "'"
|
1521
|
-
end_pos = toml_skip_until(
|
1522
|
-
src,
|
1523
|
-
pos,
|
1524
|
-
"'''",
|
1525
|
-
error_on=TOML_ILLEGAL_MULTILINE_LITERAL_STR_CHARS,
|
1526
|
-
error_on_eof=True,
|
1527
|
-
)
|
1528
|
-
result = src[pos:end_pos]
|
1529
|
-
pos = end_pos + 3
|
1530
|
-
else:
|
1531
|
-
delim = '"'
|
1532
|
-
pos, result = toml_parse_basic_str(src, pos, multiline=True)
|
1533
|
-
|
1534
|
-
# Add at maximum two extra apostrophes/quotes if the end sequence is 4 or 5 chars long instead of just 3.
|
1535
|
-
if not src.startswith(delim, pos):
|
1536
|
-
return pos, result
|
1537
|
-
pos += 1
|
1538
|
-
if not src.startswith(delim, pos):
|
1539
|
-
return pos, result + delim
|
1540
|
-
pos += 1
|
1541
|
-
return pos, result + (delim * 2)
|
1542
|
-
|
1543
|
-
|
1544
|
-
def toml_parse_basic_str(src: str, pos: TomlPos, *, multiline: bool) -> ta.Tuple[TomlPos, str]:
|
1545
|
-
if multiline:
|
1546
|
-
error_on = TOML_ILLEGAL_MULTILINE_BASIC_STR_CHARS
|
1547
|
-
parse_escapes = toml_parse_basic_str_escape_multiline
|
1548
|
-
else:
|
1549
|
-
error_on = TOML_ILLEGAL_BASIC_STR_CHARS
|
1550
|
-
parse_escapes = toml_parse_basic_str_escape
|
1551
|
-
result = ''
|
1552
|
-
start_pos = pos
|
1553
|
-
while True:
|
1510
|
+
def parse_value(self) -> ta.Any: # noqa: C901
|
1554
1511
|
try:
|
1555
|
-
char = src[pos]
|
1512
|
+
char: ta.Optional[str] = self.src[self.pos]
|
1556
1513
|
except IndexError:
|
1557
|
-
|
1514
|
+
char = None
|
1515
|
+
|
1516
|
+
# IMPORTANT: order conditions based on speed of checking and likelihood
|
1517
|
+
|
1518
|
+
# Basic strings
|
1558
1519
|
if char == '"':
|
1559
|
-
if
|
1560
|
-
return
|
1561
|
-
|
1562
|
-
|
1563
|
-
|
1564
|
-
|
1565
|
-
|
1566
|
-
|
1567
|
-
|
1568
|
-
|
1569
|
-
|
1570
|
-
|
1571
|
-
|
1572
|
-
|
1573
|
-
|
1520
|
+
if self.src.startswith('"""', self.pos):
|
1521
|
+
return self.parse_multiline_str(literal=False)
|
1522
|
+
return self.parse_one_line_basic_str()
|
1523
|
+
|
1524
|
+
# Literal strings
|
1525
|
+
if char == "'":
|
1526
|
+
if self.src.startswith("'''", self.pos):
|
1527
|
+
return self.parse_multiline_str(literal=True)
|
1528
|
+
return self.parse_literal_str()
|
1529
|
+
|
1530
|
+
# Booleans
|
1531
|
+
if char == 't':
|
1532
|
+
if self.src.startswith('true', self.pos):
|
1533
|
+
self.pos += 4
|
1534
|
+
return True
|
1535
|
+
if char == 'f':
|
1536
|
+
if self.src.startswith('false', self.pos):
|
1537
|
+
self.pos += 5
|
1538
|
+
return False
|
1574
1539
|
|
1540
|
+
# Arrays
|
1541
|
+
if char == '[':
|
1542
|
+
return self.parse_array()
|
1575
1543
|
|
1576
|
-
|
1577
|
-
|
1578
|
-
|
1579
|
-
|
1580
|
-
|
1581
|
-
|
1582
|
-
|
1583
|
-
|
1584
|
-
|
1585
|
-
|
1586
|
-
|
1587
|
-
|
1588
|
-
|
1589
|
-
|
1590
|
-
if
|
1591
|
-
|
1592
|
-
|
1593
|
-
|
1594
|
-
|
1595
|
-
|
1596
|
-
|
1597
|
-
|
1598
|
-
|
1599
|
-
|
1600
|
-
|
1601
|
-
|
1602
|
-
|
1603
|
-
|
1604
|
-
|
1605
|
-
|
1606
|
-
|
1607
|
-
|
1608
|
-
|
1609
|
-
|
1610
|
-
|
1611
|
-
|
1612
|
-
|
1613
|
-
|
1614
|
-
|
1615
|
-
|
1616
|
-
# Dates and times
|
1617
|
-
datetime_match = TOML_RE_DATETIME.match(src, pos)
|
1618
|
-
if datetime_match:
|
1619
|
-
try:
|
1620
|
-
datetime_obj = toml_match_to_datetime(datetime_match)
|
1621
|
-
except ValueError as e:
|
1622
|
-
raise toml_suffixed_err(src, pos, 'Invalid date or datetime') from e
|
1623
|
-
return datetime_match.end(), datetime_obj
|
1624
|
-
localtime_match = TOML_RE_LOCALTIME.match(src, pos)
|
1625
|
-
if localtime_match:
|
1626
|
-
return localtime_match.end(), toml_match_to_localtime(localtime_match)
|
1627
|
-
|
1628
|
-
# Integers and "normal" floats. The regex will greedily match any type starting with a decimal char, so needs to be
|
1629
|
-
# located after handling of dates and times.
|
1630
|
-
number_match = TOML_RE_NUMBER.match(src, pos)
|
1631
|
-
if number_match:
|
1632
|
-
return number_match.end(), toml_match_to_number(number_match, parse_float)
|
1633
|
-
|
1634
|
-
# Special floats
|
1635
|
-
first_three = src[pos:pos + 3]
|
1636
|
-
if first_three in {'inf', 'nan'}:
|
1637
|
-
return pos + 3, parse_float(first_three)
|
1638
|
-
first_four = src[pos:pos + 4]
|
1639
|
-
if first_four in {'-inf', '+inf', '-nan', '+nan'}:
|
1640
|
-
return pos + 4, parse_float(first_four)
|
1641
|
-
|
1642
|
-
raise toml_suffixed_err(src, pos, 'Invalid value')
|
1643
|
-
|
1644
|
-
|
1645
|
-
def toml_suffixed_err(src: str, pos: TomlPos, msg: str) -> TomlDecodeError:
|
1646
|
-
"""Return a `TomlDecodeError` where error message is suffixed with coordinates in source."""
|
1647
|
-
|
1648
|
-
def coord_repr(src: str, pos: TomlPos) -> str:
|
1649
|
-
if pos >= len(src):
|
1544
|
+
# Inline tables
|
1545
|
+
if char == '{':
|
1546
|
+
return self.parse_inline_table()
|
1547
|
+
|
1548
|
+
# Dates and times
|
1549
|
+
datetime_match = self.RE_DATETIME.match(self.src, self.pos)
|
1550
|
+
if datetime_match:
|
1551
|
+
try:
|
1552
|
+
datetime_obj = self.match_to_datetime(datetime_match)
|
1553
|
+
except ValueError as e:
|
1554
|
+
raise self.suffixed_err('Invalid date or datetime') from e
|
1555
|
+
self.pos = datetime_match.end()
|
1556
|
+
return datetime_obj
|
1557
|
+
localtime_match = self.RE_LOCALTIME.match(self.src, self.pos)
|
1558
|
+
if localtime_match:
|
1559
|
+
self.pos = localtime_match.end()
|
1560
|
+
return self.match_to_localtime(localtime_match)
|
1561
|
+
|
1562
|
+
# Integers and "normal" floats. The regex will greedily match any type starting with a decimal char, so needs to
|
1563
|
+
# be located after handling of dates and times.
|
1564
|
+
number_match = self.RE_NUMBER.match(self.src, self.pos)
|
1565
|
+
if number_match:
|
1566
|
+
self.pos = number_match.end()
|
1567
|
+
return self.match_to_number(number_match, self.parse_float)
|
1568
|
+
|
1569
|
+
# Special floats
|
1570
|
+
first_three = self.src[self.pos:self.pos + 3]
|
1571
|
+
if first_three in {'inf', 'nan'}:
|
1572
|
+
self.pos += 3
|
1573
|
+
return self.parse_float(first_three)
|
1574
|
+
first_four = self.src[self.pos:self.pos + 4]
|
1575
|
+
if first_four in {'-inf', '+inf', '-nan', '+nan'}:
|
1576
|
+
self.pos += 4
|
1577
|
+
return self.parse_float(first_four)
|
1578
|
+
|
1579
|
+
raise self.suffixed_err('Invalid value')
|
1580
|
+
|
1581
|
+
def coord_repr(self, pos: TomlPos) -> str:
|
1582
|
+
if pos >= len(self.src):
|
1650
1583
|
return 'end of document'
|
1651
|
-
line = src.count('\n', 0, pos) + 1
|
1584
|
+
line = self.src.count('\n', 0, pos) + 1
|
1652
1585
|
if line == 1:
|
1653
1586
|
column = pos + 1
|
1654
1587
|
else:
|
1655
|
-
column = pos - src.rindex('\n', 0, pos)
|
1588
|
+
column = pos - self.src.rindex('\n', 0, pos)
|
1656
1589
|
return f'line {line}, column {column}'
|
1657
1590
|
|
1658
|
-
|
1591
|
+
def suffixed_err(self, msg: str, *, pos: ta.Optional[TomlPos] = None) -> TomlDecodeError:
|
1592
|
+
"""Return a `TomlDecodeError` where error message is suffixed with coordinates in source."""
|
1593
|
+
|
1594
|
+
if pos is None:
|
1595
|
+
pos = self.pos
|
1596
|
+
return TomlDecodeError(f'{msg} (at {self.coord_repr(pos)})')
|
1597
|
+
|
1598
|
+
_TIME_RE_STR = r'([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])(?:\.([0-9]{1,6})[0-9]*)?'
|
1599
|
+
|
1600
|
+
RE_NUMBER = re.compile(
|
1601
|
+
r"""
|
1602
|
+
0
|
1603
|
+
(?:
|
1604
|
+
x[0-9A-Fa-f](?:_?[0-9A-Fa-f])* # hex
|
1605
|
+
|
|
1606
|
+
b[01](?:_?[01])* # bin
|
1607
|
+
|
|
1608
|
+
o[0-7](?:_?[0-7])* # oct
|
1609
|
+
)
|
1610
|
+
|
|
1611
|
+
[+-]?(?:0|[1-9](?:_?[0-9])*) # dec, integer part
|
1612
|
+
(?P<floatpart>
|
1613
|
+
(?:\.[0-9](?:_?[0-9])*)? # optional fractional part
|
1614
|
+
(?:[eE][+-]?[0-9](?:_?[0-9])*)? # optional exponent part
|
1615
|
+
)
|
1616
|
+
""",
|
1617
|
+
flags=re.VERBOSE,
|
1618
|
+
)
|
1659
1619
|
|
1620
|
+
RE_LOCALTIME = re.compile(_TIME_RE_STR)
|
1660
1621
|
|
1661
|
-
|
1662
|
-
|
1622
|
+
RE_DATETIME = re.compile(
|
1623
|
+
rf"""
|
1624
|
+
([0-9]{{4}})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01]) # date, e.g. 1988-10-27
|
1625
|
+
(?:
|
1626
|
+
[Tt ]
|
1627
|
+
{_TIME_RE_STR}
|
1628
|
+
(?:([Zz])|([+-])([01][0-9]|2[0-3]):([0-5][0-9]))? # optional time offset
|
1629
|
+
)?
|
1630
|
+
""",
|
1631
|
+
flags=re.VERBOSE,
|
1632
|
+
)
|
1663
1633
|
|
1634
|
+
@classmethod
|
1635
|
+
def match_to_datetime(cls, match: re.Match) -> ta.Union[datetime.datetime, datetime.date]:
|
1636
|
+
"""
|
1637
|
+
Convert a `RE_DATETIME` match to `datetime.datetime` or `datetime.date`.
|
1664
1638
|
|
1665
|
-
|
1666
|
-
|
1639
|
+
Raises ValueError if the match does not correspond to a valid date or datetime.
|
1640
|
+
"""
|
1667
1641
|
|
1668
|
-
|
1669
|
-
|
1670
|
-
|
1671
|
-
|
1672
|
-
|
1673
|
-
|
1642
|
+
(
|
1643
|
+
year_str,
|
1644
|
+
month_str,
|
1645
|
+
day_str,
|
1646
|
+
hour_str,
|
1647
|
+
minute_str,
|
1648
|
+
sec_str,
|
1649
|
+
micros_str,
|
1650
|
+
zulu_time,
|
1651
|
+
offset_sign_str,
|
1652
|
+
offset_hour_str,
|
1653
|
+
offset_minute_str,
|
1654
|
+
) = match.groups()
|
1655
|
+
year, month, day = int(year_str), int(month_str), int(day_str)
|
1656
|
+
if hour_str is None:
|
1657
|
+
return datetime.date(year, month, day)
|
1658
|
+
hour, minute, sec = int(hour_str), int(minute_str), int(sec_str)
|
1659
|
+
micros = int(micros_str.ljust(6, '0')) if micros_str else 0
|
1660
|
+
if offset_sign_str:
|
1661
|
+
tz: ta.Optional[datetime.tzinfo] = toml_cached_tz(
|
1662
|
+
offset_hour_str, offset_minute_str, offset_sign_str,
|
1663
|
+
)
|
1664
|
+
elif zulu_time:
|
1665
|
+
tz = datetime.UTC
|
1666
|
+
else: # local date-time
|
1667
|
+
tz = None
|
1668
|
+
return datetime.datetime(year, month, day, hour, minute, sec, micros, tzinfo=tz)
|
1674
1669
|
|
1675
|
-
|
1676
|
-
|
1677
|
-
|
1678
|
-
|
1679
|
-
return
|
1670
|
+
@classmethod
|
1671
|
+
def match_to_localtime(cls, match: re.Match) -> datetime.time:
|
1672
|
+
hour_str, minute_str, sec_str, micros_str = match.groups()
|
1673
|
+
micros = int(micros_str.ljust(6, '0')) if micros_str else 0
|
1674
|
+
return datetime.time(int(hour_str), int(minute_str), int(sec_str), micros)
|
1680
1675
|
|
1681
|
-
|
1676
|
+
@classmethod
|
1677
|
+
def match_to_number(cls, match: re.Match, parse_float: TomlParseFloat) -> ta.Any:
|
1678
|
+
if match.group('floatpart'):
|
1679
|
+
return parse_float(match.group())
|
1680
|
+
return int(match.group(), 0)
|
1682
1681
|
|
1683
1682
|
|
1684
1683
|
########################################
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: omdev
|
3
|
-
Version: 0.0.0.
|
3
|
+
Version: 0.0.0.dev269
|
4
4
|
Summary: omdev
|
5
5
|
Author: wrmsr
|
6
6
|
License: BSD-3-Clause
|
@@ -12,7 +12,7 @@ Classifier: Operating System :: OS Independent
|
|
12
12
|
Classifier: Operating System :: POSIX
|
13
13
|
Requires-Python: >=3.12
|
14
14
|
License-File: LICENSE
|
15
|
-
Requires-Dist: omlish==0.0.0.
|
15
|
+
Requires-Dist: omlish==0.0.0.dev269
|
16
16
|
Provides-Extra: all
|
17
17
|
Requires-Dist: black~=25.1; extra == "all"
|
18
18
|
Requires-Dist: pycparser~=2.22; extra == "all"
|
@@ -215,7 +215,7 @@ omdev/scripts/execrss.py,sha256=mR0G0wERBYtQmVIn63lCIIFb5zkCM6X_XOENDFYDBKc,651
|
|
215
215
|
omdev/scripts/exectime.py,sha256=S2O4MgtzTsFOY2IUJxsrnOIame9tEFc6aOlKP-F1JSg,1541
|
216
216
|
omdev/scripts/importtrace.py,sha256=NjRilVNBugswrNflIhPCPHRTd-vIijqSZ8C7J8lariI,14038
|
217
217
|
omdev/scripts/interp.py,sha256=B3YviHFRbJoom4dvmGdJ7Y0LiWWpE6p1igEme4IWspU,151083
|
218
|
-
omdev/scripts/pyproject.py,sha256=
|
218
|
+
omdev/scripts/pyproject.py,sha256=_WVVYRdyzgMKnuNYyjwduerjYFugZjdHgwAA-eUgCrQ,260133
|
219
219
|
omdev/scripts/slowcat.py,sha256=lssv4yrgJHiWfOiHkUut2p8E8Tq32zB-ujXESQxFFHY,2728
|
220
220
|
omdev/scripts/tmpexec.py,sha256=WTYcf56Tj2qjYV14AWmV8SfT0u6Y8eIU6cKgQRvEK3c,1442
|
221
221
|
omdev/tokens/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -253,9 +253,9 @@ omdev/tools/json/rendering.py,sha256=tMcjOW5edfozcMSTxxvF7WVTsbYLoe9bCKFh50qyaGw
|
|
253
253
|
omdev/tools/pawk/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
254
254
|
omdev/tools/pawk/__main__.py,sha256=VCqeRVnqT1RPEoIrqHFSu4PXVMg4YEgF4qCQm90-eRI,66
|
255
255
|
omdev/tools/pawk/pawk.py,sha256=zsEkfQX0jF5bn712uqPAyBSdJt2dno1LH2oeSMNfXQI,11424
|
256
|
-
omdev-0.0.0.
|
257
|
-
omdev-0.0.0.
|
258
|
-
omdev-0.0.0.
|
259
|
-
omdev-0.0.0.
|
260
|
-
omdev-0.0.0.
|
261
|
-
omdev-0.0.0.
|
256
|
+
omdev-0.0.0.dev269.dist-info/licenses/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
|
257
|
+
omdev-0.0.0.dev269.dist-info/METADATA,sha256=AqDahcQlEANe8r3cGhe2SVIQ97W8LuvioG1A1bKaQlI,1658
|
258
|
+
omdev-0.0.0.dev269.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
259
|
+
omdev-0.0.0.dev269.dist-info/entry_points.txt,sha256=dHLXFmq5D9B8qUyhRtFqTGWGxlbx3t5ejedjrnXNYLU,33
|
260
|
+
omdev-0.0.0.dev269.dist-info/top_level.txt,sha256=1nr7j30fEWgLYHW3lGR9pkdHkb7knv1U1ES1XRNVQ6k,6
|
261
|
+
omdev-0.0.0.dev269.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|