A Python parser for MediaWiki wikicode https://mwparserfromhell.readthedocs.io/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

457 lines
15 KiB

  1. # -*- coding: utf-8 -*-
  2. #
  3. # Copyright (C) 2012-2015 Ben Kurtovic <ben.kurtovic@gmail.com>
  4. #
  5. # Permission is hereby granted, free of charge, to any person obtaining a copy
  6. # of this software and associated documentation files (the "Software"), to deal
  7. # in the Software without restriction, including without limitation the rights
  8. # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9. # copies of the Software, and to permit persons to whom the Software is
  10. # furnished to do so, subject to the following conditions:
  11. #
  12. # The above copyright notice and this permission notice shall be included in
  13. # all copies or substantial portions of the Software.
  14. #
  15. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  16. # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  17. # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  18. # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  19. # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  20. # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  21. # SOFTWARE.
  22. """
  23. This module contains the :class:`.SmartList` type, as well as its
  24. :class:`._ListProxy` child, which together implement a list whose sublists
  25. reflect changes made to the main list, and vice-versa.
  26. """
  27. from __future__ import unicode_literals
  28. from sys import maxsize
  29. from weakref import ref
  30. from .compat import py3k
  31. __all__ = ["SmartList"]
  32. def inheritdoc(method):
  33. """Set __doc__ of *method* to __doc__ of *method* in its parent class.
  34. Since this is used on :class:`.SmartList`, the "parent class" used is
  35. ``list``. This function can be used as a decorator.
  36. """
  37. method.__doc__ = getattr(list, method.__name__).__doc__
  38. return method
  39. class _SliceNormalizerMixIn(object):
  40. """MixIn that provides a private method to normalize slices."""
  41. def _normalize_slice(self, key, clamp=False):
  42. """Return a slice equivalent to the input *key*, standardized."""
  43. if key.start is None:
  44. start = 0
  45. else:
  46. start = (len(self) + key.start) if key.start < 0 else key.start
  47. if key.stop is None or key.stop == maxsize:
  48. stop = len(self) if clamp else None
  49. else:
  50. stop = (len(self) + key.stop) if key.stop < 0 else key.stop
  51. return slice(start, stop, key.step or 1)
  52. class SmartList(_SliceNormalizerMixIn, list):
  53. """Implements the ``list`` interface with special handling of sublists.
  54. When a sublist is created (by ``list[i:j]``), any changes made to this
  55. list (such as the addition, removal, or replacement of elements) will be
  56. reflected in the sublist, or vice-versa, to the greatest degree possible.
  57. This is implemented by having sublists - instances of the
  58. :class:`._ListProxy` type - dynamically determine their elements by storing
  59. their slice info and retrieving that slice from the parent. Methods that
  60. change the size of the list also change the slice info. For example::
  61. >>> parent = SmartList([0, 1, 2, 3])
  62. >>> parent
  63. [0, 1, 2, 3]
  64. >>> child = parent[2:]
  65. >>> child
  66. [2, 3]
  67. >>> child.append(4)
  68. >>> child
  69. [2, 3, 4]
  70. >>> parent
  71. [0, 1, 2, 3, 4]
  72. """
  73. def __init__(self, iterable=None):
  74. if iterable:
  75. super(SmartList, self).__init__(iterable)
  76. else:
  77. super(SmartList, self).__init__()
  78. self._children = {}
  79. def __getitem__(self, key):
  80. if not isinstance(key, slice):
  81. return super(SmartList, self).__getitem__(key)
  82. key = self._normalize_slice(key, clamp=False)
  83. sliceinfo = [key.start, key.stop, key.step]
  84. child = _ListProxy(self, sliceinfo)
  85. child_ref = ref(child, self._delete_child)
  86. self._children[id(child_ref)] = (child_ref, sliceinfo)
  87. return child
  88. def __setitem__(self, key, item):
  89. if not isinstance(key, slice):
  90. return super(SmartList, self).__setitem__(key, item)
  91. item = list(item)
  92. super(SmartList, self).__setitem__(key, item)
  93. key = self._normalize_slice(key, clamp=True)
  94. diff = len(item) + (key.start - key.stop) // key.step
  95. if not diff:
  96. return
  97. values = self._children.values if py3k else self._children.itervalues
  98. for child, (start, stop, step) in values():
  99. if start > key.stop:
  100. self._children[id(child)][1][0] += diff
  101. if stop is not None and stop >= key.stop:
  102. self._children[id(child)][1][1] += diff
  103. def __delitem__(self, key):
  104. super(SmartList, self).__delitem__(key)
  105. if isinstance(key, slice):
  106. key = self._normalize_slice(key, clamp=True)
  107. else:
  108. key = slice(key, key + 1, 1)
  109. diff = (key.stop - key.start) // key.step
  110. values = self._children.values if py3k else self._children.itervalues
  111. for child, (start, stop, step) in values():
  112. if start > key.start:
  113. self._children[id(child)][1][0] -= diff
  114. if stop is not None and stop >= key.stop:
  115. self._children[id(child)][1][1] -= diff
  116. if not py3k:
  117. def __getslice__(self, start, stop):
  118. return self.__getitem__(slice(start, stop))
  119. def __setslice__(self, start, stop, iterable):
  120. self.__setitem__(slice(start, stop), iterable)
  121. def __delslice__(self, start, stop):
  122. self.__delitem__(slice(start, stop))
  123. def __add__(self, other):
  124. return SmartList(list(self) + other)
  125. def __radd__(self, other):
  126. return SmartList(other + list(self))
  127. def __iadd__(self, other):
  128. self.extend(other)
  129. return self
  130. def _delete_child(self, child_ref):
  131. """Remove a child reference that is about to be garbage-collected."""
  132. del self._children[id(child_ref)]
  133. def _detach_children(self):
  134. """Remove all children and give them independent parent copies."""
  135. children = [val[0] for val in self._children.values()]
  136. for child in children:
  137. child()._parent = list(self)
  138. self._children.clear()
  139. @inheritdoc
  140. def append(self, item):
  141. head = len(self)
  142. self[head:head] = [item]
  143. @inheritdoc
  144. def extend(self, item):
  145. head = len(self)
  146. self[head:head] = item
  147. @inheritdoc
  148. def insert(self, index, item):
  149. self[index:index] = [item]
  150. @inheritdoc
  151. def pop(self, index=None):
  152. if index is None:
  153. index = len(self) - 1
  154. item = self[index]
  155. del self[index]
  156. return item
  157. @inheritdoc
  158. def remove(self, item):
  159. del self[self.index(item)]
  160. @inheritdoc
  161. def reverse(self):
  162. self._detach_children()
  163. super(SmartList, self).reverse()
  164. if py3k:
  165. @inheritdoc
  166. def sort(self, key=None, reverse=None):
  167. self._detach_children()
  168. kwargs = {}
  169. if key is not None:
  170. kwargs["key"] = key
  171. if reverse is not None:
  172. kwargs["reverse"] = reverse
  173. super(SmartList, self).sort(**kwargs)
  174. else:
  175. @inheritdoc
  176. def sort(self, cmp=None, key=None, reverse=None):
  177. self._detach_children()
  178. kwargs = {}
  179. if cmp is not None:
  180. kwargs["cmp"] = cmp
  181. if key is not None:
  182. kwargs["key"] = key
  183. if reverse is not None:
  184. kwargs["reverse"] = reverse
  185. super(SmartList, self).sort(**kwargs)
  186. class _ListProxy(_SliceNormalizerMixIn, list):
  187. """Implement the ``list`` interface by getting elements from a parent.
  188. This is created by a :class:`.SmartList` object when slicing. It does not
  189. actually store the list at any time; instead, whenever the list is needed,
  190. it builds it dynamically using the :meth:`_render` method.
  191. """
  192. def __init__(self, parent, sliceinfo):
  193. super(_ListProxy, self).__init__()
  194. self._parent = parent
  195. self._sliceinfo = sliceinfo
  196. def __repr__(self):
  197. return repr(self._render())
  198. def __lt__(self, other):
  199. if isinstance(other, _ListProxy):
  200. return self._render() < list(other)
  201. return self._render() < other
  202. def __le__(self, other):
  203. if isinstance(other, _ListProxy):
  204. return self._render() <= list(other)
  205. return self._render() <= other
  206. def __eq__(self, other):
  207. if isinstance(other, _ListProxy):
  208. return self._render() == list(other)
  209. return self._render() == other
  210. def __ne__(self, other):
  211. if isinstance(other, _ListProxy):
  212. return self._render() != list(other)
  213. return self._render() != other
  214. def __gt__(self, other):
  215. if isinstance(other, _ListProxy):
  216. return self._render() > list(other)
  217. return self._render() > other
  218. def __ge__(self, other):
  219. if isinstance(other, _ListProxy):
  220. return self._render() >= list(other)
  221. return self._render() >= other
  222. if py3k:
  223. def __bool__(self):
  224. return bool(self._render())
  225. else:
  226. def __nonzero__(self):
  227. return bool(self._render())
  228. def __len__(self):
  229. return (self._stop - self._start) // self._step
  230. def __getitem__(self, key):
  231. if isinstance(key, slice):
  232. key = self._normalize_slice(key, clamp=True)
  233. keystart = min(self._start + key.start, self._stop)
  234. keystop = min(self._start + key.stop, self._stop)
  235. adjusted = slice(keystart, keystop, key.step)
  236. return self._parent[adjusted]
  237. else:
  238. return self._render()[key]
  239. def __setitem__(self, key, item):
  240. if isinstance(key, slice):
  241. key = self._normalize_slice(key, clamp=True)
  242. keystart = min(self._start + key.start, self._stop)
  243. keystop = min(self._start + key.stop, self._stop)
  244. adjusted = slice(keystart, keystop, key.step)
  245. self._parent[adjusted] = item
  246. else:
  247. length = len(self)
  248. if key < 0:
  249. key = length + key
  250. if key < 0 or key >= length:
  251. raise IndexError("list assignment index out of range")
  252. self._parent[self._start + key] = item
  253. def __delitem__(self, key):
  254. if isinstance(key, slice):
  255. key = self._normalize_slice(key, clamp=True)
  256. keystart = min(self._start + key.start, self._stop)
  257. keystop = min(self._start + key.stop, self._stop)
  258. adjusted = slice(keystart, keystop, key.step)
  259. del self._parent[adjusted]
  260. else:
  261. length = len(self)
  262. if key < 0:
  263. key = length + key
  264. if key < 0 or key >= length:
  265. raise IndexError("list assignment index out of range")
  266. del self._parent[self._start + key]
  267. def __iter__(self):
  268. i = self._start
  269. while i < self._stop:
  270. yield self._parent[i]
  271. i += self._step
  272. def __reversed__(self):
  273. i = self._stop - 1
  274. while i >= self._start:
  275. yield self._parent[i]
  276. i -= self._step
  277. def __contains__(self, item):
  278. return item in self._render()
  279. if not py3k:
  280. def __getslice__(self, start, stop):
  281. return self.__getitem__(slice(start, stop))
  282. def __setslice__(self, start, stop, iterable):
  283. self.__setitem__(slice(start, stop), iterable)
  284. def __delslice__(self, start, stop):
  285. self.__delitem__(slice(start, stop))
  286. def __add__(self, other):
  287. return SmartList(list(self) + other)
  288. def __radd__(self, other):
  289. return SmartList(other + list(self))
  290. def __iadd__(self, other):
  291. self.extend(other)
  292. return self
  293. def __mul__(self, other):
  294. return SmartList(list(self) * other)
  295. def __rmul__(self, other):
  296. return SmartList(other * list(self))
  297. def __imul__(self, other):
  298. self.extend(list(self) * (other - 1))
  299. return self
  300. @property
  301. def _start(self):
  302. """The starting index of this list, inclusive."""
  303. return self._sliceinfo[0]
  304. @property
  305. def _stop(self):
  306. """The ending index of this list, exclusive."""
  307. if self._sliceinfo[1] is None:
  308. return len(self._parent)
  309. return self._sliceinfo[1]
  310. @property
  311. def _step(self):
  312. """The number to increase the index by between items."""
  313. return self._sliceinfo[2]
  314. def _render(self):
  315. """Return the actual list from the stored start/stop/step."""
  316. return list(self._parent)[self._start:self._stop:self._step]
  317. @inheritdoc
  318. def append(self, item):
  319. self._parent.insert(self._stop, item)
  320. @inheritdoc
  321. def count(self, item):
  322. return self._render().count(item)
  323. @inheritdoc
  324. def index(self, item, start=None, stop=None):
  325. if start is not None:
  326. if stop is not None:
  327. return self._render().index(item, start, stop)
  328. return self._render().index(item, start)
  329. return self._render().index(item)
  330. @inheritdoc
  331. def extend(self, item):
  332. self._parent[self._stop:self._stop] = item
  333. @inheritdoc
  334. def insert(self, index, item):
  335. if index < 0:
  336. index = len(self) + index
  337. self._parent.insert(self._start + index, item)
  338. @inheritdoc
  339. def pop(self, index=None):
  340. length = len(self)
  341. if index is None:
  342. index = length - 1
  343. elif index < 0:
  344. index = length + index
  345. if index < 0 or index >= length:
  346. raise IndexError("pop index out of range")
  347. return self._parent.pop(self._start + index)
  348. @inheritdoc
  349. def remove(self, item):
  350. index = self.index(item)
  351. del self._parent[self._start + index]
  352. @inheritdoc
  353. def reverse(self):
  354. item = self._render()
  355. item.reverse()
  356. self._parent[self._start:self._stop:self._step] = item
  357. if py3k:
  358. @inheritdoc
  359. def sort(self, key=None, reverse=None):
  360. item = self._render()
  361. kwargs = {}
  362. if key is not None:
  363. kwargs["key"] = key
  364. if reverse is not None:
  365. kwargs["reverse"] = reverse
  366. item.sort(**kwargs)
  367. self._parent[self._start:self._stop:self._step] = item
  368. else:
  369. @inheritdoc
  370. def sort(self, cmp=None, key=None, reverse=None):
  371. item = self._render()
  372. kwargs = {}
  373. if cmp is not None:
  374. kwargs["cmp"] = cmp
  375. if key is not None:
  376. kwargs["key"] = key
  377. if reverse is not None:
  378. kwargs["reverse"] = reverse
  379. item.sort(**kwargs)
  380. self._parent[self._start:self._stop:self._step] = item
  381. del inheritdoc