Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/wcwidth/_constants.py: 53%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

72 statements  

1"""Shared data tables and constants for wcwidth.py, _wcwidth.py, and _wcswidth.py.""" 

2from __future__ import annotations 

3 

4# std imports 

5import os 

6from functools import lru_cache 

7 

8from typing import Tuple, NamedTuple 

9 

10# local 

11from .table_mc import CATEGORY_MC 

12from .table_wide import WIDE_EASTASIAN 

13from .table_zero import ZERO_WIDTH 

14from .table_grapheme import (ISC_VIRAMA, 

15 EXTENDED_PICTOGRAPHIC, 

16 ISC_INVISIBLE_STACKER, 

17 GRAPHEME_REGIONAL_INDICATOR) 

18from .table_ambiguous import AMBIGUOUS_EASTASIAN 

19from .table_overrides import (SFZ_OVERRIDES, 

20 SRI_OVERRIDES, 

21 VS15_OVERRIDES, 

22 VS16_OVERRIDES, 

23 WIDE_OVERRIDES, 

24 NARROW_OVERRIDES) 

25from .unicode_versions import list_versions 

26from .table_term_programs import ALIASES, KNOWN_TERMINALS 

27 

28_RangeTuple = Tuple[Tuple[int, int], ...] 

29 

30 

31__all__ = ( 

32 "_REGIONAL_INDICATOR_SET", 

33 "_ISC_VIRAMA_SET", 

34 "_LATEST_VERSION", 

35 "_CATEGORY_MC_TABLE", 

36 "_EMOJI_ZWJ_SET", 

37 "_FITZPATRICK_RANGE", 

38 "_ZERO_WIDTH_TABLE", 

39 "_WIDE_EASTASIAN_TABLE", 

40 "_AMBIGUOUS_TABLE", 

41 "resolve_terminal", 

42 "get_term_overrides", 

43 "list_term_programs", 

44) 

45 

46_REGIONAL_INDICATOR_SET = frozenset( 

47 range(GRAPHEME_REGIONAL_INDICATOR[0][0], GRAPHEME_REGIONAL_INDICATOR[0][1] + 1) 

48) 

49_ISC_VIRAMA_SET = frozenset( 

50 cp for lo, hi in (*ISC_VIRAMA, *ISC_INVISIBLE_STACKER) 

51 for cp in range(lo, hi + 1) 

52) 

53# pylint: disable=invalid-name 

54_LATEST_VERSION = list_versions()[-1] 

55_CATEGORY_MC_TABLE = CATEGORY_MC[_LATEST_VERSION] 

56_EMOJI_ZWJ_SET = frozenset( 

57 cp for lo, hi in EXTENDED_PICTOGRAPHIC for cp in range(lo, hi + 1) 

58) | _REGIONAL_INDICATOR_SET 

59_FITZPATRICK_RANGE = (0x1F3FB, 0x1F3FF) 

60 

61_ZERO_WIDTH_TABLE = ZERO_WIDTH[_LATEST_VERSION] 

62_WIDE_EASTASIAN_TABLE = WIDE_EASTASIAN[_LATEST_VERSION] 

63_AMBIGUOUS_TABLE = AMBIGUOUS_EASTASIAN[_LATEST_VERSION] 

64 

65 

66def list_term_programs() -> tuple[str, ...]: 

67 """ 

68 Return all recognized values for the ``term_program`` argument. 

69 

70 Includes canonical terminal names and their TERM/TERM_PROGRAM aliases. 

71 

72 .. versionadded:: 0.8.0 

73 """ 

74 return tuple(sorted(KNOWN_TERMINALS | ALIASES.keys())) 

75 

76 

77def _merge_ranges(*tuples: _RangeTuple) -> _RangeTuple: 

78 """Merge multiple sorted range tuples into one sorted, non-overlapping tuple.""" 

79 all_ranges: list[tuple[int, int]] = [] 

80 for t in tuples: 

81 all_ranges.extend(t) 

82 if not all_ranges: 

83 return () 

84 all_ranges.sort(key=lambda r: r[0]) 

85 merged = [all_ranges[0]] 

86 for lo, hi in all_ranges[1:]: 

