| @@ -60,19 +60,6 @@ class Wikicode(StringMixIn): | |||||
| for context, child in node.__iternodes__(self._get_all_nodes): | for context, child in node.__iternodes__(self._get_all_nodes): | ||||
| yield child | yield child | ||||
| def _get_context(self, node, obj): | |||||
| """Return a ``Wikicode`` that contains *obj* in its descendants. | |||||
| The closest (shortest distance from *node*) suitable ``Wikicode`` will | |||||
| be returned, or ``None`` if the *obj* is the *node* itself. | |||||
| Raises ``ValueError`` if *obj* is not within *node*. | |||||
| """ | |||||
| for context, child in node.__iternodes__(self._get_all_nodes): | |||||
| if self._is_equivalent(obj, child): | |||||
| return context | |||||
| raise ValueError(obj) | |||||
| def _get_all_nodes(self, code): | def _get_all_nodes(self, code): | ||||
| """Iterate over all of our descendant nodes. | """Iterate over all of our descendant nodes. | ||||
| @@ -105,26 +92,54 @@ class Wikicode(StringMixIn): | |||||
| return False | return False | ||||
| return obj in nodes | return obj in nodes | ||||
| def _do_search(self, obj, recursive, callback, context, *args, **kwargs): | |||||
| """Look within *context* for *obj*, executing *callback* if found. | |||||
| def _do_search(self, obj, recursive, context=None, literal=None): | |||||
| """Return some info about the location of *obj* within *context*. | |||||
| If *recursive* is ``True``, we'll look within *context* and its | |||||
| descendants, otherwise we'll just execute *callback*. We raise | |||||
| :py:exc:`ValueError` if *obj* isn't in our node list or context. If | |||||
| found, *callback* is passed the context, the index of the node within | |||||
| the context, and whatever were passed as ``*args`` and ``**kwargs``. | |||||
| If *recursive* is ``True``, we'll look within *context* (``self`` by | |||||
| default) and its descendants, otherwise just *context*. We raise | |||||
| :py:exc:`ValueError` if *obj* isn't found. The return data is a list of | |||||
| 3-tuples (*type*, *context*, *data*) where *type* is *obj*\ 's best | |||||
| type resolution (either ``Node``, ``Wikicode``, or ``str``), *context* | |||||
| is the closest ``Wikicode`` encompassing it, and *data* is either a | |||||
| ``Node``, a list of ``Node``\ s, or ``None`` depending on *type*. | |||||
| """ | """ | ||||
| if recursive: | |||||
| for i, node in enumerate(context.nodes): | |||||
| if self._is_equivalent(obj, node): | |||||
| return callback(context, i, *args, **kwargs) | |||||
| if self._contains(self._get_children(node), obj): | |||||
| context = self._get_context(node, obj) | |||||
| return self._do_search(obj, recursive, callback, context, | |||||
| *args, **kwargs) | |||||
| raise ValueError(obj) | |||||
| if not context: | |||||
| context = self | |||||
| literal = isinstance(obj, (Node, Wikicode)) | |||||
| obj = parse_anything(obj) | |||||
| if not obj or obj not in self: | |||||
| raise ValueError(obj) | |||||
| if len(obj.nodes) == 1: | |||||
| obj = obj.get(0) | |||||
| compare = lambda a, b: (a is b) if literal else (a == b) | |||||
| results = [] | |||||
| i = 0 | |||||
| while i < len(context.nodes): | |||||
| node = context.get(i) | |||||
| if isinstance(obj, Node) and compare(obj, node): | |||||
| results.append((Node, context, node)) | |||||
| elif isinstance(obj, Wikicode) and compare(obj.get(0), node): | |||||
| for j in range(1, len(obj.nodes)): | |||||
| if not compare(obj.get(j), context.get(i + j)): | |||||
| break | |||||
| else: | |||||
| nodes = list(context.nodes[i:i + len(obj.nodes)]) | |||||
| results.append((Wikicode, context, nodes)) | |||||
| i += len(obj.nodes) - 1 | |||||
| elif recursive: | |||||
| contexts = node.__iternodes__(self._get_all_nodes) | |||||
| for code in {ctx for ctx, child in contexts}: | |||||
| if code and obj in code: | |||||
| search = self._do_search(obj, recursive, code, literal) | |||||
| results.extend(search) | |||||
| i += 1 | |||||
| callback(context, self.index(obj, recursive=False), *args, **kwargs) | |||||
| if not results and not literal and recursive: | |||||
| results.append((str, context, None)) | |||||
| if not results and context is self: | |||||
| raise ValueError(obj) | |||||
| return results | |||||
| def _get_tree(self, code, lines, marker, indent): | def _get_tree(self, code, lines, marker, indent): | ||||
| """Build a tree to illustrate the way the Wikicode object was parsed. | """Build a tree to illustrate the way the Wikicode object was parsed. | ||||
| @@ -253,41 +268,64 @@ class Wikicode(StringMixIn): | |||||
| def insert_before(self, obj, value, recursive=True): | def insert_before(self, obj, value, recursive=True): | ||||
| """Insert *value* immediately before *obj* in the list of nodes. | """Insert *value* immediately before *obj* in the list of nodes. | ||||
| *obj* can be either a string or a :py:class:`~.Node`. *value* can be | |||||
| anything parasable by :py:func:`.parse_anything`. If *recursive* is | |||||
| ``True``, we will try to find *obj* within our child nodes even if it | |||||
| is not a direct descendant of this :py:class:`~.Wikicode` object. If | |||||
| *obj* is not in the node list, :py:exc:`ValueError` is raised. | |||||
| *obj* can be either a string, a :py:class:`~.Node`, or other | |||||
| :py:class:`~.Wikicode` object (as created by :py:meth:`get_sections`, | |||||
| for example). *value* can be anything parasable by | |||||
| :py:func:`.parse_anything`. If *recursive* is ``True``, we will try to | |||||
| find *obj* within our child nodes even if it is not a direct descendant | |||||
| of this :py:class:`~.Wikicode` object. If *obj* is not found, | |||||
| :py:exc:`ValueError` is raised. | |||||
| """ | """ | ||||
| callback = lambda self, i, value: self.insert(i, value) | |||||
| self._do_search(obj, recursive, callback, self, value) | |||||
| for restype, context, data in self._do_search(obj, recursive): | |||||
| if restype in (Node, Wikicode): | |||||
| i = context.index(data if restype is Node else data[0], False) | |||||
| context.insert(i, value) | |||||
| else: | |||||
| obj = str(obj) | |||||
| context.nodes = str(context).replace(obj, str(value) + obj) | |||||
| def insert_after(self, obj, value, recursive=True): | def insert_after(self, obj, value, recursive=True): | ||||
| """Insert *value* immediately after *obj* in the list of nodes. | """Insert *value* immediately after *obj* in the list of nodes. | ||||
| *obj* can be either a string or a :py:class:`~.Node`. *value* can be | |||||
| anything parasable by :py:func:`.parse_anything`. If *recursive* is | |||||
| ``True``, we will try to find *obj* within our child nodes even if it | |||||
| is not a direct descendant of this :py:class:`~.Wikicode` object. If | |||||
| *obj* is not in the node list, :py:exc:`ValueError` is raised. | |||||
| *obj* can be either a string, a :py:class:`~.Node`, or other | |||||
| :py:class:`~.Wikicode` object (as created by :py:meth:`get_sections`, | |||||
| for example). *value* can be anything parasable by | |||||
| :py:func:`.parse_anything`. If *recursive* is ``True``, we will try to | |||||
| find *obj* within our child nodes even if it is not a direct descendant | |||||
| of this :py:class:`~.Wikicode` object. If *obj* is not found, | |||||
| :py:exc:`ValueError` is raised. | |||||
| """ | """ | ||||
| callback = lambda self, i, value: self.insert(i + 1, value) | |||||
| self._do_search(obj, recursive, callback, self, value) | |||||
| for restype, context, data in self._do_search(obj, recursive): | |||||
| if restype in (Node, Wikicode): | |||||
| i = context.index(data if restype is Node else data[-1], False) | |||||
| context.insert(i + 1, value) | |||||
| else: | |||||
| obj = str(obj) | |||||
| context.nodes = str(context).replace(obj, obj + str(value)) | |||||
| def replace(self, obj, value, recursive=True): | def replace(self, obj, value, recursive=True): | ||||
| """Replace *obj* with *value* in the list of nodes. | """Replace *obj* with *value* in the list of nodes. | ||||
| *obj* can be either a string or a :py:class:`~.Node`. *value* can be | |||||
| anything parasable by :py:func:`.parse_anything`. If *recursive* is | |||||
| ``True``, we will try to find *obj* within our child nodes even if it | |||||
| is not a direct descendant of this :py:class:`~.Wikicode` object. If | |||||
| *obj* is not in the node list, :py:exc:`ValueError` is raised. | |||||
| *obj* can be either a string, a :py:class:`~.Node`, or other | |||||
| :py:class:`~.Wikicode` object (as created by :py:meth:`get_sections`, | |||||
| for example). *value* can be anything parasable by | |||||
| :py:func:`.parse_anything`. If *recursive* is ``True``, we will try to | |||||
| find *obj* within our child nodes even if it is not a direct descendant | |||||
| of this :py:class:`~.Wikicode` object. If *obj* is not found, | |||||
| :py:exc:`ValueError` is raised. | |||||
| """ | """ | ||||
| def callback(self, i, value): | |||||
| self.nodes.pop(i) | |||||
| self.insert(i, value) | |||||
| self._do_search(obj, recursive, callback, self, value) | |||||
| for restype, context, data in self._do_search(obj, recursive): | |||||
| if restype is Node: | |||||
| i = context.index(data, False) | |||||
| context.nodes.pop(i) | |||||
| context.insert(i, value) | |||||
| elif restype is Wikicode: | |||||
| i = context.index(data[0], False) | |||||
| for _ in data: | |||||
| context.nodes.pop(i) | |||||
| context.insert(i, value) | |||||
| else: | |||||
| context.nodes = str(context).replace(str(obj), str(value)) | |||||
| def append(self, value): | def append(self, value): | ||||
| """Insert *value* at the end of the list of nodes. | """Insert *value* at the end of the list of nodes. | ||||
| @@ -301,13 +339,22 @@ class Wikicode(StringMixIn): | |||||
| def remove(self, obj, recursive=True): | def remove(self, obj, recursive=True): | ||||
| """Remove *obj* from the list of nodes. | """Remove *obj* from the list of nodes. | ||||
| *obj* can be either a string or a :py:class:`~.Node`. If *recursive* is | |||||
| ``True``, we will try to find *obj* within our child nodes even if it | |||||
| is not a direct descendant of this :py:class:`~.Wikicode` object. If | |||||
| *obj* is not in the node list, :py:exc:`ValueError` is raised. | |||||
| *obj* can be either a string, a :py:class:`~.Node`, or other | |||||
| :py:class:`~.Wikicode` object (as created by :py:meth:`get_sections`, | |||||
| for example). If *recursive* is ``True``, we will try to find *obj* | |||||
| within our child nodes even if it is not a direct descendant of this | |||||
| :py:class:`~.Wikicode` object. If *obj* is not found, | |||||
| :py:exc:`ValueError` is raised. | |||||
| """ | """ | ||||
| callback = lambda self, i: self.nodes.pop(i) | |||||
| self._do_search(obj, recursive, callback, self) | |||||
| for restype, context, data in self._do_search(obj, recursive): | |||||
| if restype is Node: | |||||
| context.nodes.pop(context.index(data, False)) | |||||
| elif restype is Wikicode: | |||||
| i = context.index(data[0], False) | |||||
| for _ in data: | |||||
| context.nodes.pop(i) | |||||
| else: | |||||
| context.nodes = str(context).replace(str(obj), "") | |||||
| def matches(self, other): | def matches(self, other): | ||||
| """Do a loose equivalency test suitable for comparing page names. | """Do a loose equivalency test suitable for comparing page names. | ||||