| 318 | |
| 319 | @dataclass |
| 320 | class TermInfo: |
| 321 | terminal_name: str | bytes | None |
| 322 | fallback: bool = True |
| 323 | |
| 324 | _capabilities: dict[str, bytes] = field(default_factory=dict) |
| 325 | |
| 326 | def __post_init__(self) -> None: |
| 327 | """Initialize terminal capabilities for the given terminal type. |
| 328 | |
| 329 | Based on ncurses implementation in: |
| 330 | - ncurses/tinfo/lib_setup.c:setupterm() and _nc_setupterm() |
| 331 | - ncurses/tinfo/lib_setup.c:TINFO_SETUP_TERM() |
| 332 | |
| 333 | This version first attempts to read terminfo database files like ncurses, |
| 334 | then, if `fallback` is True, falls back to hardcoded capabilities for |
| 335 | common terminal types. |
| 336 | """ |
| 337 | # If termstr is None or empty, try to get from environment |
| 338 | if not self.terminal_name: |
| 339 | self.terminal_name = os.environ.get("TERM") or "ANSI" |
| 340 | |
| 341 | if isinstance(self.terminal_name, bytes): |
| 342 | self.terminal_name = self.terminal_name.decode("ascii") |
| 343 | |
| 344 | try: |
| 345 | self._parse_terminfo_file(self.terminal_name) |
| 346 | except (OSError, ValueError): |
| 347 | if not self.fallback: |
| 348 | raise |
| 349 | |
| 350 | term_type = _TERM_ALIASES.get( |
| 351 | self.terminal_name, self.terminal_name |
| 352 | ) |
| 353 | if term_type not in _TERMINAL_CAPABILITIES: |
| 354 | term_type = "dumb" |
| 355 | self._capabilities = _TERMINAL_CAPABILITIES[term_type].copy() |
| 356 | |
| 357 | def _parse_terminfo_file(self, terminal_name: str) -> None: |
| 358 | """Parse a terminfo file. |
| 359 | |
| 360 | Populate the _capabilities dict for easy retrieval |
| 361 | |
| 362 | Based on ncurses implementation in: |
| 363 | - ncurses/tinfo/read_entry.c:_nc_read_termtype() |
| 364 | - ncurses/tinfo/read_entry.c:_nc_read_file_entry() |
| 365 | - ncurses/tinfo/lib_ti.c:tigetstr() |
| 366 | """ |
| 367 | data = _read_terminfo_file(terminal_name) |
| 368 | too_short = f"TermInfo file for {terminal_name!r} too short" |
| 369 | offset = 12 |
| 370 | if len(data) < offset: |
| 371 | raise ValueError(too_short) |
| 372 | |
| 373 | magic, name_size, bool_count, num_count, str_count, str_size = ( |
| 374 | struct.unpack("<Hhhhhh", data[:offset]) |
| 375 | ) |
| 376 | |
| 377 | if magic == MAGIC16: |
searching dependent graphs…