"""Parse data from raw incoming bytes."""from__future__importannotationsfromtypingimportTYPE_CHECKING,TypeVar,overloadfromcable_club.constantsimportUTF8fromcable_club.exceptionsimportExhaustedReaderError,ValidationErrorifTYPE_CHECKING:fromcollections.abcimportContainerfromtypingimportLiteralfromtyping_extensionsimportSelfT=TypeVar("T")
[docs]@classmethoddefnew(cls,raw:bytes)->Self|None:"""Try an initialize a reader."""self=cls()try:line=raw.decode(UTF8)exceptUnicodeDecodeError:# unvalid UTF8 came in (automated HTTP request?)# prevent raising and cluttering logsreturnNoneself.fields=[]field=""escape=Falseforcinline:ifc==","andnotescape:self.fields.append(field)field=""elifc=="\\"andnotescape:escape=Trueelse:field+=cescape=Falseself.fields.append(field)self.fields.reverse()returnself
[docs]defread(self,*,max_len:int|None=None,)->str:"""Get a raw item from the reader."""try:value=self.fields.pop()exceptIndexError:# pop from empty listraiseExhaustedReaderErrorfromNonelength=len(value)ifmax_lenisnotNoneandlength>max_len:msg=f"len(value) has to be <= {max_len} (was {length})."raiseValidationError(msg)returnvalue
[docs]defleft(self)->list[str]:"""Return raw data."""returnlist(reversed(self.fields))
[docs]defboolean(self,*,allow_none:bool=False)->bool|None:"""Get a bool from the reader."""raw=self.read()ifnotraw:ifallow_none:returnNonemsg="value was required"raiseValidationError(msg)return{"true":True,"false":False}[raw]
[docs]definteger(self,*,min_val:int|None=None,max_val:int|None=None,allow_none:bool=False,)->int|None:"""Get an int from the reader."""raw=self.read()ifnotraw:ifallow_none:returnNonemsg="value was required"raiseValidationError(msg)value=int(raw)ifmin_valisnotNoneandnotvalue>=min_val:msg=f"value as to be >= {min_val} (was {value})."raiseValidationError(msg)ifmax_valisnotNoneandnotvalue<=max_val:msg=f"value has to be <= {max_val} (was {value})."raiseValidationError(msg)returnvalue
[docs]defone_of(self,*,options:Container[str])->str:"""Read a value within a set of options."""value=self.read()ifvalueinoptions:returnvalueoptions=repr(options)max_len=50iflen(options)>=max_len:options=options[:max_len]+" ..."msg=f"value has to be one of: {options} (was '{value}')."raiseValidationError(msg)