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.
 
 
 
 

473 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 .compat import maxsize, py3k
  29. __all__ = ["SmartList"]
  30. def inheritdoc(method):
  31. """Set __doc__ of *method* to __doc__ of *method* in its parent class.
  32. Since this is used on :class:`.SmartList`, the "parent class" used is
  33. ``list``. This function can be used as a decorator.
  34. """
  35. method.__doc__ = getattr(list, method.__name__).__doc__
  36. return method
  37. class _SliceNormalizerMixIn(object):
  38. """MixIn that provides a private method to normalize slices."""
  39. def _normalize_slice(self, key):
  40. """Return a slice equivalent to the input *key*, standardized."""
  41. if key.start is not None:
  42. start = (len(self) + key.start) if key.start < 0 else key.start
  43. else:
  44. start = 0
  45. if key.stop is not None:
  46. stop = (len(self) + key.stop) if key.stop < 0 else key.stop
  47. else:
  48. stop = maxsize
  49. return slice(start, stop, key.step or 1)
  50. class SmartList(_SliceNormalizerMixIn, list):
  51. """Implements the ``list`` interface with special handling of sublists.
  52. When a sublist is created (by ``list[i:j]``), any changes made to this
  53. list (such as the addition, removal, or replacement of elements) will be
  54. reflected in the sublist, or vice-versa, to the greatest degree possible.
  55. This is implemented by having sublists - instances of the
  56. :class:`._ListProxy` type - dynamically determine their elements by storing
  57. their slice info and retrieving that slice from the parent. Methods that
  58. change the size of the list also change the slice info. For example::
  59. >>> parent = SmartList([0, 1, 2, 3])
  60. >>> parent
  61. [0, 1, 2, 3]
  62. >>> child = parent[2:]
  63. >>> child
  64. [2, 3]
  65. >>> child.append(4)
  66. >>> child
  67. [2, 3, 4]
  68. >>> parent
  69. [0, 1, 2, 3, 4]
  70. The parent needs to keep a list of its children in order to update them,
  71. which prevents them from being garbage-collected. If you are keeping the
  72. parent around for a while but creating many children, it is advisable to
  73. call :meth:`._ListProxy.detach` when you're finished with them. Certain
  74. parent methods, like :meth:`reverse` and :meth:`sort`, will do this
  75. automatically.
  76. """
  77. def __init__(self, iterable=None):
  78. if iterable:
  79. super(SmartList, self).__init__(iterable)
  80. else:
  81. super(SmartList, self).__init__()
  82. self._children = {}
  83. def __getitem__(self, key):
  84. if not isinstance(key, slice):
  85. return super(SmartList, self).__getitem__(key)
  86. key = self._normalize_slice(key)
  87. sliceinfo = [key.start, key.stop, key.step]
  88. child = _ListProxy(self, sliceinfo)
  89. self._children[id(child)] = (child, sliceinfo)
  90. return child
  91. def __setitem__(self, key, item):
  92. if not isinstance(key, slice):
  93. return super(SmartList, self).__setitem__(key, item)
  94. item = list(item)
  95. super(SmartList, self).__setitem__(key, item)
  96. key = self._normalize_slice(key)
  97. diff = len(item) + (key.start - key.stop) // key.step
  98. values = self._children.values if py3k else self._children.itervalues
  99. if diff:
  100. for child, (start, stop, step) in values():
  101. if start > key.stop:
  102. self._children[id(child)][1][0] += diff
  103. if stop >= key.stop and stop != maxsize:
  104. self._children[id(child)][1][1] += diff
  105. def __delitem__(self, key):
  106. super(SmartList, self).__delitem__(key)
  107. if isinstance(key, slice):
  108. key = self._normalize_slice(key)
  109. else:
  110. key = slice(key, key + 1, 1)
  111. diff = (key.stop - key.start) // key.step
  112. values = self._children.values if py3k else self._children.itervalues
  113. for child, (start, stop, step) in values():
  114. if start > key.start:
  115. self._children[id(child)][1][0] -= diff
  116. if stop >= key.stop and stop != maxsize:
  117. self._children[id(child)][1][1] -= diff
  118. if not py3k:
  119. def __getslice__(self, start, stop):
  120. return self.__getitem__(slice(start, stop))
  121. def __setslice__(self, start, stop, iterable):
  122. self.__setitem__(slice(start, stop), iterable)
  123. def __delslice__(self, start, stop):
  124. self.__delitem__(slice(start, stop))
  125. def __add__(self, other):
  126. return SmartList(list(self) + other)
  127. def __radd__(self, other):
  128. return SmartList(other + list(self))
  129. def __iadd__(self, other):
  130. self.extend(other)
  131. return self
  132. def _detach_children(self):
  133. children = [val[0] for val in self._children.values()]
  134. for child in children:
  135. child.detach()
  136. @inheritdoc
  137. def append(self, item):
  138. head = len(self)
  139. self[head:head] = [item]
  140. @inheritdoc
  141. def extend(self, item):
  142. head = len(self)
  143. self[head:head] = item
  144. @inheritdoc
  145. def insert(self, index, item):
  146. self[index:index] = [item]
  147. @inheritdoc
  148. def pop(self, index=None):
  149. if index is None:
  150. index = len(self) - 1
  151. item = self[index]
  152. del self[index]
  153. return item
  154. @inheritdoc
  155. def remove(self, item):
  156. del self[self.index(item)]
  157. @inheritdoc
  158. def reverse(self):
  159. self._detach_children()
  160. super(SmartList, self).reverse()
  161. if py3k:
  162. @inheritdoc
  163. def sort(self, key=None, reverse=None):
  164. self._detach_children()
  165. kwargs = {}
  166. if key is not None:
  167. kwargs["key"] = key
  168. if reverse is not None:
  169. kwargs["reverse"] = reverse
  170. super(SmartList, self).sort(**kwargs)
  171. else:
  172. @inheritdoc
  173. def sort(self, cmp=None, key=None, reverse=None):
  174. self._detach_children()
  175. kwargs = {}
  176. if cmp is not None:
  177. kwargs["cmp"] = cmp
  178. if key is not None:
  179. kwargs["key"] = key
  180. if reverse is not None:
  181. kwargs["reverse"] = reverse
  182. super(SmartList, self).sort(**kwargs)
  183. class _ListProxy(_SliceNormalizerMixIn, list):
  184. """Implement the ``list`` interface by getting elements from a parent.
  185. This is created by a :class:`.SmartList` object when slicing. It does not
  186. actually store the list at any time; instead, whenever the list is needed,
  187. it builds it dynamically using the :meth:`_render` method.
  188. """
  189. def __init__(self, parent, sliceinfo):
  190. super(_ListProxy, self).__init__()
  191. self._parent = parent
  192. self._sliceinfo = sliceinfo
  193. self._detached = False
  194. def __repr__(self):
  195. return repr(self._render())
  196. def __lt__(self, other):
  197. if isinstance(other, _ListProxy):
  198. return self._render() < list(other)
  199. return self._render() < other
  200. def __le__(self, other):
  201. if isinstance(other, _ListProxy):
  202. return self._render() <= list(other)
  203. return self._render() <= other
  204. def __eq__(self, other):
  205. if isinstance(other, _ListProxy):
  206. return self._render() == list(other)
  207. return self._render() == other
  208. def __ne__(self, other):
  209. if isinstance(other, _ListProxy):
  210. return self._render() != list(other)
  211. return self._render() != other
  212. def __gt__(self, other):
  213. if isinstance(other, _ListProxy):
  214. return self._render() > list(other)
  215. return self._render() > other
  216. def __ge__(self, other):
  217. if isinstance(other, _ListProxy):
  218. return self._render() >= list(other)
  219. return self._render() >= other
  220. if py3k:
  221. def __bool__(self):
  222. return bool(self._render())
  223. else:
  224. def __nonzero__(self):
  225. return bool(self._render())
  226. def __len__(self):
  227. return (self._stop - self._start) // self._step
  228. def __getitem__(self, key):
  229. if isinstance(key, slice):
  230. key = self._normalize_slice(key)
  231. if key.stop == maxsize:
  232. keystop = self._stop
  233. else:
  234. keystop = key.stop + self._start
  235. adjusted = slice(key.start + self._start, 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)
  242. if key.stop == maxsize:
  243. keystop = self._stop
  244. else:
  245. keystop = key.stop + self._start
  246. adjusted = slice(key.start + self._start, keystop, key.step)
  247. self._parent[adjusted] = item
  248. else:
  249. length = len(self)
  250. if key < 0:
  251. key = length + key
  252. if key < 0 or key >= length:
  253. raise IndexError("list assignment index out of range")
  254. self._parent[self._start + key] = item
  255. def __delitem__(self, key):
  256. if isinstance(key, slice):
  257. key = self._normalize_slice(key)
  258. if key.stop == maxsize:
  259. keystop = self._stop
  260. else:
  261. keystop = key.stop + self._start
  262. adjusted = slice(key.start + self._start, keystop, key.step)
  263. del self._parent[adjusted]
  264. else:
  265. length = len(self)
  266. if key < 0:
  267. key = length + key
  268. if key < 0 or key >= length:
  269. raise IndexError("list assignment index out of range")
  270. del self._parent[self._start + key]
  271. def __iter__(self):
  272. i = self._start
  273. while i < self._stop:
  274. yield self._parent[i]
  275. i += self._step
  276. def __reversed__(self):
  277. i = self._stop - 1
  278. while i >= self._start:
  279. yield self._parent[i]
  280. i -= self._step
  281. def __contains__(self, item):
  282. return item in self._render()
  283. if not py3k:
  284. def __getslice__(self, start, stop):
  285. return self.__getitem__(slice(start, stop))
  286. def __setslice__(self, start, stop, iterable):
  287. self.__setitem__(slice(start, stop), iterable)
  288. def __delslice__(self, start, stop):
  289. self.__delitem__(slice(start, stop))
  290. def __add__(self, other):
  291. return SmartList(list(self) + other)
  292. def __radd__(self, other):
  293. return SmartList(other + list(self))
  294. def __iadd__(self, other):
  295. self.extend(other)
  296. return self
  297. def __mul__(self, other):
  298. return SmartList(list(self) * other)
  299. def __rmul__(self, other):
  300. return SmartList(other * list(self))
  301. def __imul__(self, other):
  302. self.extend(list(self) * (other - 1))
  303. return self
  304. @property
  305. def _start(self):
  306. """The starting index of this list, inclusive."""
  307. return self._sliceinfo[0]
  308. @property
  309. def _stop(self):
  310. """The ending index of this list, exclusive."""
  311. if self._sliceinfo[1] == maxsize:
  312. return len(self._parent)
  313. return self._sliceinfo[1]
  314. @property
  315. def _step(self):
  316. """The number to increase the index by between items."""
  317. return self._sliceinfo[2]
  318. def _render(self):
  319. """Return the actual list from the stored start/stop/step."""
  320. return list(self._parent)[self._start:self._stop:self._step]
  321. @inheritdoc
  322. def append(self, item):
  323. self._parent.insert(self._stop, item)
  324. @inheritdoc
  325. def count(self, item):
  326. return self._render().count(item)
  327. @inheritdoc
  328. def index(self, item, start=None, stop=None):
  329. if start is not None:
  330. if stop is not None:
  331. return self._render().index(item, start, stop)
  332. return self._render().index(item, start)
  333. return self._render().index(item)
  334. @inheritdoc
  335. def extend(self, item):
  336. self._parent[self._stop:self._stop] = item
  337. @inheritdoc
  338. def insert(self, index, item):
  339. if index < 0:
  340. index = len(self) + index
  341. self._parent.insert(self._start + index, item)
  342. @inheritdoc
  343. def pop(self, index=None):
  344. length = len(self)
  345. if index is None:
  346. index = length - 1
  347. elif index < 0:
  348. index = length + index
  349. if index < 0 or index >= length:
  350. raise IndexError("pop index out of range")
  351. return self._parent.pop(self._start + index)
  352. @inheritdoc
  353. def remove(self, item):
  354. index = self.index(item)
  355. del self._parent[self._start + index]
  356. @inheritdoc
  357. def reverse(self):
  358. item = self._render()
  359. item.reverse()
  360. self._parent[self._start:self._stop:self._step] = item
  361. if py3k:
  362. @inheritdoc
  363. def sort(self, key=None, reverse=None):
  364. item = self._render()
  365. kwargs = {}
  366. if key is not None:
  367. kwargs["key"] = key
  368. if reverse is not None:
  369. kwargs["reverse"] = reverse
  370. item.sort(**kwargs)
  371. self._parent[self._start:self._stop:self._step] = item
  372. else:
  373. @inheritdoc
  374. def sort(self, cmp=None, key=None, reverse=None):
  375. item = self._render()
  376. kwargs = {}
  377. if cmp is not None:
  378. kwargs["cmp"] = cmp
  379. if key is not None:
  380. kwargs["key"] = key
  381. if reverse is not None:
  382. kwargs["reverse"] = reverse
  383. item.sort(**kwargs)
  384. self._parent[self._start:self._stop:self._step] = item
  385. def detach(self):
  386. """Detach the child so it operates like a normal list.
  387. This allows children to be properly garbage-collected if their parent
  388. is being kept around for a long time. This method has no effect if the
  389. child is already detached.
  390. """
  391. if not self._detached:
  392. self._parent._children.pop(id(self))
  393. self._parent = list(self._parent)
  394. self._detached = True
  395. del inheritdoc