add
This commit is contained in:
@@ -0,0 +1,177 @@
|
||||
"""Shared data tables and constants for wcwidth.py, _wcwidth.py, and _wcswidth.py."""
|
||||
from __future__ import annotations
|
||||
|
||||
# std imports
|
||||
import os
|
||||
from operator import index as _index
|
||||
from functools import lru_cache
|
||||
|
||||
from typing import Tuple, NamedTuple
|
||||
|
||||
# local
|
||||
from .table_mc import CATEGORY_MC
|
||||
from .table_wide import WIDE_EASTASIAN
|
||||
from .table_zero import ZERO_WIDTH
|
||||
from .table_grapheme import (ISC_VIRAMA,
|
||||
EXTENDED_PICTOGRAPHIC,
|
||||
ISC_INVISIBLE_STACKER,
|
||||
GRAPHEME_REGIONAL_INDICATOR)
|
||||
from .table_ambiguous import AMBIGUOUS_EASTASIAN
|
||||
from .table_overrides import (SFZ_OVERRIDES,
|
||||
SRI_OVERRIDES,
|
||||
VS15_OVERRIDES,
|
||||
VS16_OVERRIDES,
|
||||
WIDE_OVERRIDES,
|
||||
NARROW_OVERRIDES)
|
||||
from .unicode_versions import list_versions
|
||||
from .table_term_programs import ALIASES, KNOWN_TERMINALS
|
||||
|
||||
_RangeTuple = Tuple[Tuple[int, int], ...]
|
||||
|
||||
|
||||
__all__ = (
|
||||
"_REGIONAL_INDICATOR_SET",
|
||||
"_ISC_VIRAMA_SET",
|
||||
"_LATEST_VERSION",
|
||||
"_CATEGORY_MC_TABLE",
|
||||
"_EMOJI_ZWJ_SET",
|
||||
"_FITZPATRICK_RANGE",
|
||||
"_ZERO_WIDTH_TABLE",
|
||||
"_WIDE_EASTASIAN_TABLE",
|
||||
"_AMBIGUOUS_TABLE",
|
||||
"resolve_terminal",
|
||||
"get_term_overrides",
|
||||
"list_term_programs",
|
||||
)
|
||||
|
||||
_REGIONAL_INDICATOR_SET = frozenset(
|
||||
range(GRAPHEME_REGIONAL_INDICATOR[0][0], GRAPHEME_REGIONAL_INDICATOR[0][1] + 1)
|
||||
)
|
||||
_ISC_VIRAMA_SET = frozenset(
|
||||
cp for lo, hi in (*ISC_VIRAMA, *ISC_INVISIBLE_STACKER)
|
||||
for cp in range(lo, hi + 1)
|
||||
)
|
||||
# pylint: disable=invalid-name
|
||||
_LATEST_VERSION = list_versions()[-1]
|
||||
_CATEGORY_MC_TABLE = CATEGORY_MC[_LATEST_VERSION]
|
||||
_EMOJI_ZWJ_SET = frozenset(
|
||||
cp for lo, hi in EXTENDED_PICTOGRAPHIC for cp in range(lo, hi + 1)
|
||||
) | _REGIONAL_INDICATOR_SET
|
||||
_FITZPATRICK_RANGE = (0x1F3FB, 0x1F3FF)
|
||||
|
||||
_ZERO_WIDTH_TABLE = ZERO_WIDTH[_LATEST_VERSION]
|
||||
_WIDE_EASTASIAN_TABLE = WIDE_EASTASIAN[_LATEST_VERSION]
|
||||
_AMBIGUOUS_TABLE = AMBIGUOUS_EASTASIAN[_LATEST_VERSION]
|
||||
|
||||
|
||||
def list_term_programs() -> tuple[str, ...]:
|
||||
"""
|
||||
Return all recognized values for the ``term_program`` argument.
|
||||
|
||||
Includes canonical terminal names and their TERM/TERM_PROGRAM aliases.
|
||||
|
||||
.. versionadded:: 0.8.0
|
||||
"""
|
||||
return tuple(sorted(KNOWN_TERMINALS | ALIASES.keys()))
|
||||
|
||||
|
||||
def _merge_ranges(*tuples: _RangeTuple) -> _RangeTuple:
|
||||
"""Merge multiple sorted range tuples into one sorted, non-overlapping tuple."""
|
||||
all_ranges: list[tuple[int, int]] = []
|
||||
for t in tuples:
|
||||
all_ranges.extend(t)
|
||||
if not all_ranges:
|
||||
return ()
|
||||
all_ranges.sort(key=lambda r: r[0])
|
||||
merged = [all_ranges[0]]
|
||||
for lo, hi in all_ranges[1:]:
|
||||
_, prev_hi = merged[-1]
|
||||
if lo <= prev_hi:
|
||||
merged[-1] = (merged[-1][0], max(prev_hi, hi))
|
||||
else:
|
||||
merged.append((lo, hi))
|
||||
return tuple(merged)
|
||||
|
||||
|
||||
class TerminalOverrides(NamedTuple):
|
||||
"""Pre-merged override range tuples for a single terminal."""
|
||||
|
||||
narrower: _RangeTuple
|
||||
vs16_narrower: _RangeTuple
|
||||
vs15_wider: _RangeTuple
|
||||
zeroer: _RangeTuple
|
||||
narrow_wider: _RangeTuple
|
||||
narrow_zeroer: _RangeTuple
|
||||
|
||||
|
||||
_EMPTY_OVERRIDES = TerminalOverrides((), (), (), (), (), ())
|
||||
|
||||
|
||||
@lru_cache(maxsize=32)
|
||||
def get_term_overrides(term_canonical: str) -> TerminalOverrides:
|
||||
"""Return a TerminalOverrides, with all empty tuples when there are no overrides."""
|
||||
# wide, sri, sfz: all narrow characters Unicode expects wide (no 'wider' data exists)
|
||||
narrower = _merge_ranges(
|
||||
WIDE_OVERRIDES.get(term_canonical, {}).get('narrower', ()),
|
||||
SRI_OVERRIDES.get(term_canonical, {}).get('narrower', ()),
|
||||
SFZ_OVERRIDES.get(term_canonical, {}).get('narrower', ()),
|
||||
)
|
||||
vs16_narrower = VS16_OVERRIDES.get(term_canonical, {}).get('narrower', ())
|
||||
vs15_wider = VS15_OVERRIDES.get(term_canonical, {}).get('wider', ())
|
||||
zeroer = _merge_ranges(
|
||||
WIDE_OVERRIDES.get(term_canonical, {}).get('zeroer', ()),
|
||||
SRI_OVERRIDES.get(term_canonical, {}).get('zeroer', ()),
|
||||
SFZ_OVERRIDES.get(term_canonical, {}).get('zeroer', ()),
|
||||
)
|
||||
narrow_wider = NARROW_OVERRIDES.get(term_canonical, {}).get('wider', ())
|
||||
narrow_zeroer = NARROW_OVERRIDES.get(term_canonical, {}).get('narrow_zeroer', ())
|
||||
# vs15_narrower intentionally excluded: no known terminal narrows VS15
|
||||
# vs16_wider intentionally excluded: any 'wider' entries in emoji_vs16_results
|
||||
# ucs-detect YAML are from the vs16n baseline test (base char without VS16),
|
||||
# not actual VS16 correction data.
|
||||
|
||||
if not (narrower or vs16_narrower or vs15_wider or zeroer
|
||||
or narrow_wider or narrow_zeroer):
|
||||
return _EMPTY_OVERRIDES
|
||||
return TerminalOverrides(narrower, vs16_narrower, vs15_wider, zeroer,
|
||||
narrow_wider, narrow_zeroer)
|
||||
|
||||
|
||||
@lru_cache(maxsize=32)
|
||||
def resolve_terminal(term_program: bool | str = False) -> str | None:
|
||||
"""
|
||||
Resolve a terminal identifier to its canonical name.
|
||||
|
||||
:param term_program: Terminal identifier. ``False`` (default) disables override lookup.
|
||||
``True`` reads the ``TERM_PROGRAM`` environment variable, falling back to ``TERM``.
|
||||
A string value is used directly (canonical name, alias, XTVERSION/ENQ result, etc.).
|
||||
:returns: Canonical terminal name if recognized, ``None`` otherwise.
|
||||
|
||||
The auto-detection path (``term_program=True``) reads environment variables at call time
|
||||
and caches the result. The environment is assumed immutable for the process lifetime;
|
||||
callers that change ``TERM`` or ``TERM_PROGRAM`` mid-process must call
|
||||
:func:`resolve_terminal.cache_clear` afterward.
|
||||
"""
|
||||
if term_program is False:
|
||||
return None
|
||||
if term_program is True:
|
||||
term_program = os.environ.get('TERM_PROGRAM', '') or os.environ.get('TERM', '')
|
||||
if not term_program:
|
||||
return None
|
||||
key = term_program.strip().lower()
|
||||
canonical = ALIASES.get(key, key)
|
||||
if canonical not in KNOWN_TERMINALS:
|
||||
return None
|
||||
return canonical
|
||||
|
||||
|
||||
def _clamp_ambiguous_width(ambiguous_width: int) -> int:
|
||||
"""Clamp *ambiguous_width* to 1 or 2."""
|
||||
# this operator.index() rejects bool/float and overall this is faster than
|
||||
# combining min() and max().
|
||||
value = _index(ambiguous_width)
|
||||
if value < 1:
|
||||
return 1
|
||||
if value > 2:
|
||||
return 2
|
||||
return value
|
||||
Reference in New Issue
Block a user