| @@ -44,21 +44,21 @@ class Wikicode(StringMixIn): | |||
| return "".join([str(node) for node in self.nodes]) | |||
| def _get_children(self, node): | |||
| """Iterate over all descendants of a given node, including itself. | |||
| """Iterate over all descendants of a given *node*, including itself. | |||
| This is implemented by the __iternodes__() generator of Node classes, | |||
| which by default yields itself and nothing more. | |||
| This is implemented by the ``__iternodes__()`` generator of ``Node`` | |||
| classes, which by default yields itself and nothing more. | |||
| """ | |||
| for context, child in node.__iternodes__(self._get_all_nodes): | |||
| yield child | |||
| def _get_context(self, node, obj): | |||
| """Return a ``Wikicode`` that contains ``obj`` in its descendants. | |||
| """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. | |||
| 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``. | |||
| Raises ``ValueError`` if *obj* is not within *node*. | |||
| """ | |||
| for context, child in node.__iternodes__(self._get_all_nodes): | |||
| if child is obj: | |||
| @@ -76,7 +76,10 @@ class Wikicode(StringMixIn): | |||
| yield child | |||
| def _is_equivalent(self, obj, node): | |||
| """Return ``True`` if obj and node are equivalent, otherwise ``False``. | |||
| """Return ``True`` if *obj* and *node* are equivalent, else ``False``. | |||
| If *obj* is a ``Node``, the function will test whether they are the | |||
| same object, otherwise it will compare them with ``==``. | |||
| """ | |||
| if isinstance(obj, Node): | |||
| if node is obj: | |||
| @@ -87,16 +90,28 @@ class Wikicode(StringMixIn): | |||
| return False | |||
| def _contains(self, nodes, obj): | |||
| """Return ``True`` if *obj* is inside of *nodes*, else ``False``. | |||
| If *obj* is a ``Node``, we will only return ``True`` if *obj* is | |||
| actually in the list (and not just a node that equals it). Otherwise, | |||
| the test is simply ``obj in nodes``. | |||
| """ | |||
| if isinstance(obj, Node): | |||
| for node in nodes: | |||
| if node is obj: | |||
| return True | |||
| else: | |||
| if obj in nodes: | |||
| return True | |||
| return False | |||
| return False | |||
| return obj in nodes | |||
| def _do_search(self, obj, recursive, callback, context, *args, **kwargs): | |||
| """Look within *context* for *obj*, executing *callback* if found. | |||
| 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: | |||
| for i, node in enumerate(context.nodes): | |||
| if self._is_equivalent(obj, node): | |||
| @@ -110,6 +125,16 @@ class Wikicode(StringMixIn): | |||
| callback(context, self.index(obj, recursive=False), *args, **kwargs) | |||
| def _get_tree(self, code, lines, marker, indent): | |||
| """Build a tree to illustrate the way the Wikicode object was parsed. | |||
| The method that builds the actual tree is ``__showtree__`` of ``Node`` | |||
| objects. *code* is the ``Wikicode`` object to build a tree for. *lines* | |||
| is the list to append the tree to, which is returned at the end of the | |||
| method. *marker* is some object to be used to indicate that the builder | |||
| should continue on from the last line instead of starting a new one; it | |||
| should be any object that can be tested for with ``is``. *indent* is | |||
| the starting indentation. | |||
| """ | |||
| def write(*args): | |||
| if lines and lines[-1] is marker: # Continue from the last line | |||
| lines.pop() # Remove the marker | |||
| @@ -126,6 +151,11 @@ class Wikicode(StringMixIn): | |||
| @property | |||
| def nodes(self): | |||
| """A list of :py:class:`~mwparserfromhell.nodes.Node` objects. | |||
| This is the internal data actually stored within a | |||
| :py:class:`~mwparserfromhell.wikicode.Wikicode` object. | |||
| """ | |||
| return self._nodes | |||
| @nodes.setter | |||
| @@ -133,9 +163,18 @@ class Wikicode(StringMixIn): | |||
| self._nodes = value | |||
| def get(self, index): | |||
| """Return the *index*\ th node within the list of nodes.""" | |||
| return self.nodes[index] | |||
| def set(self, index, value): | |||
| """Set the ``Node`` at *index* to *value*. | |||
| Raises :py:exc:`IndexError` if *index* is out of range, or | |||
| :py:exc:`ValueError` if *value* cannot be coerced into one | |||
| :py:class:`~mwparserfromhell.nodes.Node`. To insert multiple nodes at | |||
| an index, use :py:meth:`get` with either :py:meth:`remove` and | |||
| :py:meth:`insert` or :py:meth:`replace`. | |||
| """ | |||
| nodes = parse_anything(value).nodes | |||
| if len(nodes) > 1: | |||
| raise ValueError("Cannot coerce multiple nodes into one index") | |||
| @@ -146,6 +185,13 @@ class Wikicode(StringMixIn): | |||
| self.nodes[index] = nodes[0] | |||
| def index(self, obj, recursive=False): | |||
| """Return the index of *obj* in the list of nodes. | |||
| Raises :py:exc:`ValueError` if *obj* is not found. If *recursive* is | |||
| ``True``, we will look in all nodes of ours and their descendants, and | |||
| return the index of our direct descendant node within *our* list of | |||
| nodes. Otherwise, the lookup is done only on direct descendants. | |||
| """ | |||
| if recursive: | |||
| for i, node in enumerate(self.nodes): | |||
| if self._contains(self._get_children(node), obj): | |||
| @@ -158,15 +204,40 @@ class Wikicode(StringMixIn): | |||
| raise ValueError(obj) | |||
| def insert(self, index, value): | |||
| """Insert *value* at *index* in the list of nodes. | |||
| *value* can be anything parasable by | |||
| :py:func:`mwparserfromhell.utils.parse_anything`, which includes | |||
| strings or other :py:class:`~mwparserfromhell.wikicode.Wikicode` or | |||
| :py:class:`~mwparserfromhell.nodes.Node` objects. | |||
| """ | |||
| nodes = parse_anything(value).nodes | |||
| for node in reversed(nodes): | |||
| self.nodes.insert(index, node) | |||
| def insert_before(self, obj, value, recursive=True): | |||
| """Insert *value* immediately before *obj* in the list of nodes. | |||
| *value* can be anything parasable by | |||
| :py:func:`mwparserfromhell.utils.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:`~mwparserfromhell.wikicode.Wikicode` object. If *obj* is not | |||
| in the node list, :py:exc:`ValueError` is raised. | |||
| """ | |||
| callback = lambda self, i, value: self.insert(i, value) | |||
| self._do_search(obj, recursive, callback, self, value) | |||
| def insert_after(self, obj, value, recursive=True): | |||
| """Insert *value* immediately after *obj* in the list of nodes. | |||
| *value* can be anything parasable by | |||
| :py:func:`mwparserfromhell.utils.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:`~mwparserfromhell.wikicode.Wikicode` object. If *obj* is not | |||
| in the node list, :py:exc:`ValueError` is raised. | |||
| """ | |||
| callback = lambda self, i, value: self.insert(i + 1, value) | |||
| self._do_search(obj, recursive, callback, self, value) | |||