1# util.py 2import warnings 3import types 4import collections 5import itertools 6from functools import lru_cache 7from typing import List, Union, Iterable 8 9_bslash = chr(92) 10 11 12class __config_flags: 13 """Internal class for defining compatibility and debugging flags""" 14 15 _all_names: List[str] = [] 16 _fixed_names: List[str] = [] 17 _type_desc = "configuration" 18 19 @classmethod 20 def _set(cls, dname, value): 21 if dname in cls._fixed_names: 22 warnings.warn( 23 "{}.{} {} is {} and cannot be overridden".format( 24 cls.__name__, 25 dname, 26 cls._type_desc, 27 str(getattr(cls, dname)).upper(), 28 ) 29 ) 30 return 31 if dname in cls._all_names: 32 setattr(cls, dname, value) 33 else: 34 raise ValueError("no such {} {!r}".format(cls._type_desc, dname)) 35 36 enable = classmethod(lambda cls, name: cls._set(name, True)) 37 disable = classmethod(lambda cls, name: cls._set(name, False)) 38 39 40@lru_cache(maxsize=128) 41def col(loc: int, strg: str) -> int: 42 """ 43 Returns current column within a string, counting newlines as line separators. 44 The first column is number 1. 45 46 Note: the default parsing behavior is to expand tabs in the input string 47 before starting the parsing process. See 48 :class:`ParserElement.parseString` for more 49 information on parsing strings containing ``<TAB>`` s, and suggested 50 methods to maintain a consistent view of the parsed string, the parse 51 location, and line and column positions within the parsed string. 52 """ 53 s = strg 54 return 1 if 0 < loc < len(s) and s[loc - 1] == "\n" else loc - s.rfind("\n", 0, loc) 55 56 57@lru_cache(maxsize=128) 58def lineno(loc: int, strg: str) -> int: 59 """Returns current line number within a string, counting newlines as line separators. 60 The first line is number 1. 61 62 Note - the default parsing behavior is to expand tabs in the input string 63 before starting the parsing process. See :class:`ParserElement.parseString` 64 for more information on parsing strings containing ``<TAB>`` s, and 65 suggested methods to maintain a consistent view of the parsed string, the 66 parse location, and line and column positions within the parsed string. 67 """ 68 return strg.count("\n", 0, loc) + 1 69 70 71@lru_cache(maxsize=128) 72def line(loc: int, strg: str) -> str: 73 """ 74 Returns the line of text containing loc within a string, counting newlines as line separators. 75 """ 76 last_cr = strg.rfind("\n", 0, loc) 77 next_cr = strg.find("\n", loc) 78 return strg[last_cr + 1 : next_cr] if next_cr >= 0 else strg[last_cr + 1 :] 79 80 81class _UnboundedCache: 82 def __init__(self): 83 cache = {} 84 cache_get = cache.get 85 self.not_in_cache = not_in_cache = object() 86 87 def get(_, key): 88 return cache_get(key, not_in_cache) 89 90 def set_(_, key, value): 91 cache[key] = value 92 93 def clear(_): 94 cache.clear() 95 96 self.size = None 97 self.get = types.MethodType(get, self) 98 self.set = types.MethodType(set_, self) 99 self.clear = types.MethodType(clear, self) 100 101 102class _FifoCache: 103 def __init__(self, size): 104 self.not_in_cache = not_in_cache = object() 105 cache = collections.OrderedDict() 106 cache_get = cache.get 107 108 def get(_, key): 109 return cache_get(key, not_in_cache) 110 111 def set_(_, key, value): 112 cache[key] = value 113 while len(cache) > size: 114 cache.popitem(last=False) 115 116 def clear(_): 117 cache.clear() 118 119 self.size = size 120 self.get = types.MethodType(get, self) 121 self.set = types.MethodType(set_, self) 122 self.clear = types.MethodType(clear, self) 123 124 125class LRUMemo: 126 """ 127 A memoizing mapping that retains `capacity` deleted items 128 129 The memo tracks retained items by their access order; once `capacity` items 130 are retained, the least recently used item is discarded. 131 """ 132 133 def __init__(self, capacity): 134 self._capacity = capacity 135 self._active = {} 136 self._memory = collections.OrderedDict() 137 138 def __getitem__(self, key): 139 try: 140 return self._active[key] 141 except KeyError: 142 self._memory.move_to_end(key) 143 return self._memory[key] 144 145 def __setitem__(self, key, value): 146 self._memory.pop(key, None) 147 self._active[key] = value 148 149 def __delitem__(self, key): 150 try: 151 value = self._active.pop(key) 152 except KeyError: 153 pass 154 else: 155 while len(self._memory) >= self._capacity: 156 self._memory.popitem(last=False) 157 self._memory[key] = value 158 159 def clear(self): 160 self._active.clear() 161 self._memory.clear() 162 163 164class UnboundedMemo(dict): 165 """ 166 A memoizing mapping that retains all deleted items 167 """ 168 169 def __delitem__(self, key): 170 pass 171 172 173def _escape_regex_range_chars(s: str) -> str: 174 # escape these chars: ^-[] 175 for c in r"\^-[]": 176 s = s.replace(c, _bslash + c) 177 s = s.replace("\n", r"\n") 178 s = s.replace("\t", r"\t") 179 return str(s) 180 181 182def _collapse_string_to_ranges( 183 s: Union[str, Iterable[str]], re_escape: bool = True 184) -> str: 185 def is_consecutive(c): 186 c_int = ord(c) 187 is_consecutive.prev, prev = c_int, is_consecutive.prev 188 if c_int - prev > 1: 189 is_consecutive.value = next(is_consecutive.counter) 190 return is_consecutive.value 191 192 is_consecutive.prev = 0 193 is_consecutive.counter = itertools.count() 194 is_consecutive.value = -1 195 196 def escape_re_range_char(c): 197 return "\\" + c if c in r"\^-][" else c 198 199 def no_escape_re_range_char(c): 200 return c 201 202 if not re_escape: 203 escape_re_range_char = no_escape_re_range_char 204 205 ret = [] 206 s = "".join(sorted(set(s))) 207 if len(s) > 3: 208 for _, chars in itertools.groupby(s, key=is_consecutive): 209 first = last = next(chars) 210 last = collections.deque( 211 itertools.chain(iter([last]), chars), maxlen=1 212 ).pop() 213 if first == last: 214 ret.append(escape_re_range_char(first)) 215 else: 216 ret.append( 217 "{}-{}".format( 218 escape_re_range_char(first), escape_re_range_char(last) 219 ) 220 ) 221 else: 222 ret = [escape_re_range_char(c) for c in s] 223 224 return "".join(ret) 225 226 227def _flatten(ll: list) -> list: 228 ret = [] 229 for i in ll: 230 if isinstance(i, list): 231 ret.extend(_flatten(i)) 232 else: 233 ret.append(i) 234 return ret 235