mirror of https://github.com/morpheus65535/bazarr
89 lines
3.5 KiB
Python
89 lines
3.5 KiB
Python
from __future__ import annotations
|
|
_C=True
|
|
_B=False
|
|
_A='\n'
|
|
from collections.abc import Generator,Mapping
|
|
from datetime import date,datetime,time
|
|
from decimal import Decimal
|
|
import string
|
|
from types import MappingProxyType
|
|
from typing import Any,BinaryIO,NamedTuple
|
|
ASCII_CTRL=frozenset(chr(A)for A in range(32))|frozenset(chr(127))
|
|
ILLEGAL_BASIC_STR_CHARS=frozenset('"\\')|ASCII_CTRL-frozenset('\t')
|
|
BARE_KEY_CHARS=frozenset(string.ascii_letters+string.digits+'-_')
|
|
ARRAY_TYPES=list,tuple
|
|
ARRAY_INDENT=' '*4
|
|
MAX_LINE_LENGTH=100
|
|
COMPACT_ESCAPES=MappingProxyType({'\x08':'\\b',_A:'\\n','\x0c':'\\f','\r':'\\r','"':'\\"','\\':'\\\\'})
|
|
def dump(__obj,__fp,*,multiline_strings=_B):
|
|
A=Context(multiline_strings,{})
|
|
for B in gen_table_chunks(__obj,A,name=''):__fp.write(B.encode())
|
|
def dumps(__obj,*,multiline_strings=_B):A=Context(multiline_strings,{});return''.join(gen_table_chunks(__obj,A,name=''))
|
|
class Context(NamedTuple):allow_multiline:bool;inline_table_cache:dict[int,str]
|
|
def gen_table_chunks(table,ctx,*,name,inside_aot=_B):
|
|
H=inside_aot;G=ctx;C=name;D=_B;E=[];F=[]
|
|
for(B,A)in table.items():
|
|
if isinstance(A,dict):F.append((B,A,_B))
|
|
elif is_aot(A)and not all(is_suitable_inline_table(A,G)for A in A):F.extend((B,A,_C)for A in A)
|
|
else:E.append((B,A))
|
|
if H or C and(E or not F):D=_C;yield f"[[{C}]]\n"if H else f"[{C}]\n"
|
|
if E:
|
|
D=_C
|
|
for(B,A)in E:yield f"{format_key_part(B)} = {format_literal(A,G)}\n"
|
|
for(B,A,J)in F:
|
|
if D:yield _A
|
|
else:D=_C
|
|
I=format_key_part(B);K=f"{C}.{I}"if C else I;yield from gen_table_chunks(A,G,name=K,inside_aot=J)
|
|
def format_literal(obj,ctx,*,nest_level=0):
|
|
B=ctx;A=obj
|
|
if isinstance(A,bool):return'true'if A else'false'
|
|
if isinstance(A,(int,float,date,datetime)):return str(A)
|
|
if isinstance(A,Decimal):return format_decimal(A)
|
|
if isinstance(A,time):
|
|
if A.tzinfo:raise ValueError('TOML does not support offset times')
|
|
return str(A)
|
|
if isinstance(A,str):return format_string(A,allow_multiline=B.allow_multiline)
|
|
if isinstance(A,ARRAY_TYPES):return format_inline_array(A,B,nest_level)
|
|
if isinstance(A,dict):return format_inline_table(A,B)
|
|
raise TypeError(f"Object of type {type(A)} is not TOML serializable")
|
|
def format_decimal(obj):
|
|
C='-inf';B='inf';A=obj
|
|
if A.is_nan():return'nan'
|
|
if A==Decimal(B):return B
|
|
if A==Decimal(C):return C
|
|
return str(A)
|
|
def format_inline_table(obj,ctx):
|
|
B=obj;A=ctx;C=id(B)
|
|
if C in A.inline_table_cache:return A.inline_table_cache[C]
|
|
if not B:D='{}'
|
|
else:D='{ '+', '.join(f"{format_key_part(B)} = {format_literal(C,A)}"for(B,C)in B.items())+' }'
|
|
A.inline_table_cache[C]=D;return D
|
|
def format_inline_array(obj,ctx,nest_level):
|
|
A=nest_level
|
|
if not obj:return'[]'
|
|
B=ARRAY_INDENT*(1+A);C=ARRAY_INDENT*A;return'[\n'+',\n'.join(B+format_literal(C,ctx,nest_level=A+1)for C in obj)+f",\n{C}]"
|
|
def format_key_part(part):
|
|
A=part
|
|
if A and BARE_KEY_CHARS.issuperset(A):return A
|
|
return format_string(A,allow_multiline=_B)
|
|
def format_string(s,*,allow_multiline):
|
|
D=allow_multiline and _A in s
|
|
if D:A='"""\n';s=s.replace('\r\n',_A)
|
|
else:A='"'
|
|
B=E=0
|
|
while _C:
|
|
try:C=s[B]
|
|
except IndexError:
|
|
A+=s[E:B]
|
|
if D:return A+'"""'
|
|
return A+'"'
|
|
if C in ILLEGAL_BASIC_STR_CHARS:
|
|
A+=s[E:B]
|
|
if C in COMPACT_ESCAPES:
|
|
if D and C==_A:A+=_A
|
|
else:A+=COMPACT_ESCAPES[C]
|
|
else:A+='\\u'+hex(ord(C))[2:].rjust(4,'0')
|
|
E=B+1
|
|
B+=1
|
|
def is_aot(obj):A=obj;return bool(isinstance(A,ARRAY_TYPES)and A and all(isinstance(A,dict)for A in A))
|
|
def is_suitable_inline_table(obj,ctx):A=f"{ARRAY_INDENT}{format_inline_table(obj,ctx)},";return len(A)<=MAX_LINE_LENGTH and _A not in A |