Browse Source

Cleanup Wikicode's filter functions; implement test_filter_family().

tags/v0.2
Ben Kurtovic 11 years ago
parent
commit
f700914caf
2 changed files with 99 additions and 35 deletions
  1. +36
    -33
      mwparserfromhell/wikicode.py
  2. +63
    -2
      tests/test_wikicode.py

+ 36
- 33
mwparserfromhell/wikicode.py View File

@@ -24,7 +24,8 @@ from __future__ import unicode_literals
import re import re


from .compat import maxsize, py3k, str from .compat import maxsize, py3k, str
from .nodes import Heading, Node, Tag, Template, Text, Wikilink
from .nodes import (Argument, Comment, Heading, HTMLEntity, Node, Tag,
Template, Text, Wikilink)
from .string_mixin import StringMixIn from .string_mixin import StringMixIn
from .utils import parse_anything from .utils import parse_anything


@@ -151,6 +152,36 @@ class Wikicode(StringMixIn):
node.__showtree__(write, get, mark) node.__showtree__(write, get, mark)
return lines return lines


@classmethod
def _build_filter_methods(cls, **meths):
"""Given Node types, build the corresponding i?filter shortcuts.

The should be given as keys storing the method's base name paired
with values storing the corresponding :py:class:`~.Node` type. For
example, the dict may contain the pair ``("templates", Template)``,
which will produce the methods :py:meth:`ifilter_templates` and
:py:meth:`filter_templates`, which are shortcuts for
:py:meth:`ifilter(forcetype=Template) <ifilter>` and
:py:meth:`filter(forcetype=Template) <filter>`, respectively. These
shortcuts are added to the class itself, with an appropriate docstring.
"""
doc = """Iterate over {0}.

This is equivalent to :py:meth:`{1}` with *forcetype* set to
:py:class:`~.{2}`.
"""
make_ifilter = lambda ftype: (lambda self, **kw:
self.ifilter(forcetype=ftype, **kw))
make_filter = lambda ftype: (lambda self, **kw:
self.filter(forcetype=ftype, **kw))
for name, ftype in (meths.items() if py3k else meths.iteritems()):
ifilter = make_ifilter(ftype)
filter = make_filter(ftype)
ifilter.__doc__ = doc.format(name, "ifilter", ftype.__name__)
filter.__doc__ = doc.format(name, "filter", ftype.__name__)
setattr(cls, "ifilter_" + name, ifilter)
setattr(cls, "filter_" + name, filter)

@property @property
def nodes(self): def nodes(self):
"""A list of :py:class:`~.Node` objects. """A list of :py:class:`~.Node` objects.
@@ -296,32 +327,6 @@ class Wikicode(StringMixIn):
if not matches or re.search(matches, str(node), flags): if not matches or re.search(matches, str(node), flags):
yield node yield node


@classmethod
def _build_filter_methods(cls, meths):
"""Given a dict of Node types, build corresponding i?filter shortcuts.

The dict should be given as keys storing the method's base name paired
with values storing the corresponding :py:class:`~.Node` type. For
example, the dict may contain the pair ``("templates", Template)``,
which will produce the methods :py:meth:`ifilter_templates` and
:py:meth:`filter_templates`, which are shortcuts for
:py:meth:`ifilter(forcetype=Template) <ifilter>` and
:py:meth:`filter(forcetype=Template) <filter>`, respectively. These
shortcuts are added to the class itself, with an appropriate docstring.
"""
doc = """Iterate over {0}.

This is equivalent to :py:meth:`{1}` with *forcetype* set to
:py:class:`~.{2}`.
"""
for name, forcetype in (meths.items() if py3k else meths.iteritems()):
ifil = lambda self, **kw: self.ifilter(forcetype=forcetype, **kw)
fil = lambda self, **kw: self.filter(forcetype=forcetype, **kw)
ifil.__doc__ = doc.format(name, "ifilter", forcetype)
fil.__doc__ = doc.format(name, "filter", forcetype)
setattr(cls, "ifilter_" + name, ifil)
setattr(cls, "filter_" + name, fil)

def filter(self, recursive=False, matches=None, flags=FLAGS, def filter(self, recursive=False, matches=None, flags=FLAGS,
forcetype=None): forcetype=None):
"""Return a list of nodes within our list matching certain conditions. """Return a list of nodes within our list matching certain conditions.
@@ -429,9 +434,7 @@ class Wikicode(StringMixIn):
marker = object() # Random object we can find with certainty in a list marker = object() # Random object we can find with certainty in a list
return "\n".join(self._get_tree(self, [], marker, 0)) return "\n".join(self._get_tree(self, [], marker, 0))


Wikicode._build_filter_methods({
"links": Wikilink,
"templates": Template,
"text": Text,
"tag": Tag
})
Wikicode._build_filter_methods(
arguments=Argument, comments=Comment, headings=Heading,
html_entities=HTMLEntity, tags=Tag, templates=Template, text=Text,
wikilinks=Wikilink)

