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

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

80 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 operator import index as _index 

7from functools import lru_cache 

8 

9from typing import Tuple, NamedTuple 

10 

11# local 

12from .table_mc import CATEGORY_MC 

13from .table_wide import WIDE_EASTASIAN 

14from .table_zero import ZERO_WIDTH 

15from .table_grapheme import (ISC_VIRAMA, 

16 EXTENDED_PICTOGRAPHIC, 

17 ISC_INVISIBLE_STACKER, 

18 GRAPHEME_REGIONAL_INDICATOR) 

19from .table_ambiguous import AMBIGUOUS_EASTASIAN 

20from .table_overrides import (SFZ_OVERRIDES, 

21 SRI_OVERRIDES, 

22 VS15_OVERRIDES, 

23 VS16_OVERRIDES, 

24 WIDE_OVERRIDES, 

25 NARROW_OVERRIDES) 

26from .unicode_versions import list_versions 

27from .table_term_programs import ALIASES, KNOWN_TERMINALS 

28 

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

30 

31 

32__all__ = ( 

33 "_REGIONAL_INDICATOR_SET", 

34 "_ISC_VIRAMA_SET", 

35 "_LATEST_VERSION", 

36 "_CATEGORY_MC_TABLE", 

37 "_EMOJI_ZWJ_SET", 

38 "_FITZPATRICK_RANGE", 

39 "_ZERO_WIDTH_TABLE", 

40 "_WIDE_EASTASIAN_TABLE", 

41 "_AMBIGUOUS_TABLE", 

42 "resolve_terminal", 

43 "get_term_overrides", 

44 "list_term_programs", 

45) 

46 

47_REGIONAL_INDICATOR_SET = frozenset( 

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

49) 

50_ISC_VIRAMA_SET = frozenset( 

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

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

53) 

54# pylint: disable=invalid-name 

55_LATEST_VERSION = list_versions()[-1] 

56_CATEGORY_MC_TABLE = CATEGORY_MC[_LATEST_VERSION] 

57_EMOJI_ZWJ_SET = frozenset( 

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

59) | _REGIONAL_INDICATOR_SET 

60_FITZPATRICK_RANGE = (0x1F3FB, 0x1F3FF) 

61 

62_ZERO_WIDTH_TABLE = ZERO_WIDTH[_LATEST_VERSION] 

63_WIDE_EASTASIAN_TABLE = WIDE_EASTASIAN[_LATEST_VERSION] 

64_AMBIGUOUS_TABLE = AMBIGUOUS_EASTASIAN[_LATEST_VERSION] 

65 

66 

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

68 """ 

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

70 

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

72 

73 .. versionadded:: 0.8.0 

74 """ 

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

76 

77 

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

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

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

81 for t in tuples: 

82 all_ranges.extend(t) 

83 if not all_ranges: 

84 return () 

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

86 merged = [all_ranges[0]] 

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

88 _, prev_hi = merged[-1] 

89 if lo <= prev_hi: 

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

91 else: 

92 merged.append((lo, hi)) 

93 return tuple(merged) 

94 

95 

96class TerminalOverrides(NamedTuple): 

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

98 

99 narrower: _RangeTuple 

100 vs16_narrower: _RangeTuple 

101 vs15_wider: _RangeTuple 

102 zeroer: _RangeTuple 

103 narrow_wider: _RangeTuple 

104 narrow_zeroer: _RangeTuple 

105 

106 

107_EMPTY_OVERRIDES = TerminalOverrides((), (), (), (), (), ()) 

108 

109 

110@lru_cache(maxsize=32) 

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

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

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

114 narrower = _merge_ranges( 

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

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

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

118 ) 

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

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

121 zeroer = _merge_ranges( 

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

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

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

125 ) 

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

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

128 # vs15_narrower intentionally excluded: no known terminal narrows VS15 

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

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

131 # not actual VS16 correction data. 

132 

133 if not (narrower or vs16_narrower or vs15_wider or zeroer 

134 or narrow_wider or narrow_zeroer): 

135 return _EMPTY_OVERRIDES 

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

137 narrow_wider, narrow_zeroer) 

138 

139 

140@lru_cache(maxsize=32) 

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

142 """ 

143 Resolve a terminal identifier to its canonical name. 

144 

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

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

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

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

149 

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

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

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

153 :func:`resolve_terminal.cache_clear` afterward. 

154 """ 

155 if term_program is False: 

156 return None 

157 if term_program is True: 

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

159 if not term_program: 

160 return None 

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

162 canonical = ALIASES.get(key, key) 

163 if canonical not in KNOWN_TERMINALS: 

164 return None 

165 return canonical 

166 

167 

168def _clamp_ambiguous_width(ambiguous_width: int) -> int: 

169 """Clamp *ambiguous_width* to 1 or 2.""" 

170 # this operator.index() rejects bool/float and overall this is faster than 

171 # combining min() and max(). 

172 value = _index(ambiguous_width) 

173 if value < 1: 

174 return 1 

175 if value > 2: 

176 return 2 

177 return value