You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
bazarr/libs/dynaconf/vendor/tomllib/_parser.py

298 lines
11 KiB

from __future__ import annotations
_H='flags'
_G='Cannot overwrite a value'
_F='recursive_flags'
_E='nested'
_D='\n'
_C=False
_B=None
_A=True
from collections.abc import Iterable
import string
from types import MappingProxyType
from typing import Any,BinaryIO,NamedTuple
from._re import RE_DATETIME,RE_LOCALTIME,RE_NUMBER,match_to_datetime,match_to_localtime,match_to_number
from._types import Key,ParseFloat,Pos
ASCII_CTRL=frozenset(chr(A)for A in range(32))|frozenset(chr(127))
ILLEGAL_BASIC_STR_CHARS=ASCII_CTRL-frozenset('\t')
ILLEGAL_MULTILINE_BASIC_STR_CHARS=ASCII_CTRL-frozenset('\t\n')
ILLEGAL_LITERAL_STR_CHARS=ILLEGAL_BASIC_STR_CHARS
ILLEGAL_MULTILINE_LITERAL_STR_CHARS=ILLEGAL_MULTILINE_BASIC_STR_CHARS
ILLEGAL_COMMENT_CHARS=ILLEGAL_BASIC_STR_CHARS
TOML_WS=frozenset(' \t')
TOML_WS_AND_NEWLINE=TOML_WS|frozenset(_D)
BARE_KEY_CHARS=frozenset(string.ascii_letters+string.digits+'-_')
KEY_INITIAL_CHARS=BARE_KEY_CHARS|frozenset('"\'')
HEXDIGIT_CHARS=frozenset(string.hexdigits)
BASIC_STR_ESCAPE_REPLACEMENTS=MappingProxyType({'\\b':'\x08','\\t':'\t','\\n':_D,'\\f':'\x0c','\\r':'\r','\\"':'"','\\\\':'\\'})
class TOMLDecodeError(ValueError):0
def load(A,*,parse_float=float):
B=A.read()
try:C=B.decode()
except AttributeError:raise TypeError("File must be opened in binary mode, e.g. use `open('foo.toml', 'rb')`")from _B
return loads(C,parse_float=parse_float)
def loads(H,*,parse_float=float):
E=parse_float;B=H.replace('\r\n',_D);A=0;D=Output(NestedDict(),Flags());F=();E=make_safe_parse_float(E)
while _A:
A=skip_chars(B,A,TOML_WS)
try:C=B[A]
except IndexError:break
if C==_D:A+=1;continue
if C in KEY_INITIAL_CHARS:A=key_value_rule(B,A,D,F,E);A=skip_chars(B,A,TOML_WS)
elif C=='[':
try:G=B[A+1]
except IndexError:G=_B
D.flags.finalize_pending()
if G=='[':A,F=create_list_rule(B,A,D)
else:A,F=create_dict_rule(B,A,D)
A=skip_chars(B,A,TOML_WS)
elif C!='#':raise suffixed_err(B,A,'Invalid statement')
A=skip_comment(B,A)
try:C=B[A]
except IndexError:break
if C!=_D:raise suffixed_err(B,A,'Expected newline or end of document after a statement')
A+=1
return D.data.dict
class Flags:
FROZEN=0;EXPLICIT_NEST=1
def __init__(A):A._flags={};A._pending_flags=set()
def add_pending(A,key,flag):A._pending_flags.add((key,flag))
def finalize_pending(A):
for(B,C)in A._pending_flags:A.set(B,C,recursive=_C)
A._pending_flags.clear()
def unset_all(C,key):
A=C._flags
for B in key[:-1]:
if B not in A:return
A=A[B][_E]
A.pop(key[-1],_B)
def set(D,key,flag,*,recursive):
A=D._flags;E,B=key[:-1],key[-1]
for C in E:
if C not in A:A[C]={_H:set(),_F:set(),_E:{}}
A=A[C][_E]
if B not in A:A[B]={_H:set(),_F:set(),_E:{}}
A[B][_F if recursive else _H].add(flag)
def is_(G,key,flag):
C=flag;B=key
if not B:return _C
A=G._flags
for D in B[:-1]:
if D not in A:return _C
E=A[D]
if C in E[_F]:return _A
A=E[_E]
F=B[-1]
if F in A:A=A[F];return C in A[_H]or C in A[_F]
return _C
class NestedDict:
def __init__(A):A.dict={}
def get_or_create_nest(C,key,*,access_lists=_A):
A=C.dict
for B in key:
if B not in A:A[B]={}
A=A[B]
if access_lists and isinstance(A,list):A=A[-1]
if not isinstance(A,dict):raise KeyError('There is no nest behind this key')
return A
def append_nest_to_list(D,key):
A=D.get_or_create_nest(key[:-1]);B=key[-1]
if B in A:
C=A[B]
if not isinstance(C,list):raise KeyError('An object other than list found behind this key')
C.append({})
else:A[B]=[{}]
class Output(NamedTuple):data:NestedDict;flags:Flags
def skip_chars(src,pos,chars):
A=pos
try:
while src[A]in chars:A+=1
except IndexError:pass
return A
def skip_until(src,pos,expect,*,error_on,error_on_eof):
E=error_on;D=expect;B=pos;A=src
try:C=A.index(D,B)
except ValueError:
C=len(A)
if error_on_eof:raise suffixed_err(A,C,f"Expected {D!r}")from _B
if not E.isdisjoint(A[B:C]):
while A[B]not in E:B+=1
raise suffixed_err(A,B,f"Found invalid character {A[B]!r}")
return C
def skip_comment(src,pos):
A=pos
try:B=src[A]
except IndexError:B=_B
if B=='#':return skip_until(src,A+1,_D,error_on=ILLEGAL_COMMENT_CHARS,error_on_eof=_C)
return A
def skip_comments_and_array_ws(src,pos):
A=pos
while _A:
B=A;A=skip_chars(src,A,TOML_WS_AND_NEWLINE);A=skip_comment(src,A)
if A==B:return A
def create_dict_rule(src,pos,out):
D=out;B=src;A=pos;A+=1;A=skip_chars(B,A,TOML_WS);A,C=parse_key(B,A)
if D.flags.is_(C,Flags.EXPLICIT_NEST)or D.flags.is_(C,Flags.FROZEN):raise suffixed_err(B,A,f"Cannot declare {C} twice")
D.flags.set(C,Flags.EXPLICIT_NEST,recursive=_C)
try:D.data.get_or_create_nest(C)
except KeyError:raise suffixed_err(B,A,_G)from _B
if not B.startswith(']',A):raise suffixed_err(B,A,"Expected ']' at the end of a table declaration")
return A+1,C
def create_list_rule(src,pos,out):
D=out;B=src;A=pos;A+=2;A=skip_chars(B,A,TOML_WS);A,C=parse_key(B,A)
if D.flags.is_(C,Flags.FROZEN):raise suffixed_err(B,A,f"Cannot mutate immutable namespace {C}")
D.flags.unset_all(C);D.flags.set(C,Flags.EXPLICIT_NEST,recursive=_C)
try:D.data.append_nest_to_list(C)
except KeyError:raise suffixed_err(B,A,_G)from _B
if not B.startswith(']]',A):raise suffixed_err(B,A,"Expected ']]' at the end of an array declaration")
return A+2,C
def key_value_rule(src,pos,out,header,parse_float):
E=header;C=out;B=src;A=pos;A,D,H=parse_key_value_pair(B,A,parse_float);K,I=D[:-1],D[-1];F=E+K;L=(E+D[:A]for A in range(1,len(D)))
for G in L:
if C.flags.is_(G,Flags.EXPLICIT_NEST):raise suffixed_err(B,A,f"Cannot redefine namespace {G}")
C.flags.add_pending(G,Flags.EXPLICIT_NEST)
if C.flags.is_(F,Flags.FROZEN):raise suffixed_err(B,A,f"Cannot mutate immutable namespace {F}")
try:J=C.data.get_or_create_nest(F)
except KeyError:raise suffixed_err(B,A,_G)from _B
if I in J:raise suffixed_err(B,A,_G)
if isinstance(H,(dict,list)):C.flags.set(E+D,Flags.FROZEN,recursive=_A)
J[I]=H;return A
def parse_key_value_pair(src,pos,parse_float):
B=src;A=pos;A,D=parse_key(B,A)
try:C=B[A]
except IndexError:C=_B
if C!='=':raise suffixed_err(B,A,"Expected '=' after a key in a key/value pair")
A+=1;A=skip_chars(B,A,TOML_WS);A,E=parse_value(B,A,parse_float);return A,D,E
def parse_key(src,pos):
B=src;A=pos;A,C=parse_key_part(B,A);D=C,;A=skip_chars(B,A,TOML_WS)
while _A:
try:E=B[A]
except IndexError:E=_B
if E!='.':return A,D
A+=1;A=skip_chars(B,A,TOML_WS);A,C=parse_key_part(B,A);D+=C,;A=skip_chars(B,A,TOML_WS)
def parse_key_part(src,pos):
B=src;A=pos
try:C=B[A]
except IndexError:C=_B
if C in BARE_KEY_CHARS:D=A;A=skip_chars(B,A,BARE_KEY_CHARS);return A,B[D:A]
if C=="'":return parse_literal_str(B,A)
if C=='"':return parse_one_line_basic_str(B,A)
raise suffixed_err(B,A,'Invalid initial character for a key part')
def parse_one_line_basic_str(src,pos):pos+=1;return parse_basic_str(src,pos,multiline=_C)
def parse_array(src,pos,parse_float):
B=src;A=pos;A+=1;C=[];A=skip_comments_and_array_ws(B,A)
if B.startswith(']',A):return A+1,C
while _A:
A,E=parse_value(B,A,parse_float);C.append(E);A=skip_comments_and_array_ws(B,A);D=B[A:A+1]
if D==']':return A+1,C
if D!=',':raise suffixed_err(B,A,'Unclosed array')
A+=1;A=skip_comments_and_array_ws(B,A)
if B.startswith(']',A):return A+1,C
def parse_inline_table(src,pos,parse_float):
B=src;A=pos;A+=1;D=NestedDict();F=Flags();A=skip_chars(B,A,TOML_WS)
if B.startswith('}',A):return A+1,D.dict
while _A:
A,C,G=parse_key_value_pair(B,A,parse_float);J,E=C[:-1],C[-1]
if F.is_(C,Flags.FROZEN):raise suffixed_err(B,A,f"Cannot mutate immutable namespace {C}")
try:H=D.get_or_create_nest(J,access_lists=_C)
except KeyError:raise suffixed_err(B,A,_G)from _B
if E in H:raise suffixed_err(B,A,f"Duplicate inline table key {E!r}")
H[E]=G;A=skip_chars(B,A,TOML_WS);I=B[A:A+1]
if I=='}':return A+1,D.dict
if I!=',':raise suffixed_err(B,A,'Unclosed inline table')
if isinstance(G,(dict,list)):F.set(C,Flags.FROZEN,recursive=_A)
A+=1;A=skip_chars(B,A,TOML_WS)
def parse_basic_str_escape(src,pos,*,multiline=_C):
E="Unescaped '\\' in a string";D='\\\n';B=src;A=pos;C=B[A:A+2];A+=2
if multiline and C in{'\\ ','\\\t',D}:
if C!=D:
A=skip_chars(B,A,TOML_WS)
try:F=B[A]
except IndexError:return A,''
if F!=_D:raise suffixed_err(B,A,E)
A+=1
A=skip_chars(B,A,TOML_WS_AND_NEWLINE);return A,''
if C=='\\u':return parse_hex_char(B,A,4)
if C=='\\U':return parse_hex_char(B,A,8)
try:return A,BASIC_STR_ESCAPE_REPLACEMENTS[C]
except KeyError:raise suffixed_err(B,A,E)from _B
def parse_basic_str_escape_multiline(src,pos):return parse_basic_str_escape(src,pos,multiline=_A)
def parse_hex_char(src,pos,hex_len):
C=hex_len;B=src;A=pos;D=B[A:A+C]
if len(D)!=C or not HEXDIGIT_CHARS.issuperset(D):raise suffixed_err(B,A,'Invalid hex value')
A+=C;E=int(D,16)
if not is_unicode_scalar_value(E):raise suffixed_err(B,A,'Escaped character is not a Unicode scalar value')
return A,chr(E)
def parse_literal_str(src,pos):A=pos;A+=1;B=A;A=skip_until(src,A,"'",error_on=ILLEGAL_LITERAL_STR_CHARS,error_on_eof=_A);return A+1,src[B:A]
def parse_multiline_str(src,pos,*,literal):
B=src;A=pos;A+=3
if B.startswith(_D,A):A+=1
if literal:C="'";E=skip_until(B,A,"'''",error_on=ILLEGAL_MULTILINE_LITERAL_STR_CHARS,error_on_eof=_A);D=B[A:E];A=E+3
else:C='"';A,D=parse_basic_str(B,A,multiline=_A)
if not B.startswith(C,A):return A,D
A+=1
if not B.startswith(C,A):return A,D+C
A+=1;return A,D+C*2
def parse_basic_str(src,pos,*,multiline):
F=multiline;B=src;A=pos
if F:G=ILLEGAL_MULTILINE_BASIC_STR_CHARS;H=parse_basic_str_escape_multiline
else:G=ILLEGAL_BASIC_STR_CHARS;H=parse_basic_str_escape
C='';D=A
while _A:
try:E=B[A]
except IndexError:raise suffixed_err(B,A,'Unterminated string')from _B
if E=='"':
if not F:return A+1,C+B[D:A]
if B.startswith('"""',A):return A+3,C+B[D:A]
A+=1;continue
if E=='\\':C+=B[D:A];A,I=H(B,A);C+=I;D=A;continue
if E in G:raise suffixed_err(B,A,f"Illegal character {E!r}")
A+=1
def parse_value(src,pos,parse_float):
D=parse_float;B=src;A=pos
try:C=B[A]
except IndexError:C=_B
if C=='"':
if B.startswith('"""',A):return parse_multiline_str(B,A,literal=_C)
return parse_one_line_basic_str(B,A)
if C=="'":
if B.startswith("'''",A):return parse_multiline_str(B,A,literal=_A)
return parse_literal_str(B,A)
if C=='t':
if B.startswith('true',A):return A+4,_A
if C=='f':
if B.startswith('false',A):return A+5,_C
if C=='[':return parse_array(B,A,D)
if C=='{':return parse_inline_table(B,A,D)
E=RE_DATETIME.match(B,A)
if E:
try:J=match_to_datetime(E)
except ValueError as K:raise suffixed_err(B,A,'Invalid date or datetime')from K
return E.end(),J
F=RE_LOCALTIME.match(B,A)
if F:return F.end(),match_to_localtime(F)
G=RE_NUMBER.match(B,A)
if G:return G.end(),match_to_number(G,D)
H=B[A:A+3]
if H in{'inf','nan'}:return A+3,D(H)
I=B[A:A+4]
if I in{'-inf','+inf','-nan','+nan'}:return A+4,D(I)
raise suffixed_err(B,A,'Invalid value')
def suffixed_err(src,pos,msg):
def A(src,pos):
B=src;A=pos
if A>=len(B):return'end of document'
C=B.count(_D,0,A)+1
if C==1:D=A+1
else:D=A-B.rindex(_D,0,A)
return f"line {C}, column {D}"
return TOMLDecodeError(f"{msg} (at {A(src,pos)})")
def is_unicode_scalar_value(codepoint):A=codepoint;return 0<=A<=55295 or 57344<=A<=1114111
def make_safe_parse_float(parse_float):
A=parse_float
if A is float:return float
def B(float_str):
B=A(float_str)
if isinstance(B,(dict,list)):raise ValueError('parse_float must not return dicts or lists')
return B
return B