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.
 
 
 
 

453 lines
14 KiB

  1. # -*- coding: utf-8 -*-
  2. #
  3. # Copyright (C) 2012-2013 Ben Kurtovic <ben.kurtovic@verizon.net>
  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 :py:class:`~.SmartList` type, as well as its
  24. :py: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 :py: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. :py:class:`~._ListProxy` type - dynamically determine their elements by
  57. storing their slice info and retrieving that slice from the parent. Methods
  58. that 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. """
  71. def __init__(self, iterable=None):
  72. if iterable:
  73. super(SmartList, self).__init__(iterable)
  74. else:
  75. super(SmartList, self).__init__()
  76. self._children = {}
  77. def __getitem__(self, key):
  78. if not isinstance(key, slice):
  79. return super(SmartList, self).__getitem__(key)
  80. key = self._normalize_slice(key)
  81. sliceinfo = [key.start, key.stop, key.step]
  82. child = _ListProxy(self, sliceinfo)
  83. self._children[id(child)] = (child, sliceinfo)
  84. return child
  85. def __setitem__(self, key, item):
  86. if not isinstance(key, slice):
  87. return super(SmartList, self).__setitem__(key, item)
  88. item = list(item)
  89. super(SmartList, self).__setitem__(key, item)
  90. key = self._normalize_slice(key)
  91. diff = len(item) + (key.start - key.stop) // key.step
  92. values = self._children.values if py3k else self._children.itervalues
  93. if diff:
  94. for child, (start, stop, step) in values():
  95. if start > key.stop:
  96. self._children[id(child)][1][0] += diff
  97. if stop >= key.stop and stop != maxsize:
  98. self._children[id(child)][1][1] += diff
  99. def __delitem__(self, key):
  100. super(SmartList, self).__delitem__(key)
  101. if isinstance(key, slice):
  102. key = self._normalize_slice(key)
  103. else:
  104. key = slice(key, key + 1, 1)
  105. diff = (key.stop - key.start) // key.step
  106. values = self._children.values if py3k else self._children.itervalues
  107. for child, (start, stop, step) in values():
  108. if start > key.start:
  109. self._children[id(child)][1][0] -= diff
  110. if stop >= key.stop and stop != maxsize:
  111. self._children[id(child)][1][1] -= diff
  112. if not py3k:
  113. def __getslice__(self, start, stop):
  114. return self.__getitem__(slice(start, stop))
  115. def __setslice__(self, start, stop, iterable):
  116. self.__setitem__(slice(start, stop), iterable)
  117. def __delslice__(self, start, stop):
  118. self.__delitem__(slice(start, stop))
  119. def __add__(self, other):
  120. return SmartList(list(self) + other)
  121. def __radd__(self, other):
  122. return SmartList(other + list(self))
  123. def __iadd__(self, other):
  124. self.extend(other)
  125. return self
  126. @inheritdoc
  127. def append(self, item):
  128. head = len(self)
  129. self[head:head] = [item]
  130. @inheritdoc
  131. def extend(self, item):
  132. head = len(self)
  133. self[head:head] = item
  134. @inheritdoc
  135. def insert(self, index, item):
  136. self[index:index] = [item]
  137. @inheritdoc
  138. def pop(self, index=None):
  139. if index is None:
  140. index = len(self) - 1
  141. item = self[index]
  142. del self[index]
  143. return item
  144. @inheritdoc
  145. def remove(self, item):
  146. del self[self.index(item)]
  147. @inheritdoc
  148. def reverse(self):
  149. copy = list(self)
  150. for child in self._children:
  151. child._parent = copy
  152. super(SmartList, self).reverse()
  153. if py3k:
  154. @inheritdoc
  155. def sort(self, key=None, reverse=None):
  156. copy = list(self)
  157. for child in self._children:
  158. child._parent = copy
  159. kwargs = {}
  160. if key is not None:
  161. kwargs["key"] = key
  162. if reverse is not None:
  163. kwargs["reverse"] = reverse
  164. super(SmartList, self).sort(**kwargs)
  165. else:
  166. @inheritdoc
  167. def sort(self, cmp=None, key=None, reverse=None):
  168. copy = list(self)
  169. for child in self._children:
  170. child._parent = copy
  171. kwargs = {}
  172. if cmp is not None:
  173. kwargs["cmp"] = cmp
  174. if key is not None:
  175. kwargs["key"] = key
  176. if reverse is not None:
  177. kwargs["reverse"] = reverse
  178. super(SmartList, self).sort(**kwargs)
  179. class _ListProxy(_SliceNormalizerMixIn, list):
  180. """Implement the ``list`` interface by getting elements from a parent.
  181. This is created by a :py:class:`~.SmartList` object when slicing. It does
  182. not actually store the list at any time; instead, whenever the list is
  183. needed, it builds it dynamically using the :py:meth:`_render` method.
  184. """
  185. def __init__(self, parent, sliceinfo):
  186. super(_ListProxy, self).__init__()
  187. self._parent = parent
  188. self._sliceinfo = sliceinfo
  189. def __repr__(self):
  190. return repr(self._render())
  191. def __lt__(self, other):
  192. if isinstance(other, _ListProxy):
  193. return self._render() < list(other)
  194. return self._render() < other
  195. def __le__(self, other):
  196. if isinstance(other, _ListProxy):
  197. return self._render() <= list(other)
  198. return self._render() <= other
  199. def __eq__(self, other):
  200. if isinstance(other, _ListProxy):
  201. return self._render() == list(other)
  202. return self._render() == other
  203. def __ne__(self, other):
  204. if isinstance(other, _ListProxy):
  205. return self._render() != list(other)
  206. return self._render() != other
  207. def __gt__(self, other):
  208. if isinstance(other, _ListProxy):
  209. return self._render() > list(other)
  210. return self._render() > other
  211. def __ge__(self, other):
  212. if isinstance(other, _ListProxy):
  213. return self._render() >= list(other)
  214. return self._render() >= other
  215. if py3k:
  216. def __bool__(self):
  217. return bool(self._render())
  218. else:
  219. def __nonzero__(self):
  220. return bool(self._render())
  221. def __len__(self):
  222. return (self._stop - self._start) // self._step
  223. def __getitem__(self, key):
  224. if isinstance(key, slice):
  225. key = self._normalize_slice(key)
  226. if key.stop == maxsize:
  227. keystop = self._stop
  228. else:
  229. keystop = key.stop + self._start
  230. adjusted = slice(key.start + self._start, keystop, key.step)
  231. return self._parent[adjusted]
  232. else:
  233. return self._render()[key]
  234. def __setitem__(self, key, item):
  235. if isinstance(key, slice):
  236. key = self._normalize_slice(key)
  237. if key.stop == maxsize:
  238. keystop = self._stop
  239. else:
  240. keystop = key.stop + self._start
  241. adjusted = slice(key.start + self._start, keystop, key.step)
  242. self._parent[adjusted] = item
  243. else:
  244. length = len(self)
  245. if key < 0:
  246. key = length + key
  247. if key < 0 or key >= length:
  248. raise IndexError("list assignment index out of range")
  249. self._parent[self._start + key] = item
  250. def __delitem__(self, key):
  251. if isinstance(key, slice):
  252. key = self._normalize_slice(key)
  253. if key.stop == maxsize:
  254. keystop = self._stop
  255. else:
  256. keystop = key.stop + self._start
  257. adjusted = slice(key.start + self._start, keystop, key.step)
  258. del self._parent[adjusted]
  259. else:
  260. length = len(self)
  261. if key < 0:
  262. key = length + key
  263. if key < 0 or key >= length:
  264. raise IndexError("list assignment index out of range")
  265. del self._parent[self._start + key]
  266. def __iter__(self):
  267. i = self._start
  268. while i < self._stop:
  269. yield self._parent[i]
  270. i += self._step
  271. def __reversed__(self):
  272. i = self._stop - 1
  273. while i >= self._start:
  274. yield self._parent[i]
  275. i -= self._step
  276. def __contains__(self, item):
  277. return item in self._render()
  278. if not py3k:
  279. def __getslice__(self, start, stop):
  280. return self.__getitem__(slice(start, stop))
  281. def __setslice__(self, start, stop, iterable):
  282. self.__setitem__(slice(start, stop), iterable)
  283. def __delslice__(self, start, stop):
  284. self.__delitem__(slice(start, stop))
  285. def __add__(self, other):
  286. return SmartList(list(self) + other)
  287. def __radd__(self, other):
  288. return SmartList(other + list(self))
  289. def __iadd__(self, other):
  290. self.extend(other)
  291. return self
  292. def __mul__(self, other):
  293. return SmartList(list(self) * other)
  294. def __rmul__(self, other):
  295. return SmartList(other * list(self))
  296. def __imul__(self, other):
  297. self.extend(list(self) * (other - 1))
  298. return self
  299. @property
  300. def _start(self):
  301. """The starting index of this list, inclusive."""
  302. return self._sliceinfo[0]
  303. @property
  304. def _stop(self):
  305. """The ending index of this list, exclusive."""
  306. if self._sliceinfo[1] == maxsize:
  307. return len(self._parent)
  308. return self._sliceinfo[1]
  309. @property
  310. def _step(self):
  311. """The number to increase the index by between items."""
  312. return self._sliceinfo[2]
  313. def _render(self):
  314. """Return the actual list from the stored start/stop/step."""
  315. return list(self._parent)[self._start:self._stop:self._step]
  316. @inheritdoc
  317. def append(self, item):
  318. self._parent.insert(self._stop, item)
  319. @inheritdoc
  320. def count(self, item):
  321. return self._render().count(item)
  322. @inheritdoc
  323. def index(self, item, start=None, stop=None):
  324. if start is not None:
  325. if stop is not None:
  326. return self._render().index(item, start, stop)
  327. return self._render().index(item, start)
  328. return self._render().index(item)
  329. @inheritdoc
  330. def extend(self, item):
  331. self._parent[self._stop:self._stop] = item
  332. @inheritdoc
  333. def insert(self, index, item):
  334. if index < 0:
  335. index = len(self) + index
  336. self._parent.insert(self._start + index, item)
  337. @inheritdoc
  338. def pop(self, index=None):
  339. length = len(self)
  340. if index is None:
  341. index = length - 1
  342. elif index < 0:
  343. index = length + index
  344. if index < 0 or index >= length:
  345. raise IndexError("pop index out of range")
  346. return self._parent.pop(self._start + index)
  347. @inheritdoc
  348. def remove(self, item):
  349. index = self.index(item)
  350. del self._parent[self._start + index]
  351. @inheritdoc
  352. def reverse(self):
  353. item = self._render()
  354. item.reverse()
  355. self._parent[self._start:self._stop:self._step] = item
  356. if py3k:
  357. @inheritdoc
  358. def sort(self, key=None, reverse=None):
  359. item = self._render()
  360. kwargs = {}
  361. if key is not None:
  362. kwargs["key"] = key
  363. if reverse is not None:
  364. kwargs["reverse"] = reverse
  365. item.sort(**kwargs)
  366. self._parent[self._start:self._stop:self._step] = item
  367. else:
  368. @inheritdoc
  369. def sort(self, cmp=None, key=None, reverse=None):
  370. item = self._render()
  371. kwargs = {}
  372. if cmp is not None:
  373. kwargs["cmp"] = cmp
  374. if key is not None:
  375. kwargs["key"] = key
  376. if reverse is not None:
  377. kwargs["reverse"] = reverse
  378. item.sort(**kwargs)
  379. self._parent[self._start:self._stop:self._step] = item
  380. del inheritdoc