7171    "OTGV" , "OTGC" ,"meml" , "memu" , "box1" 
7272)
7373# fmt: on 
74- _STRING_CAPABILITY_NAMES  =  {name : i  for  i , name  in  enumerate (_STRING_NAMES )}
7574
7675
7776def  _get_terminfo_dirs () ->  list [Path ]:
@@ -322,10 +321,6 @@ class TermInfo:
322321    terminal_name : str  |  bytes  |  None 
323322    fallback : bool  =  True 
324323
325-     _names : list [str ] =  field (default_factory = list )
326-     _booleans : list [int ] =  field (default_factory = list )
327-     _numbers : list [int ] =  field (default_factory = list )
328-     _strings : list [bytes  |  None ] =  field (default_factory = list )
329324    _capabilities : dict [str , bytes ] =  field (default_factory = dict )
330325
331326    def  __post_init__ (self ) ->  None :
@@ -362,9 +357,12 @@ def __post_init__(self) -> None:
362357    def  _parse_terminfo_file (self , terminal_name : str ) ->  None :
363358        """Parse a terminfo file. 
364359
360+         Populate the _capabilities dict for easy retrieval 
361+ 
365362        Based on ncurses implementation in: 
366363        - ncurses/tinfo/read_entry.c:_nc_read_termtype() 
367364        - ncurses/tinfo/read_entry.c:_nc_read_file_entry() 
365+         - ncurses/tinfo/lib_ti.c:tigetstr() 
368366        """ 
369367        data  =  _read_terminfo_file (terminal_name )
370368        too_short  =  f"TermInfo file for { terminal_name !r}  
@@ -377,107 +375,67 @@ def _parse_terminfo_file(self, terminal_name: str) -> None:
377375        )
378376
379377        if  magic  ==  MAGIC16 :
380-             number_format  =  "<h"   # 16-bit signed 
381378            number_size  =  2 
382379        elif  magic  ==  MAGIC32 :
383-             number_format  =  "<i"   # 32-bit signed 
384380            number_size  =  4 
385381        else :
386382            raise  ValueError (
387383                f"TermInfo file for { terminal_name !r}  
388384            )
389385
390-         # Read terminal names 
391-         if  offset  +  name_size  >  len (data ):
392-             raise  ValueError (too_short )
393-         names  =  data [offset  : offset  +  name_size  -  1 ].decode (
394-             "ascii" , errors = "ignore" 
395-         )
386+         # Skip data than PyREPL doesn't need: 
387+         # - names (`|`-separated ASCII strings) 
388+         # - boolean capabilities (bytes with value 0 or 1) 
389+         # - numbers (little-endian integers, `number_size` bytes each) 
396390        offset  +=  name_size 
397- 
398-         # Read boolean capabilities 
399-         if  offset  +  bool_count  >  len (data ):
400-             raise  ValueError (too_short )
401-         booleans  =  list (data [offset  : offset  +  bool_count ])
402391        offset  +=  bool_count 
403- 
404-         # Align to even byte boundary for numbers 
405392        if  offset  %  2 :
393+             # Align to even byte boundary for numbers 
406394            offset  +=  1 
407- 
408-         # Read numeric capabilities 
409-         numbers  =  []
410-         for  i  in  range (num_count ):
411-             if  offset  +  number_size  >  len (data ):
412-                 raise  ValueError (too_short )
413-             num  =  struct .unpack (
414-                 number_format , data [offset  : offset  +  number_size ]
415-             )[0 ]
416-             numbers .append (num )
417-             offset  +=  number_size 
395+         offset  +=  num_count  *  number_size 
396+         if  offset  >  len (data ):
397+             raise  ValueError (too_short )
418398
419399        # Read string offsets 
420-         string_offsets  =  []
421-         for  i  in  range (str_count ):
422-             if  offset  +  2  >  len (data ):
423-                 raise  ValueError (too_short )
424-             off  =  struct .unpack ("<h" , data [offset  : offset  +  2 ])[0 ]
425-             string_offsets .append (off )
426-             offset  +=  2 
400+         end_offset  =  offset  +  2  *  str_count 
401+         if  offset  >  len (data ):
402+             raise  ValueError (too_short )
403+         string_offset_data  =  data [offset :end_offset ]
404+         string_offsets  =  [
405+             off  for  [off ] in  struct .iter_unpack ("<h" , string_offset_data )
406+         ]
407+         offset  =  end_offset 
427408
428409        # Read string table 
429410        if  offset  +  str_size  >  len (data ):
430411            raise  ValueError (too_short )
431412        string_table  =  data [offset  : offset  +  str_size ]
432413
433414        # Extract strings from string table 
434-         strings :  list [ bytes   |   None ]  =  [] 
435-         for  off  in  string_offsets :
415+         capabilities   =  {} 
416+         for  cap ,  off  in  zip ( _STRING_NAMES ,  string_offsets ) :
436417            if  off  <  0 :
437-                 strings .append (CANCELLED_STRING )
418+                 # CANCELLED_STRING; we do not store those 
419+                 continue 
438420            elif  off  <  len (string_table ):
439421                # Find null terminator 
440-                 end  =  off 
441-                 while  end  <  len (string_table ) and  string_table [end ] !=  0 :
442-                     end  +=  1 
443-                 if  end  <=  len (string_table ):
444-                     strings .append (string_table [off :end ])
445-                 else :
446-                     strings .append (ABSENT_STRING )
447-             else :
448-                 strings .append (ABSENT_STRING )
449- 
450-         self ._names  =  names .split ("|" )
451-         self ._booleans  =  booleans 
452-         self ._numbers  =  numbers 
453-         self ._strings  =  strings 
422+                 end  =  string_table .find (0 , off )
423+                 if  end  >=  0 :
424+                     capabilities [cap ] =  string_table [off :end ]
425+             # in other cases this is ABSENT_STRING; we don't store those. 
454426
455-     def   get ( self ,  cap :  str )  ->   bytes   |   None : 
456-         """Get terminal capability string by name .
427+          # Note: we don't support extended capabilities since PyREPL doesn't 
428+         # need them .
457429
458-         Based on ncurses implementation in: 
459-         - ncurses/tinfo/lib_ti.c:tigetstr() 
430+         self ._capabilities  =  capabilities 
460431
461-         The ncurses version searches through compiled terminfo data structures. 
462-         This version first checks parsed terminfo data, then falls back to 
463-         hardcoded capabilities. 
432+     def  get (self , cap : str ) ->  bytes  |  None :
433+         """Get terminal capability string by name. 
464434        """ 
465435        if  not  isinstance (cap , str ):
466436            raise  TypeError (f"`cap` must be a string, not { type (cap )}  )
467437
468-         if  self ._capabilities :
469-             # Fallbacks populated, use them 
470-             return  self ._capabilities .get (cap )
471- 
472-         # Look up in standard capabilities first 
473-         if  cap  in  _STRING_CAPABILITY_NAMES :
474-             index  =  _STRING_CAPABILITY_NAMES [cap ]
475-             if  index  <  len (self ._strings ):
476-                 return  self ._strings [index ]
477- 
478-         # Note: we don't support extended capabilities since PyREPL doesn't 
479-         # need them. 
480-         return  None 
438+         return  self ._capabilities .get (cap )
481439
482440
483441def  tparm (cap_bytes : bytes , * params : int ) ->  bytes :
0 commit comments