+ 63
- 2
tests/test_wikicode.py View File

@@ -21,6 +21,8 @@
# SOFTWARE. # SOFTWARE.


from __future__ import unicode_literals from __future__ import unicode_literals
import re
from types import GeneratorType
import unittest import unittest


from mwparserfromhell.nodes import (Argument, Comment, Heading, HTMLEntity, from mwparserfromhell.nodes import (Argument, Comment, Heading, HTMLEntity,
@@ -210,7 +212,67 @@ class TestWikicode(TreeEqualityTestCase):


def test_filter_family(self): def test_filter_family(self):
"""test the Wikicode.i?filter() family of functions""" """test the Wikicode.i?filter() family of functions"""
pass
def genlist(gen):
self.assertIsInstance(gen, GeneratorType)
return list(gen)
ifilter = lambda code: (lambda **kw: genlist(code.ifilter(**kw)))

code = parse("a{{b}}c[[d]]{{{e}}}{{f}}[[g]]")
for func in (code.filter, ifilter(code)):
self.assertEqual(["a", "{{b}}", "c", "[[d]]", "{{{e}}}", "{{f}}",
"[[g]]"], func())
self.assertEqual(["{{{e}}}"], func(forcetype=Argument))
self.assertIs(code.get(4), func(forcetype=Argument)[0])
self.assertEqual(["a", "c"], func(forcetype=Text))
self.assertEqual([], func(forcetype=Heading))
self.assertRaises(TypeError, func, forcetype=True)

funcs = [
lambda name, **kw: getattr(code, "filter_" + name)(**kw),
lambda name, **kw: genlist(getattr(code, "ifilter_" + name)(**kw))
]
for get_filter in funcs:
self.assertEqual(["{{{e}}}"], get_filter("arguments"))
self.assertIs(code.get(4), get_filter("arguments")[0])
self.assertEqual([], get_filter("comments"))
self.assertEqual([], get_filter("headings"))
self.assertEqual([], get_filter("html_entities"))
self.assertEqual([], get_filter("tags"))
self.assertEqual(["{{b}}", "{{f}}"], get_filter("templates"))
self.assertEqual(["a", "c"], get_filter("text"))
self.assertEqual(["[[d]]", "[[g]]"], get_filter("wikilinks"))

code2 = parse("{{a|{{b}}|{{c|d={{f}}{{h}}}}}}")
for func in (code2.filter, ifilter(code2)):
self.assertEqual(["{{a|{{b}}|{{c|d={{f}}{{h}}}}}}"],
func(recursive=False, forcetype=Template))
self.assertEqual(["{{a|{{b}}|{{c|d={{f}}{{h}}}}}}", "{{b}}",
"{{c|d={{f}}{{h}}}}", "{{f}}", "{{h}}"],
func(recursive=True, forcetype=Template))

code3 = parse("{{foobar}}{{FOO}}{{baz}}{{bz}}")
for func in (code3.filter, ifilter(code3)):
self.assertEqual(["{{foobar}}", "{{FOO}}"], func(matches=r"foo"))
self.assertEqual(["{{foobar}}", "{{FOO}}"],
func(matches=r"^{{foo.*?}}"))
self.assertEqual(["{{foobar}}"],
func(matches=r"^{{foo.*?}}", flags=re.UNICODE))
self.assertEqual(["{{baz}}", "{{bz}}"], func(matches=r"^{{b.*?z"))
self.assertEqual(["{{baz}}"], func(matches=r"^{{b.+?z}}"))

self.assertEqual(["{{a|{{b}}|{{c|d={{f}}{{h}}}}}}"],
code2.filter_templates(recursive=False))
self.assertEqual(["{{a|{{b}}|{{c|d={{f}}{{h}}}}}}", "{{b}}",
"{{c|d={{f}}{{h}}}}", "{{f}}", "{{h}}"],
code2.filter_templates(recursive=True))
self.assertEqual(["{{baz}}", "{{bz}}"],
code3.filter_templates(matches=r"^{{b.*?z"))
self.assertEqual([], code3.filter_tags(matches=r"^{{b.*?z"))
self.assertEqual([], code3.filter_tags(matches=r"^{{b.*?z", flags=0))

self.assertRaises(TypeError, code.filter_templates, 100)
self.assertRaises(TypeError, code.filter_templates, a=42)
self.assertRaises(TypeError, code.filter_templates, forcetype=Template)


def test_get_sections(self): def test_get_sections(self):
"""test Wikicode.get_sections()""" """test Wikicode.get_sections()"""
@@ -224,6 +286,5 @@ class TestWikicode(TreeEqualityTestCase):
"""test Wikicode.get_tree()""" """test Wikicode.get_tree()"""
pass pass



if __name__ == "__main__": if __name__ == "__main__":
unittest.main(verbosity=2) unittest.main(verbosity=2)

Loading…
Cancel
Save