@@ -322,10 +322,6 @@ class TermInfo:
322322 terminal_name : str | bytes | None
323323 fallback : bool = True
324324
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 )
329325 _capabilities : dict [str , bytes ] = field (default_factory = dict )
330326
331327 def __post_init__ (self ) -> None :
@@ -362,9 +358,12 @@ def __post_init__(self) -> None:
362358 def _parse_terminfo_file (self , terminal_name : str ) -> None :
363359 """Parse a terminfo file.
364360
361+ Populate the _capabilities dict for easy retrieval
362+
365363 Based on ncurses implementation in:
366364 - ncurses/tinfo/read_entry.c:_nc_read_termtype()
367365 - ncurses/tinfo/read_entry.c:_nc_read_file_entry()
366+ - ncurses/tinfo/lib_ti.c:tigetstr()
368367 """
369368 data = _read_terminfo_file (terminal_name )
370369 too_short = f"TermInfo file for { terminal_name !r} too short"
@@ -377,105 +376,67 @@ def _parse_terminfo_file(self, terminal_name: str) -> None:
377376 )
378377
379378 if magic == MAGIC16 :
380- number_format = "<h" # 16-bit signed
381379 number_size = 2
382380 elif magic == MAGIC32 :
383- number_format = "<i" # 32-bit signed
384381 number_size = 4
385382 else :
386383 raise ValueError (
387384 f"TermInfo file for { terminal_name !r} uses unknown magic"
388385 )
389386
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- )
387+ # Skip data than PyREPL doesn't need:
388+ # - names (`|`-separated ASCII strings)
389+ # - boolean capabilities (bytes with value 0 or 1)
390+ # - numbers (little-endian integers, `number_size` bytes each)
396391 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 ])
402392 offset += bool_count
403-
404- # Align to even byte boundary for numbers
405393 if offset % 2 :
394+ # Align to even byte boundary for numbers
406395 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
396+ offset += num_count * number_size
397+ if offset > len (data ):
398+ raise ValueError (too_short )
418399
419400 # 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
401+ end_offset = offset + 2 * str_count
402+ if offset > len (data ):
403+ raise ValueError (too_short )
404+ string_offset_data = data [offset :end_offset ]
405+ string_offsets = [
406+ off for [off ] in struct .iter_unpack ("<h" , string_offset_data )
407+ ]
408+ offset = end_offset
427409
428410 # Read string table
429411 if offset + str_size > len (data ):
430412 raise ValueError (too_short )
431413 string_table = data [offset : offset + str_size ]
432414
433415 # Extract strings from string table
434- strings : list [ bytes | None ] = []
435- for off in string_offsets :
416+ capabilities = {}
417+ for cap , off in zip ( _STRING_CAPABILITY_NAMES , string_offsets ) :
436418 if off < 0 :
437- strings .append (CANCELLED_STRING )
419+ # CANCELLED_STRING; we do not store those
420+ continue
438421 elif off < len (string_table ):
439422 # Find null terminator
440423 end = string_table .find (0 , off )
441424 if end >= 0 :
442- strings .append (string_table [off :end ])
443- else :
444- strings .append (ABSENT_STRING )
445- else :
446- strings .append (ABSENT_STRING )
425+ capabilities [cap ] = string_table [off :end ]
426+ # in other cases this is ABSENT_STRING; we don't store those.
447427
448- self . _names = names . split ( "|" )
449- self . _booleans = booleans
450- self . _numbers = numbers
451- self ._strings = strings
428+ # Note: we don't support extended capabilities since PyREPL doesn't
429+ # need them.
430+
431+ self ._capabilities = capabilities
452432
453433 def get (self , cap : str ) -> bytes | None :
454434 """Get terminal capability string by name.
455-
456- Based on ncurses implementation in:
457- - ncurses/tinfo/lib_ti.c:tigetstr()
458-
459- The ncurses version searches through compiled terminfo data structures.
460- This version first checks parsed terminfo data, then falls back to
461- hardcoded capabilities.
462435 """
463436 if not isinstance (cap , str ):
464437 raise TypeError (f"`cap` must be a string, not { type (cap )} " )
465438
466- if self ._capabilities :
467- # Fallbacks populated, use them
468- return self ._capabilities .get (cap )
469-
470- # Look up in standard capabilities first
471- if cap in _STRING_CAPABILITY_NAMES :
472- index = _STRING_CAPABILITY_NAMES [cap ]
473- if index < len (self ._strings ):
474- return self ._strings [index ]
475-
476- # Note: we don't support extended capabilities since PyREPL doesn't
477- # need them.
478- return None
439+ return self ._capabilities .get (cap )
479440
480441
481442def tparm (cap_bytes : bytes , * params : int ) -> bytes :
0 commit comments