1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388
|
import weakref
from weakref import ReferenceType
try:
from typing import (
Iterable, Optional, Generic, Dict, List, Iterator, TypeVar, TYPE_CHECKING, Any,
Callable,
)
# Used a generic type for any case where we need a generic type without any bounds
# (e.g. for the LinkedList interface and some super-classes/mixins).
T = TypeVar('T')
except ImportError: # pragma: no cover
TYPE_CHECKING = False
def resolve_ref(ref):
# type: (Optional[ReferenceType[T]]) -> Optional[T]
return ref() if ref is not None else None
class _CaseInsensitiveString(str):
"""Case insensitive string.
"""
__slots__ = ['str_lower']
if TYPE_CHECKING: # pragma: no cover
# neither pylint nor mypy cope with str_lower being defined in __new__
def __init__(self, s):
# type: (str) -> None
super(_CaseInsensitiveString, self).__init__(s) # type: ignore
self.str_lower = ''
def __new__(cls, str_): # type: ignore
s = str.__new__(cls, str_)
# We cache the lower case version of the string to speed up some operations
s.str_lower = str_.lower()
return s
def __hash__(self):
# type: () -> int
return hash(self.str_lower)
def __eq__(self, other):
# type: (Any) -> Any
try:
return self.str_lower == other.lower()
except AttributeError:
return False
def __ne__(self, other):
# type: (Any) -> Any
return not self == other
def lower(self):
# type: () -> str
return self.str_lower
_strI = _CaseInsensitiveString
def default_field_sort_key(x):
# type: (str) -> Any
return x.lower()
class LinkedListNode(Generic[T]):
__slots__ = ('_previous_node', 'value', 'next_node', '__weakref__')
def __init__(self, value):
# type: (T) -> None
self._previous_node = None # type: Optional[ReferenceType[LinkedListNode[T]]]
self.next_node = None # type: Optional[LinkedListNode[T]]
self.value = value
@property
def previous_node(self):
# type: () -> Optional[LinkedListNode[T]]
return resolve_ref(self._previous_node)
@previous_node.setter
def previous_node(self, node):
# type: (LinkedListNode[T]) -> None
self._previous_node = weakref.ref(node) if node is not None else None
def remove(self):
# type: () -> T
LinkedListNode.link_nodes(self.previous_node, self.next_node)
self.previous_node = None
self.next_node = None
return self.value
def iter_next(self, *,
skip_current=False # type: Optional[bool]
):
# type: (...) -> Iterator[LinkedListNode[T]]
node = self.next_node if skip_current else self
while node:
yield node
node = node.next_node
def iter_previous(self, *,
skip_current=False # type: Optional[bool]
):
# type: (...) -> Iterator[LinkedListNode[T]]
node = self.previous_node if skip_current else self
while node:
yield node
node = node.previous_node
@staticmethod
def link_nodes(previous_node, next_node):
# type: (Optional[LinkedListNode[T]], Optional['LinkedListNode[T]']) -> None
if next_node:
next_node.previous_node = previous_node
if previous_node:
previous_node.next_node = next_node
@staticmethod
def _insert_link(first_node, # type: Optional[LinkedListNode[T]]
new_node, # type: LinkedListNode[T]
last_node, # type: Optional[LinkedListNode[T]]
):
# type: (...) -> None
LinkedListNode.link_nodes(first_node, new_node)
LinkedListNode.link_nodes(new_node, last_node)
def insert_before(self, new_node):
# type: (LinkedListNode[T]) -> None
assert self is not new_node and new_node is not self.previous_node
LinkedListNode._insert_link(self.previous_node, new_node, self)
def insert_after(self, new_node):
# type: (LinkedListNode[T]) -> None
assert self is not new_node and new_node is not self.next_node
LinkedListNode._insert_link(self, new_node, self.next_node)
class LinkedList(Generic[T]):
"""Specialized linked list implementation to support the deb822 parser needs
We deliberately trade "encapsulation" for features needed by this library
to facilitate their implementation. Notably, we allow nodes to leak and assume
well-behaved calls to remove_node - because that makes it easier to implement
components like Deb822InvalidParagraphElement.
"""
__slots__ = ('head_node', 'tail_node', '_size')
def __init__(self, values=None):
# type: (Optional[Iterable[T]]) -> None
self.head_node = None # type: Optional[LinkedListNode[T]]
self.tail_node = None # type: Optional[LinkedListNode[T]]
self._size = 0
if values is not None:
self.extend(values)
def __bool__(self):
# type: () -> bool
return self.head_node is not None
def __len__(self):
# type: () -> int
return self._size
@property
def tail(self):
# type: () -> Optional[T]
return self.tail_node.value if self.tail_node is not None else None
def pop(self):
# type: () -> None
if self.tail_node is None:
raise IndexError('pop from empty list')
self.remove_node(self.tail_node)
def iter_nodes(self):
# type: () -> Iterator[LinkedListNode[T]]
head_node = self.head_node
if head_node is None:
return
yield from head_node.iter_next()
def __iter__(self):
# type: () -> Iterator[T]
yield from (node.value for node in self.iter_nodes())
def __reversed__(self):
# type: () -> Iterator[T]
tail_node = self.tail_node
if tail_node is None:
return
yield from (n.value for n in tail_node.iter_previous())
def remove_node(self, node):
# type: (LinkedListNode[T]) -> None
if node is self.head_node:
self.head_node = node.next_node
if self.head_node is None:
self.tail_node = None
elif node is self.tail_node:
self.tail_node = node.previous_node
# That case should have happened in the "if node is self._head"
# part
assert self.tail_node is not None
assert self._size > 0
self._size -= 1
node.remove()
def insert_at_head(self, value):
# type: (T) -> LinkedListNode[T]
if self.head_node is None:
return self.append(value)
return self.insert_before(value, self.head_node)
def append(self, value):
# type: (T) -> LinkedListNode[T]
node = LinkedListNode(value)
if self.head_node is None:
self.head_node = node
self.tail_node = node
else:
# Primarily as a hint to mypy
assert self.tail_node is not None
# Optimize for lots of appends (will happen if you are reading a Packages file) by
# inlining relevant bits of tail_node.insert_after (removing unnecessary checks and
# linking).
assert self.tail_node is not node
node.previous_node = self.tail_node
self.tail_node.next_node = node
self.tail_node = node
self._size += 1
return node
def insert_before(self, value, existing_node):
# type: (T, LinkedListNode[T]) -> LinkedListNode[T]
return self.insert_node_before(LinkedListNode(value), existing_node)
def insert_after(self, value, existing_node):
# type: (T, LinkedListNode[T]) -> LinkedListNode[T]
return self.insert_node_after(LinkedListNode(value), existing_node)
def insert_node_before(self, new_node, existing_node):
# type: (LinkedListNode[T], LinkedListNode[T]) -> LinkedListNode[T]
if self.head_node is None:
raise ValueError("List is empty; node argument cannot be valid")
if new_node.next_node is not None or new_node.previous_node is not None:
raise ValueError("New node must not already be inserted!")
existing_node.insert_before(new_node)
if existing_node is self.head_node:
self.head_node = new_node
self._size += 1
return new_node
def insert_node_after(self, new_node, existing_node):
# type: (LinkedListNode[T], LinkedListNode[T]) -> LinkedListNode[T]
if self.tail_node is None:
raise ValueError("List is empty; node argument cannot be valid")
if new_node.next_node is not None or new_node.previous_node is not None:
raise ValueError("New node must not already be inserted!")
existing_node.insert_after(new_node)
if existing_node is self.tail_node:
self.tail_node = new_node
self._size += 1
return new_node
def extend(self, values):
# type: (Iterable[T]) -> None
for v in values:
self.append(v)
def clear(self):
# type: () -> None
self.head_node = None
self.tail_node = None
self._size = 0
class OrderedSet(object):
"""A set-like object that preserves order when iterating over it
We use this to keep track of keys in Deb822Dict, because it's much faster
to look up if a key is in a set than in a list.
"""
def __init__(self, iterable=None):
# type: (Optional[Iterable[str]]) -> None
# We implement the OrderedSet as a "Home-built" LinkedHashSet because
# python does not provide better facilities for it. On the flip side,
# we can add specialized functionality on top of it like "insert after"
# or "move to the end".
self.__table = {} # type: Dict[str, LinkedListNode[str]]
self.__order = LinkedList() # type: LinkedList[str]
if iterable is None:
iterable = []
for item in iterable:
self.add(item)
def add(self, item):
# type: (str) -> None
if item not in self:
# We rely on the dict to raise an exception if the item is unhashable
# Unfortunately, we need to add it to the linked list first (to obtain
# the node) which makes this a bit more cumbersome than one might have
# hoped.
node = self.__order.append(item)
try:
self.__table[item] = node
except Exception:
self.__order.remove_node(node)
raise
def remove(self, item):
# type: (str) -> None
# The dict will raise KeyError, so we don't need to handle that
# ourselves
node = self.__table[item]
del self.__table[item]
self.__order.remove_node(node)
def __iter__(self):
# type: () -> Iterator[str]
# Return an iterator of items in the order they were added
return iter(self.__order)
def __reversed__(self):
# type: () -> Iterator[str]
# Return an iterator of items in the opposite order they were added
return iter(reversed(self.__order))
def __len__(self):
# type: () -> int
return len(self.__order)
def __contains__(self, item):
# type: (str) -> bool
# This is what makes OrderedSet faster than using a list to keep track
# of keys. Lookup in a dict is O(1) instead of O(n) for a list.
return item in self.__table
# ### list-like methods
append = add
def extend(self, iterable):
# type: (Iterable[str]) -> None
for item in iterable:
self.add(item)
# ### methods specialized for Deb822 usage
def order_last(self, item):
# type: (str) -> None
"""Re-order the given item so it is "last" in the set"""
self._reorder(item, self.__order.append)
def order_first(self, item):
# type: (str) -> None
"""Re-order the given item so it is "first" in the set"""
self._reorder(item, self.__order.insert_at_head)
def order_before(self, item, reference_item):
# type: (str, str) -> None
"""Re-order the given item so appears directly after the reference item in the sequence"""
if item == reference_item:
raise ValueError("Cannot re-order an item relative to itself")
reference_node = self.__table[reference_item]
self._reorder(item, lambda x: self.__order.insert_before(x, reference_node))
def order_after(self, item, reference_item):
# type: (str, str) -> None
"""Re-order the given item so appears directly before the reference item in the sequence"""
if item == reference_item:
raise ValueError("Cannot re-order an item relative to itself")
reference_node = self.__table[reference_item]
self._reorder(item, lambda x: self.__order.insert_after(x, reference_node))
def _reorder(self,
item, # type: str
reinserter, # type: Callable[[str], LinkedListNode[str]]
):
# type: (...) -> None
node = self.__table[item]
self.__order.remove_node(node)
new_node = reinserter(node.value)
self.__table[item] = new_node
|