87 _, prev_hi = merged[-1] 

88 if lo <= prev_hi: 

89 merged[-1] = (merged[-1][0], max(prev_hi, hi)) 

90 else: 

91 merged.append((lo, hi)) 

92 return tuple(merged) 

93 

94 

95class TerminalOverrides(NamedTuple): 

96 """Pre-merged override range tuples for a single terminal.""" 

97 

98 narrower: _RangeTuple 

99 vs16_narrower: _RangeTuple 

100 vs15_wider: _RangeTuple 

101 zeroer: _RangeTuple 

102 narrow_wider: _RangeTuple 

103 narrow_zeroer: _RangeTuple 

104 

105 

106_EMPTY_OVERRIDES = TerminalOverrides((), (), (), (), (), ()) 

107 

108 

109@lru_cache(maxsize=32) 

110def get_term_overrides(term_canonical: str) -> TerminalOverrides: 

111 """Return a TerminalOverrides, with all empty tuples when there are no overrides.""" 

112 # wide, sri, sfz: all narrow characters Unicode expects wide (no 'wider' data exists) 

113 narrower = _merge_ranges( 

114 WIDE_OVERRIDES.get(term_canonical, {}).get('narrower', ()), 

115 SRI_OVERRIDES.get(term_canonical, {}).get('narrower', ()), 

116 SFZ_OVERRIDES.get(term_canonical, {}).get('narrower', ()), 

117 ) 

118 vs16_narrower = VS16_OVERRIDES.get(term_canonical, {}).get('narrower', ()) 

119 vs15_wider = VS15_OVERRIDES.get(term_canonical, {}).get('wider', ()) 

120 zeroer = _merge_ranges( 

121 WIDE_OVERRIDES.get(term_canonical, {}).get('zeroer', ()), 

122 SRI_OVERRIDES.get(term_canonical, {}).get('zeroer', ()), 

123 SFZ_OVERRIDES.get(term_canonical, {}).get('zeroer', ()), 

124 ) 

125 narrow_wider = NARROW_OVERRIDES.get(term_canonical, {}).get('wider', ()) 

126 narrow_zeroer = NARROW_OVERRIDES.get(term_canonical, {}).get('narrow_zeroer', ()) 

127 # vs15_narrower intentionally excluded: no known terminal narrows VS15 

128 # vs16_wider intentionally excluded: any 'wider' entries in emoji_vs16_results 

129 # ucs-detect YAML are from the vs16n baseline test (base char without VS16), 

130 # not actual VS16 correction data. 

131 

132 if not (narrower or vs16_narrower or vs15_wider or zeroer 

133 or narrow_wider or narrow_zeroer): 

134 return _EMPTY_OVERRIDES 

135 return TerminalOverrides(narrower, vs16_narrower, vs15_wider, zeroer, 

136 narrow_wider, narrow_zeroer) 

137 

138 

139@lru_cache(maxsize=32) 

140def resolve_terminal(term_program: bool | str = False) -> str | None: 

141 """ 

142 Resolve a terminal identifier to its canonical name. 

143 

144 :param term_program: Terminal identifier. ``False`` (default) disables override lookup. 

145 ``True`` reads the ``TERM_PROGRAM`` environment variable, falling back to ``TERM``. 

146 A string value is used directly (canonical name, alias, XTVERSION/ENQ result, etc.). 

147 :returns: Canonical terminal name if recognized, ``None`` otherwise. 

148 

149 The auto-detection path (``term_program=True``) reads environment variables at call time 

150 and caches the result. The environment is assumed immutable for the process lifetime; 

151 callers that change ``TERM`` or ``TERM_PROGRAM`` mid-process must call 

152 :func:`resolve_terminal.cache_clear` afterward. 

153 """ 

154 if term_program is False: 

155 return None 

156 if term_program is True: 

157 term_program = os.environ.get('TERM_PROGRAM', '') or os.environ.get('TERM', '') 

158 if not term_program: 

159 return None 

160 key = term_program.strip().lower() 

161 canonical = ALIASES.get(key, key) 

162 if canonical not in KNOWN_TERMINALS: 

163 return None 

164 return canonical