| @@ -44,21 +44,21 @@ class Wikicode(StringMixIn): | |||||
| return "".join([str(node) for node in self.nodes]) | return "".join([str(node) for node in self.nodes]) | ||||
| def _get_children(self, node): | 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): | for context, child in node.__iternodes__(self._get_all_nodes): | ||||
| yield child | yield child | ||||
| def _get_context(self, node, obj): | 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): | for context, child in node.__iternodes__(self._get_all_nodes): | ||||
| if child is obj: | if child is obj: | ||||
| @@ -76,7 +76,10 @@ class Wikicode(StringMixIn): | |||||
| yield child | yield child | ||||
| def _is_equivalent(self, obj, node): | 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 isinstance(obj, Node): | ||||
| if node is obj: | if node is obj: | ||||
| @@ -87,16 +90,28 @@ class Wikicode(StringMixIn): | |||||
| return False | return False | ||||
| def _contains(self, nodes, obj): | 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): | if isinstance(obj, Node): | ||||
| for node in nodes: | for node in nodes: | ||||
| if node is obj: | if node is obj: | ||||
| return True | 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): | 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: | if recursive: | ||||
| for i, node in enumerate(context.nodes): | for i, node in enumerate(context.nodes): | ||||
| if self._is_equivalent(obj, node): | if self._is_equivalent(obj, node): | ||||
| @@ -110,6 +125,16 @@ class Wikicode(StringMixIn): | |||||
| callback(context, self.index(obj, recursive=False), *args, **kwargs) | callback(context, self.index(obj, recursive=False), *args, **kwargs) | ||||
| 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. | |||||
| 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): | def write(*args): | ||||
| if lines and lines[-1] is marker: # Continue from the last line | if lines and lines[-1] is marker: # Continue from the last line | ||||
| lines.pop() # Remove the marker | lines.pop() # Remove the marker | ||||
| @@ -126,6 +151,11 @@ class Wikicode(StringMixIn): | |||||
| @property | @property | ||||
| def nodes(self): | 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 | return self._nodes | ||||
| @nodes.setter | @nodes.setter | ||||
| @@ -133,9 +163,18 @@ class Wikicode(StringMixIn): | |||||
| self._nodes = value | self._nodes = value | ||||
| def get(self, index): | def get(self, index): | ||||
| """Return the *index*\ th node within the list of nodes.""" | |||||
| return self.nodes[index] | return self.nodes[index] | ||||
| def set(self, index, value): | 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 | nodes = parse_anything(value).nodes | ||||
| if len(nodes) > 1: | if len(nodes) > 1: | ||||
| raise ValueError("Cannot coerce multiple nodes into one index") | raise ValueError("Cannot coerce multiple nodes into one index") | ||||
| @@ -146,6 +185,13 @@ class Wikicode(StringMixIn): | |||||
| self.nodes[index] = nodes[0] | self.nodes[index] = nodes[0] | ||||
| def index(self, obj, recursive=False): | 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: | if recursive: | ||||
| for i, node in enumerate(self.nodes): | for i, node in enumerate(self.nodes): | ||||
| if self._contains(self._get_children(node), obj): | if self._contains(self._get_children(node), obj): | ||||
| @@ -158,15 +204,40 @@ class Wikicode(StringMixIn): | |||||
| raise ValueError(obj) | raise ValueError(obj) | ||||
| def insert(self, index, value): | 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 | nodes = parse_anything(value).nodes | ||||
| for node in reversed(nodes): | for node in reversed(nodes): | ||||
| self.nodes.insert(index, node) | self.nodes.insert(index, node) | ||||
| 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. | |||||
| *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) | callback = lambda self, i, value: self.insert(i, value) | ||||
| self._do_search(obj, recursive, callback, self, value) | self._do_search(obj, recursive, callback, self, value) | ||||
| 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. | |||||
| *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) | callback = lambda self, i, value: self.insert(i + 1, value) | ||||
| self._do_search(obj, recursive, callback, self, value) | self._do_search(obj, recursive, callback, self, value) | ||||