@@ -744,6 +744,10 @@ def remove(name):
744744 json .dump (PERS_MACROS , f , indent = 2 )
745745 except KeyError :
746746 pass
747+ try :
748+ del CODECS_CACHE [name ]
749+ except KeyError :
750+ pass
747751 for s in ["En" , "De" ]:
748752 try :
749753 delattr (builtins , "%s%scodeError" % (name .capitalize (), s ))
@@ -864,6 +868,7 @@ def _handle_error(token, position, output="", eename=None):
864868 """
865869 if errors == "strict" :
866870 msg = "'%s' codec can't %scode %s '%s' in %s %d"
871+ token = ensure_str (token )
867872 token = token [:7 ] + "..." if len (token ) > 10 else token
868873 err = getattr (builtins , exc )(msg % (eename or ename , ["en" , "de" ][decode ], kind , token , item , position ))
869874 err .output = output
@@ -968,36 +973,37 @@ def __register(search_function):
968973codecs .register = __register
969974
970975
971- def search (encoding_regex ):
976+ def search (encoding_regex , extended = True ):
972977 """ Function similar to lookup but allows to search for an encoding based on a regex instead. It searches this way
973978 into the local registry but also tries a simple lookup with the original lookup function. """
974979 matches = []
975- for search_function in __codecs_registry :
980+ for search_function in CODECS_OVERWRITTEN + __codecs_registry :
976981 n = search_function .__name__
977982 for name in [n , n .replace ("_" , "-" )]:
978983 if re .search (encoding_regex , name ):
979- matches .append (name )
984+ matches .append (n . replace ( "_" , "-" ) )
980985 continue
981- # in some cases, encoding_regex can match a generated string that uses a particular portion of its generating
982- # pattern ; e.g. we expect encoding_regex="uu_" to find "uu" and "uu_codec" while it can also find "morse" or
983- # "atbash" very rarely because of their dynamic patterns and the limited number of randomly generated strings
984- # so, we can use a qualified majority voting to ensure we do not get a "junk" encoding in the list of matches ;
985- # executing 5 times the string generation for a given codec but adding the codec to the list of matches only
986- # if we get at least 3 matches ensures that we consider up to 2 failures that could be stochastic, therefore
987- # drastically decreasing the probability to get a "junk" encoding in the matches list
988- c = 0
989- for i in range (5 ):
990- for s in generate_strings_from_regex (search_function .__pattern__ ):
991- if re .search (encoding_regex , s ):
992- c += 1
986+ if extended :
987+ # in some cases, encoding_regex can match a generated string that uses a particular portion of its
988+ # generating pattern ; e.g. we expect encoding_regex="uu_" to find "uu" and "uu_codec" while it can also
989+ # find "morse" or "atbash" very rarely because of their dynamic patterns and the limited number of randomly
990+ # generated strings
991+ # so, we can use a qualified majority voting to ensure we do not get a "junk" encoding in the list of
992+ # matches ; executing 5 times the string generation for a given codec but adding the codec to the list of
993+ # matches only if we get at least 3 matches ensures that we consider up to 2 failures that could be
994+ # stochastic, therefore drastically decreasing the probability to get a "junk" encoding in the matches list
995+ c = 0
996+ for i in range (5 ):
997+ for s in generate_strings_from_regex (search_function .__pattern__ ):
998+ if re .search (encoding_regex , s ):
999+ c += 1
1000+ break
1001+ if c >= 3 :
1002+ matches .append (n )
9931003 break
994- if c >= 3 :
995- matches .append (n )
996- break
9971004 for s , n in ALIASES .items ():
9981005 if re .search (encoding_regex , s ) or re .search (encoding_regex , n ):
9991006 matches .append (n )
1000- break
10011007 return sorted (list (set (matches )), key = _human_keys )
10021008codecs .search = search
10031009
@@ -1241,7 +1247,7 @@ def _validate(stop_function, lang_backend="none"):
12411247stopfunc ._validate = _validate
12421248
12431249
1244- def __guess (prev_input , input , stop_func , depth , max_depth , min_depth , encodings , codecs , result , found = (),
1250+ def __guess (prev_input , input , stop_func , depth , max_depth , min_depth , encodings , result , found = (),
12451251 stop = True , show = False , scoring_heuristic = False , extended = False , debug = False ):
12461252 """ Perform a breadth-first tree search using a ranking logic to select and prune the list of codecs. """
12471253 if depth > min_depth and stop_func (input ):
@@ -1255,58 +1261,53 @@ def __guess(prev_input, input, stop_func, depth, max_depth, min_depth, encodings
12551261 return
12561262 prev_enc = found [- 1 ] if len (found ) > 0 else ""
12571263 e = encodings .get (depth , encodings .get (- 1 , []))
1258- for new_input , encoding in __rank (prev_input , input , prev_enc , e , codecs , scoring_heuristic , extended ):
1264+ for new_input , encoding in __rank (prev_input , input , prev_enc , e , scoring_heuristic , extended ):
12591265 if len (result ) > 0 and stop :
12601266 return
12611267 if debug :
12621268 print ("[*] Depth %0{}d/%d: %s" .format (len (str (max_depth ))) % (depth + 1 , max_depth , encoding ))
1263- __guess (input , new_input , stop_func , depth + 1 , max_depth , min_depth , encodings , codecs , result ,
1264- found + ( encoding , ), stop , show , scoring_heuristic , extended , debug )
1269+ __guess (input , new_input , stop_func , depth + 1 , max_depth , min_depth , encodings , result , found + ( encoding , ) ,
1270+ stop , show , scoring_heuristic , extended , debug )
12651271
12661272
12671273def __make_encodings_dict (include , exclude ):
12681274 """ Process encodings inclusion and exclusion lists, listing categories and developping codecs' lists of possible
12691275 encoding names. It also creates a cache with the CodecInfo objects for improving performance. """
1270- codecs = {}
12711276 def _develop (d , keep = True ):
12721277 d = d or {}
12731278 for k , v in d .items ():
12741279 l , cc = [], [e for e in v if e in CODECS_CATEGORIES ]
1275- for enc in (list_encodings (* cc ) if len (cc ) > 0 or keep else [] + \
1280+ # list from in-scope categories and then everything that is not a category
1281+ for enc in ((list_encodings (* cc ) if len (cc ) > 0 or keep else []) + \
12761282 [e for e in v if e not in CODECS_CATEGORIES ]):
1277- try :
1278- g = lookup (enc , False ).parameters ['guess' ]
1279- except :
1280- g = [enc ]
1281- if enc in g and not keep : # e.g. "rot-1" => ["rot-1", "rot-2", ...] ; only "rot-1" is to be selected
1283+ g = []
1284+ for e in (search (enc , False ) or [enc ]):
1285+ try :
1286+ ci = lookup (e , False )
1287+ g .extend (ci .parameters ['guess' ])
1288+ except :
1289+ pass
1290+ if enc in g : # e.g. "rot-1" => ["rot-1", "rot-2", ...] ; only "rot-1" is to be selected
12821291 l .append (enc )
1283- else : # e.g. "rot" => ["rot-1", "rot-2", ...] ; all the "rot-N" shall be selected
1292+ else : # e.g. "rot" => ["rot-1", "rot-2", ...] ; all the "rot-N" shall be selected
12841293 l .extend (g )
1285- d [k ] = l
1286- if keep :
1287- for e in l :
1288- # cache newly loaded CodecInfo objects
1289- ci = lookup (e , False )
1290- n = ci .name
1291- if n in CODECS_CACHE :
1292- ci = CODECS_CACHE [n ] # keep the cached object
1293- else :
1294- CODECS_CACHE [n ] = ci # cache the new object
1295- codecs [e ] = ci
1294+ d [k ] = list (set (l ))
12961295 return d
12971296 exclude = _develop (exclude , False )
1298- return {k : [x for x in v if x not in exclude .get (k , [])] for k , v in _develop (include ).items ()}, codecs
1297+ return {k : [x for x in v if x not in exclude .get (k , [])] for k , v in _develop (include ).items ()}
12991298
13001299
1301- def __rank (prev_input , input , prev_encoding , encodings , codecs , heuristic = False , extended = False , yield_score = False ):
1300+ def __rank (prev_input , input , prev_encoding , encodings , heuristic = False , extended = False , yield_score = False ):
13021301 """ Filter valid encodings and rank them by relevance. """
13031302 ranking = {}
1304- for encoding in encodings :
1303+ for e in encodings :
13051304 try :
1306- score , new = __score (prev_input , input , prev_encoding , encoding , codecs .get (encoding ), heuristic , extended )
1307- except TypeError :
1308- continue
1309- ranking [encoding ] = (score , new )
1305+ codec = CODECS_CACHE [e ]
1306+ except KeyError :
1307+ CODECS_CACHE [e ] = codec = lookup (e , False )
1308+ t = __score (prev_input , input , prev_encoding , e , codec , heuristic , extended )
1309+ if t :
1310+ ranking [e ] = t
13101311 for encoding , result in sorted (ranking .items (), key = lambda x : - x [1 ][0 ]):
13111312 yield result if yield_score else result [1 ], encoding
13121313
@@ -1315,16 +1316,16 @@ class _Text(object):
13151316 __slots__ = ["entropy" , "lcharset" , "len" , "padding" , "printables" , "text" ]
13161317
13171318 def __init__ (self , text , pad_char = None ):
1318- self .text = text
1319- c = text [- 1 ]
1320- pad_char , last_char = (b (pad_char ), c ) if isinstance (c , int ) else (pad_char , ord ( c ) )
1321- self .padding = pad_char is not None and last_char == ord ( pad_char )
1319+ self .text = ensure_str ( text )
1320+ c = self . text [- 1 ]
1321+ pad_char , last_char = (chr (pad_char ), chr ( c )) if isinstance (c , int ) else (pad_char , c )
1322+ self .padding = pad_char is not None and last_char == pad_char
13221323 if self .padding :
13231324 text = text .rstrip (pad_char )
1324- self .len = len (text )
1325- self .lcharset = len (set (text ))
1326- self .printables = float (len ([c for c in text if ( chr ( c ) if isinstance ( c , int ) else c ) in printable ])) / self .len
1327- self .entropy = entropy (text )
1325+ self .len = len (self . text )
1326+ self .lcharset = len (set (self . text ))
1327+ self .printables = float (len ([c for c in self . text if c in printable ])) / self .len
1328+ self .entropy = entropy (self . text )
13281329
13291330
13301331def __score (prev_input , input , prev_encoding , encoding , codec , heuristic = False , extended = False ):
@@ -1386,13 +1387,14 @@ def __score(prev_input, input, prev_encoding, encoding, codec, heuristic=False,
13861387 except TypeError :
13871388 expf = expf (f )
13881389 if isinstance (expf , (int , float )):
1390+ tmp = expf
13891391 expf = (1 / f - .1 <= 1 / expf <= 1 / f + .1 )
13901392 elif isinstance (expf , (tuple , list )) and len (expf ) == 2 :
13911393 expf = 1 / f - expf [1 ] <= 1 / expf [0 ] <= 1 / f + expf [1 ]
13921394 s += [- 1. , .1 ][expf ]
13931395 # afterwards, if the input text has an entropy close to the expected one, give a bonus weighted on the
13941396 # number of input characters to take bad entropies of shorter strings into account
1395- entr = sc .get ('entropy' , {} )
1397+ entr = sc .get ('entropy' , lambda e : e )
13961398 entr = entr .get (encoding , entr .get ('default' )) if isinstance (entr , dict ) else entr
13971399 if isinstance (entr , type (lambda : None )):
13981400 try : # this case allows to consider the current encoding name from the current codec
@@ -1401,7 +1403,7 @@ def __score(prev_input, input, prev_encoding, encoding, codec, heuristic=False,
14011403 entr = entr (obj .entropy )
14021404 if entr is not None :
14031405 # use a quadratic heuristic to compute a weight for the entropy delta, aligned on (100w,.1) and (200w,1)
1404- d_entr = min (4e-05 * obj .len ** 2 - .003 * obj .len , 1 ) * abs (entr - entropy (new_input ))
1406+ d_entr = min (5.958194e-06 * obj .len ** 2 - .002381 * obj .len , 1 ) * abs (entr - entropy (new_input ))
14051407 if d_entr <= .5 :
14061408 s += .5 - d_entr
14071409 # finally, if relevant, apply a custom bonus (e.g. when a regex pattern is matched)
@@ -1475,12 +1477,11 @@ def guess(input, stop_func=stopfunc.default, min_depth=0, max_depth=5, include=N
14751477 if not isinstance (l , dict ) or not all (isinstance (k , int ) for k in l .keys ()):
14761478 raise ValueError ("Include argument shall be a list or a dictionary with integer keys" )
14771479 # precompute encodings lists per depth and cache the related CodecInfo objects
1478- encodings , codecs = __make_encodings_dict (include , exclude )
1479- result = {}
1480+ encodings , result = __make_encodings_dict (include , exclude ), {}
14801481 try :
14811482 # breadth-first search
14821483 for d in range (max_depth ):
1483- __guess ("" , input , stop_func , 0 , d + 1 , min_depth , encodings , codecs , result , tuple (found ), stop , show ,
1484+ __guess ("" , input , stop_func , 0 , d + 1 , min_depth , encodings , result , tuple (found ), stop , show ,
14841485 scoring_heuristic , extended , debug )
14851486 if stop and len (result ) > 0 :
14861487 break
@@ -1500,9 +1501,8 @@ def rank(input, extended=False, limit=-1, include=None, exclude=None):
15001501 :param include: inclusion list with category, codec or encoding names (nothing means include every encoding)
15011502 :param exclude: exclusion list with category, codec or encoding names (nothing means exclude no encoding)
15021503 """
1503- encodings , codecs = __make_encodings_dict ({0 : include or CODECS_CATEGORIES }, {0 : exclude or []})
1504- r = list (__rank (None , input , "" , encodings [0 ], codecs , True , extended , True ))
1505- CODECS_CACHE = {}
1504+ encodings = __make_encodings_dict ({- 1 : include or CODECS_CATEGORIES }, {- 1 : exclude or []})
1505+ r = list (__rank (None , input , "" , encodings [- 1 ], True , extended , True ))
15061506 return r [:limit ] if len (r ) > 1 else r
15071507codecs .rank = rank
15081508
0 commit comments