Skip to content

Commit 71ae3c0

Browse files
committed
Skip parsing/storing stuff we don't need
1 parent 9f60858 commit 71ae3c0

1 file changed

Lines changed: 30 additions & 69 deletions

File tree

Lib/_pyrepl/terminfo.py

Lines changed: 30 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -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

481442
def tparm(cap_bytes: bytes, *params: int) -> bytes:

0 commit comments

Comments
 (0)