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.
 
 
 
 

410 lines
14 KiB

  1. # -*- coding: utf-8 -*-
  2. #
  3. # Copyright (C) 2012-2016 Ben Kurtovic <ben.kurtovic@gmail.com>
  4. # Copyright (C) 2019-2020 Yuri Astrakhan <YuriAstrakhan@gmail.com>
  5. #
  6. # Permission is hereby granted, free of charge, to any person obtaining a copy
  7. # of this software and associated documentation files (the "Software"), to deal
  8. # in the Software without restriction, including without limitation the rights
  9. # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  10. # copies of the Software, and to permit persons to whom the Software is
  11. # furnished to do so, subject to the following conditions:
  12. #
  13. # The above copyright notice and this permission notice shall be included in
  14. # all copies or substantial portions of the Software.
  15. #
  16. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17. # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18. # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  19. # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20. # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  21. # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  22. # SOFTWARE.
  23. from _weakref import ref
  24. from typing import Tuple
  25. from .utils import inheritdoc
  26. from ..compat import py3k
  27. class ListStore:
  28. def __init__(self, iterator=None, attach=False):
  29. if attach:
  30. self.data = iterator
  31. else:
  32. self.data = [] if iterator is None else list(iterator)
  33. self.slices = {}
  34. def attach_slice(self, slc):
  35. slc_ref = ref(slc, self.detach_slice_ref)
  36. self.slices[id(slc_ref)] = slc_ref
  37. def detach_slice_ref(self, slice_ref):
  38. """Remove a child reference that is about to be garbage-collected."""
  39. del self.slices[id(slice_ref)]
  40. def detach_slice(self, slc_to_delete):
  41. """
  42. :type slc_to_delete: SmartSlice
  43. """
  44. for sid, slc_ref in self.slices.items():
  45. # slc: Slice
  46. slc = slc_ref()
  47. if slc is not None and slc is slc_to_delete:
  48. del self.slices[sid]
  49. break
  50. class SmartSlice:
  51. def __init__(self, store, start, stop, step):
  52. assert step is not None and step != 0
  53. assert start is None or stop is None or start <= stop
  54. # self._store: Store
  55. self._store = store
  56. # self._start: Union[int, None]
  57. self._start = start
  58. # self._stop: Union[int, None]
  59. self._stop = stop
  60. # self._step: int
  61. self._step = step
  62. self._store.attach_slice(self)
  63. def _update_indexes(self, start, stop, shift):
  64. if shift:
  65. for slc_ref in self._store.slices.values():
  66. # slc: SmartSlice
  67. slc = slc_ref()
  68. if slc is not None:
  69. if slc._start is not None and stop < slc._start:
  70. slc._start += shift
  71. if slc._stop is not None and start <= slc._stop:
  72. slc._stop += shift
  73. def _render(self):
  74. """Return the actual list from the stored start/stop/step."""
  75. if self._start is None:
  76. if self._stop is None:
  77. return self._store.data[::self._step]
  78. else:
  79. return self._store.data[:self._stop:self._step]
  80. elif self._stop is None:
  81. return self._store.data[self._start::self._step]
  82. else:
  83. return self._store.data[self._start:self._stop:self._step]
  84. def _adjust(self, start, stop, step, materialize=False):
  85. """
  86. :rtype: Tuple[int, int, int, int, int]
  87. """
  88. int_start = 0 if self._start is None else self._start
  89. int_stop = len(self._store.data) if self._stop is None else self._stop
  90. if self._step > 0:
  91. if start is None:
  92. _start = self._start
  93. else:
  94. _start = min(int_stop, max(int_start, (
  95. int_start if start >= 0 else int_stop) + start))
  96. if stop is None:
  97. _stop = self._stop
  98. else:
  99. _stop = min(int_stop, max(int_start, (
  100. int_start if stop >= 0 else int_stop) + stop))
  101. _step = self._step if step is None else (self._step * step)
  102. else:
  103. raise ValueError("Not implemented")
  104. # _start = stop if self._start is None else (
  105. # self._stop if stop is None else (self._stop + stop))
  106. # _stop = stop if self._stop is None else (
  107. # self._stop if stop is None else (self._stop + stop))
  108. # _step = self._step if step is None else (self._step * step)
  109. if materialize:
  110. if _start is None:
  111. _start = int_start
  112. if _stop is None:
  113. _stop = int_stop
  114. rng_start = _start if self._start is None else int_start
  115. rng_stop = _stop if self._stop is None else int_stop
  116. return _start, _stop, _step, rng_start, rng_stop
  117. def _adjust_pos(self, pos, validate):
  118. """
  119. :type pos: int
  120. :type validate: bool
  121. :rtype: int
  122. """
  123. assert isinstance(pos, int)
  124. int_start = 0 if self._start is None else self._start
  125. int_stop = len(self._store.data) if self._stop is None else self._stop
  126. if self._step > 0:
  127. _pos = (int_start if pos >= 0 else int_stop) + pos
  128. else:
  129. raise ValueError("Not implemented")
  130. if validate and not (int_start <= _pos < int_stop):
  131. raise IndexError('list index out of range')
  132. return _pos
  133. # def _delete_child(self, child_ref):
  134. # """Remove a child reference that is about to be garbage-collected."""
  135. # del self._children[id(child_ref)]
  136. #
  137. # def _detach_children(self):
  138. # """Remove all children and give them independent parent copies."""
  139. # children = [val[0] for val in self._children.values()]
  140. # for child in children:
  141. # child()._parent = list(self)
  142. # self._children.clear()
  143. def __getitem__(self, key):
  144. if isinstance(key, slice):
  145. start, stop, step, _, _ = self._adjust(key.start, key.stop, key.step)
  146. if start is not None and stop is not None and start > stop:
  147. stop = start
  148. return SmartSlice(self._store, start, stop, step)
  149. elif isinstance(key, int):
  150. return self._store.data[self._adjust_pos(key, True)]
  151. else:
  152. raise TypeError('list indices must be integers or slices, not '
  153. + type(key).__name__)
  154. def __setitem__(self, key, item):
  155. old_size = len(self._store.data)
  156. if isinstance(key, slice):
  157. start, stop, step, rng_start, rng_stop = self._adjust(key.start, key.stop,
  158. key.step, True)
  159. self._store.data[start:stop:step] = item
  160. self._update_indexes(start, stop, len(self._store.data) - old_size)
  161. elif isinstance(key, int):
  162. self._store.data[self._adjust_pos(key, True)] = item
  163. else:
  164. raise TypeError('list indices must be integers or slices, not '
  165. + type(key).__name__)
  166. def __delitem__(self, key):
  167. old_size = len(self._store.data)
  168. if isinstance(key, slice):
  169. start, stop, step, _, _ = self._adjust(key.start, key.stop, key.step, True)
  170. del self._store.data[start:stop:step]
  171. elif isinstance(key, int):
  172. start = stop = self._adjust_pos(key, True)
  173. del self._store.data[start]
  174. else:
  175. raise TypeError('list indices must be integers or slices, not '
  176. + type(key).__name__)
  177. self._update_indexes(start, stop, len(self._store.data) - old_size)
  178. if not py3k:
  179. def __getslice__(self, start, stop):
  180. return self.__getitem__(slice(start, stop))
  181. def __setslice__(self, start, stop, iterable):
  182. self.__setitem__(slice(start, stop), iterable)
  183. def __delslice__(self, start, stop):
  184. self.__delitem__(slice(start, stop))
  185. def __iter__(self):
  186. start = self._start
  187. stop = self._stop
  188. if start is None:
  189. start = 0 if self._step > 0 else (len(self._store.data) - 1)
  190. slc = SmartSlice(self._store, start, stop, self._step)
  191. while True:
  192. i = slc._start
  193. if self._step > 0:
  194. if i >= (len(self._store.data) if self._stop is None else self._stop):
  195. break
  196. elif i <= (-1 if self._stop is None else self._stop):
  197. break
  198. value = self._store.data[i]
  199. slc._start += self._step
  200. yield value
  201. def __reversed__(self):
  202. start = self._start
  203. stop = self._stop
  204. if stop is None:
  205. stop = len(self._store.data)
  206. slc = SmartSlice(self._store, start, stop, self._step)
  207. while True:
  208. i = slc._stop
  209. if self._step > 0:
  210. if i <= (0 if self._start is None else self._start):
  211. break
  212. elif i >= (len(self._store.data) if self._start is None else self._start):
  213. break
  214. value = self._store.data[i - 1]
  215. slc._stop -= self._step
  216. yield value
  217. def __repr__(self):
  218. return repr(self._render())
  219. def __lt__(self, other):
  220. if isinstance(other, SmartSlice):
  221. return self._render() < other._render()
  222. return self._render() < other
  223. def __le__(self, other):
  224. if isinstance(other, SmartSlice):
  225. return self._render() <= other._render()
  226. return self._render() <= other
  227. def __eq__(self, other):
  228. if isinstance(other, SmartSlice):
  229. return self._render() == other._render()
  230. return self._render() == other
  231. def __ne__(self, other):
  232. if isinstance(other, SmartSlice):
  233. return self._render() != other._render()
  234. return self._render() != other
  235. def __gt__(self, other):
  236. if isinstance(other, SmartSlice):
  237. return self._render() > other._render()
  238. return self._render() > other
  239. def __ge__(self, other):
  240. if isinstance(other, SmartSlice):
  241. return self._render() >= other._render()
  242. return self._render() >= other
  243. def __bool__(self):
  244. return bool(self._render())
  245. def __len__(self):
  246. size = len(self._store.data) if self._stop is None else self._stop
  247. if self._start is not None:
  248. size -= self._start
  249. return max(0, size // abs(self._step))
  250. def __mul__(self, other):
  251. return smart_list(self._render() * other)
  252. def __rmul__(self, other):
  253. return smart_list(other * self._render())
  254. def __imul__(self, other):
  255. self.extend(self._render() * (other - 1))
  256. return self
  257. def __contains__(self, item):
  258. return item in self._render()
  259. def __add__(self, other):
  260. return smart_list(self._render() + other)
  261. def __radd__(self, other):
  262. return smart_list(other + self._render())
  263. def __iadd__(self, other):
  264. self.extend(other)
  265. return self
  266. @inheritdoc
  267. def append(self, item):
  268. size = len(self)
  269. self[size:size] = [item]
  270. @inheritdoc
  271. def extend(self, item):
  272. size = len(self)
  273. self[size:size] = item
  274. @inheritdoc
  275. def insert(self, index, item):
  276. start, stop, step, rng_start, rng_stop = self._adjust(index, index, 1, True)
  277. self._store.data.insert(start, item)
  278. self._update_indexes(start, stop, 1)
  279. @inheritdoc
  280. def pop(self, index=None):
  281. start = 0 if self._start is None else self._start
  282. size = len(self)
  283. if index is None:
  284. index = size - 1
  285. elif index < 0:
  286. index = size + index
  287. if index < 0 or index >= size:
  288. raise IndexError("pop index out of range")
  289. pos = start + index
  290. result = self._store.data.pop(pos)
  291. self._update_indexes(pos, pos, -1)
  292. return result
  293. @inheritdoc
  294. def remove(self, item):
  295. index = self.index(item)
  296. del self[index]
  297. @inheritdoc
  298. def reverse(self):
  299. if self._start is None and self._stop is None and self._step == 1:
  300. self._store.data.reverse()
  301. else:
  302. vals = self._render()
  303. vals.reverse()
  304. self[:] = vals
  305. # values = self._render()
  306. # self._store.detach_slice(self)
  307. # self._store = Store(values, attach=True)
  308. # self._store.attach_slice(self)
  309. # self._start = None
  310. # self._stop = None
  311. # self._step = 1
  312. @inheritdoc
  313. def count(self, item):
  314. return self._render().count(item)
  315. @inheritdoc
  316. def index(self, item, start=None, stop=None):
  317. if start is None:
  318. return self._render().index(item)
  319. elif stop is None:
  320. return self._render().index(item, start)
  321. else:
  322. return self._render().index(item, start, stop)
  323. if py3k:
  324. @inheritdoc
  325. def sort(self, key=None, reverse=None):
  326. item = self._render()
  327. kwargs = {}
  328. if key is not None:
  329. kwargs["key"] = key
  330. if reverse is not None:
  331. kwargs["reverse"] = reverse
  332. item.sort(**kwargs)
  333. self._store.data[self._start:self._stop:self._step] = item
  334. else:
  335. @inheritdoc
  336. def sort(self, cmp=None, key=None, reverse=None):
  337. item = self._render()
  338. kwargs = {}
  339. if cmp is not None:
  340. kwargs["cmp"] = cmp
  341. if key is not None:
  342. kwargs["key"] = key
  343. if reverse is not None:
  344. kwargs["reverse"] = reverse
  345. item.sort(**kwargs)
  346. self._store.data[self._start:self._stop:self._step] = item
  347. def smart_list(iterator=None):
  348. return SmartSlice(ListStore(iterator), None, None, 1)