Browse Source

Merge branch 'develop'

tags/v0.4
Ben Kurtovic 11 years ago
parent
commit
38957be574
11 changed files with 138 additions and 29 deletions
  1. +1
    -0
      .travis.yml
  2. +6
    -0
      CHANGELOG
  3. +11
    -0
      docs/changelog.rst
  4. +1
    -1
      mwparserfromhell/__init__.py
  5. +2
    -1
      mwparserfromhell/compat.py
  6. +29
    -12
      mwparserfromhell/nodes/template.py
  7. +4
    -4
      mwparserfromhell/string_mixin.py
  8. +1
    -0
      setup.py
  9. +6
    -4
      tests/test_string_mixin.py
  10. +76
    -6
      tests/test_template.py
  11. +1
    -1
      tests/tokenizer/tags.mwtest

+ 1
- 0
.travis.yml View File

@@ -1,6 +1,7 @@
language: python language: python
python: python:
- "2.7" - "2.7"
- "3.2"
- "3.3" - "3.3"
install: python setup.py build install: python setup.py build
script: python setup.py test -q script: python setup.py test -q

+ 6
- 0
CHANGELOG View File

@@ -1,3 +1,9 @@
v0.3.2 (released September 1, 2013):

- Added support for Python 3.2 (along with current support for 3.3 and 2.7).
- Renamed Template.remove()'s first argument from 'name' to 'param', which now
accepts Parameter objects in addition to parameter name strings.

v0.3.1 (released August 29, 2013): v0.3.1 (released August 29, 2013):


- Fixed a parser bug involving URLs nested inside other markup. - Fixed a parser bug involving URLs nested inside other markup.


+ 11
- 0
docs/changelog.rst View File

@@ -1,6 +1,17 @@
Changelog Changelog
========= =========


v0.3.2
------

`Released September 1, 2013 <https://github.com/earwig/mwparserfromhell/tree/v0.3.2>`_
(`changes <https://github.com/earwig/mwparserfromhell/compare/v0.3.1...v0.3.2>`__):

- Added support for Python 3.2 (along with current support for 3.3 and 2.7).
- Renamed :py:meth:`.Template.remove`\ 's first argument from *name* to
*param*, which now accepts :py:class:`.Parameter` objects in addition to
parameter name strings.

v0.3.1 v0.3.1
------ ------




+ 1
- 1
mwparserfromhell/__init__.py View File

@@ -31,7 +31,7 @@ from __future__ import unicode_literals
__author__ = "Ben Kurtovic" __author__ = "Ben Kurtovic"
__copyright__ = "Copyright (C) 2012, 2013 Ben Kurtovic" __copyright__ = "Copyright (C) 2012, 2013 Ben Kurtovic"
__license__ = "MIT License" __license__ = "MIT License"
__version__ = "0.3.1"
__version__ = "0.3.2"
__email__ = "ben.kurtovic@verizon.net" __email__ = "ben.kurtovic@verizon.net"


from . import (compat, definitions, nodes, parser, smart_list, string_mixin, from . import (compat, definitions, nodes, parser, smart_list, string_mixin,


+ 2
- 1
mwparserfromhell/compat.py View File

@@ -10,7 +10,8 @@ types are meant to be imported directly from within the parser's modules.


import sys import sys


py3k = sys.version_info[0] == 3
py3k = sys.version_info.major == 3
py32 = py3k and sys.version_info.minor == 2


if py3k: if py3k:
bytes = bytes bytes = bytes


+ 29
- 12
mwparserfromhell/nodes/template.py View File

@@ -150,6 +150,16 @@ class Template(Node):
return False return False
return True return True


def _remove_exact(self, needle, keep_field):
"""Remove a specific parameter, *needle*, from the template."""
for i, param in enumerate(self.params):
if param is needle:
if keep_field or not self._remove_without_field(param, i):
self._blank_param_value(param.value)
else:
self.params.pop(i)
return

@property @property
def name(self): def name(self):
"""The name of the template, as a :py:class:`~.Wikicode` object.""" """The name of the template, as a :py:class:`~.Wikicode` object."""
@@ -180,7 +190,8 @@ class Template(Node):
return True return True
return False return False


has_param = lambda self, *args, **kwargs: self.has(*args, **kwargs)
has_param = lambda self, name, ignore_empty=True: \
self.has(name, ignore_empty)
has_param.__doc__ = "Alias for :py:meth:`has`." has_param.__doc__ = "Alias for :py:meth:`has`."


def get(self, name): def get(self, name):
@@ -280,8 +291,12 @@ class Template(Node):
self.params.append(param) self.params.append(param)
return param return param


def remove(self, name, keep_field=False):
"""Remove a parameter from the template whose name is *name*.
def remove(self, param, keep_field=False):
"""Remove a parameter from the template, identified by *param*.

If *param* is a :py:class:`.Parameter` object, it will be matched
exactly, otherwise it will be treated like the *name* argument to
:py:meth:`has` and :py:meth:`get`.


If *keep_field* is ``True``, we will keep the parameter's name, but If *keep_field* is ``True``, we will keep the parameter's name, but
blank its value. Otherwise, we will remove the parameter completely blank its value. Otherwise, we will remove the parameter completely
@@ -289,12 +304,14 @@ class Template(Node):
from ``{{foo|bar|baz}}`` is unsafe because ``{{foo|baz}}`` is not what from ``{{foo|bar|baz}}`` is unsafe because ``{{foo|baz}}`` is not what
we expected, so ``{{foo||baz}}`` will be produced instead). we expected, so ``{{foo||baz}}`` will be produced instead).


If the parameter shows up multiple times in the template, we will
remove all instances of it (and keep one if *keep_field* is ``True`` -
the first instance if none have dependents, otherwise the one with
dependents will be kept).
If the parameter shows up multiple times in the template and *param* is
not a :py:class:`.Parameter` object, we will remove all instances of it
(and keep only one if *keep_field* is ``True`` - the first instance if
none have dependents, otherwise the one with dependents will be kept).
""" """
name = str(name).strip()
if isinstance(param, Parameter):
return self._remove_exact(param, keep_field)
name = str(param).strip()
removed = False removed = False
to_remove = [] to_remove = []
for i, param in enumerate(self.params): for i, param in enumerate(self.params):
@@ -304,15 +321,15 @@ class Template(Node):
self._blank_param_value(param.value) self._blank_param_value(param.value)
keep_field = False keep_field = False
else: else:
to_remove.append(param)
to_remove.append(i)
else: else:
if self._remove_without_field(param, i): if self._remove_without_field(param, i):
to_remove.append(param)
to_remove.append(i)
else: else:
self._blank_param_value(param.value) self._blank_param_value(param.value)
if not removed: if not removed:
removed = True removed = True
if not removed: if not removed:
raise ValueError(name) raise ValueError(name)
for param in to_remove:
self.params.remove(param)
for i in reversed(to_remove):
self.params.pop(i)

+ 4
- 4
mwparserfromhell/string_mixin.py View File

@@ -27,7 +27,7 @@ interface for the ``unicode`` type (``str`` on py3k) in a dynamic manner.


from __future__ import unicode_literals from __future__ import unicode_literals


from .compat import py3k, str
from .compat import py3k, py32, str


__all__ = ["StringMixIn"] __all__ = ["StringMixIn"]


@@ -125,7 +125,7 @@ class StringMixIn(object):
def capitalize(self): def capitalize(self):
return self.__unicode__().capitalize() return self.__unicode__().capitalize()


if py3k:
if py3k and not py32:
@inheritdoc @inheritdoc
def casefold(self): def casefold(self):
return self.__unicode__().casefold() return self.__unicode__().casefold()
@@ -288,7 +288,7 @@ class StringMixIn(object):
def rpartition(self, sep): def rpartition(self, sep):
return self.__unicode__().rpartition(sep) return self.__unicode__().rpartition(sep)


if py3k:
if py3k and not py32:
@inheritdoc @inheritdoc
def rsplit(self, sep=None, maxsplit=None): def rsplit(self, sep=None, maxsplit=None):
kwargs = {} kwargs = {}
@@ -310,7 +310,7 @@ class StringMixIn(object):
def rstrip(self, chars=None): def rstrip(self, chars=None):
return self.__unicode__().rstrip(chars) return self.__unicode__().rstrip(chars)


if py3k:
if py3k and not py32:
@inheritdoc @inheritdoc
def split(self, sep=None, maxsplit=None): def split(self, sep=None, maxsplit=None):
kwargs = {} kwargs = {}


+ 1
- 0
setup.py View File

@@ -54,6 +54,7 @@ setup(
"Operating System :: OS Independent", "Operating System :: OS Independent",
"Programming Language :: Python :: 2.7", "Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3", "Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.2",
"Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.3",
"Topic :: Text Processing :: Markup" "Topic :: Text Processing :: Markup"
], ],


+ 6
- 4
tests/test_string_mixin.py View File

@@ -25,7 +25,7 @@ from sys import getdefaultencoding
from types import GeneratorType from types import GeneratorType
import unittest import unittest


from mwparserfromhell.compat import bytes, py3k, str
from mwparserfromhell.compat import bytes, py3k, py32, str
from mwparserfromhell.string_mixin import StringMixIn from mwparserfromhell.string_mixin import StringMixIn


from .compat import range from .compat import range
@@ -52,8 +52,10 @@ class TestStringMixIn(unittest.TestCase):
"rsplit", "rstrip", "split", "splitlines", "startswith", "strip", "rsplit", "rstrip", "split", "splitlines", "startswith", "strip",
"swapcase", "title", "translate", "upper", "zfill"] "swapcase", "title", "translate", "upper", "zfill"]
if py3k: if py3k:
methods.extend(["casefold", "format_map", "isidentifier",
"isprintable", "maketrans"])
if not py32:
methods.append("casefold")
methods.extend(["format_map", "isidentifier", "isprintable",
"maketrans"])
else: else:
methods.append("decode") methods.append("decode")
for meth in methods: for meth in methods:
@@ -325,7 +327,7 @@ class TestStringMixIn(unittest.TestCase):
self.assertEqual("", str15.lower()) self.assertEqual("", str15.lower())
self.assertEqual("foobar", str16.lower()) self.assertEqual("foobar", str16.lower())
self.assertEqual("ß", str22.lower()) self.assertEqual("ß", str22.lower())
if py3k:
if py3k and not py32:
self.assertEqual("", str15.casefold()) self.assertEqual("", str15.casefold())
self.assertEqual("foobar", str16.casefold()) self.assertEqual("foobar", str16.casefold())
self.assertEqual("ss", str22.casefold()) self.assertEqual("ss", str22.casefold())


+ 76
- 6
tests/test_template.py View File

@@ -316,12 +316,12 @@ class TestTemplate(TreeEqualityTestCase):
def test_remove(self): def test_remove(self):
"""test Template.remove()""" """test Template.remove()"""
node1 = Template(wraptext("foobar")) node1 = Template(wraptext("foobar"))
node2 = Template(wraptext("foo"), [pgenh("1", "bar"),
pgens("abc", "def")])
node3 = Template(wraptext("foo"), [pgenh("1", "bar"),
pgens("abc", "def")])
node4 = Template(wraptext("foo"), [pgenh("1", "bar"),
pgenh("2", "baz")])
node2 = Template(wraptext("foo"),
[pgenh("1", "bar"), pgens("abc", "def")])
node3 = Template(wraptext("foo"),
[pgenh("1", "bar"), pgens("abc", "def")])
node4 = Template(wraptext("foo"),
[pgenh("1", "bar"), pgenh("2", "baz")])
node5 = Template(wraptext("foo"), [ node5 = Template(wraptext("foo"), [
pgens(" a", "b"), pgens("b", "c"), pgens("a ", "d")]) pgens(" a", "b"), pgens("b", "c"), pgens("a ", "d")])
node6 = Template(wraptext("foo"), [ node6 = Template(wraptext("foo"), [
@@ -334,6 +334,44 @@ class TestTemplate(TreeEqualityTestCase):
pgens("1 ", "a"), pgenh("1", "b"), pgenh("2", "c")]) pgens("1 ", "a"), pgenh("1", "b"), pgenh("2", "c")])
node10 = Template(wraptext("foo"), [ node10 = Template(wraptext("foo"), [
pgens("1 ", "a"), pgenh("1", "b"), pgenh("2", "c")]) pgens("1 ", "a"), pgenh("1", "b"), pgenh("2", "c")])
node11 = Template(wraptext("foo"), [
pgens(" a", "b"), pgens("b", "c"), pgens("a ", "d")])
node12 = Template(wraptext("foo"), [
pgens(" a", "b"), pgens("b", "c"), pgens("a ", "d")])
node13 = Template(wraptext("foo"), [
pgens(" a", "b"), pgens("b", "c"), pgens("a ", "d")])
node14 = Template(wraptext("foo"), [
pgens(" a", "b"), pgens("b", "c"), pgens("a ", "d")])
node15 = Template(wraptext("foo"), [
pgens(" a", "b"), pgens("b", "c"), pgens("a ", "d")])
node16 = Template(wraptext("foo"), [
pgens(" a", "b"), pgens("b", "c"), pgens("a ", "d")])
node17 = Template(wraptext("foo"), [
pgens("1 ", "a"), pgenh("1", "b"), pgenh("2", "c")])
node18 = Template(wraptext("foo"), [
pgens("1 ", "a"), pgenh("1", "b"), pgenh("2", "c")])
node19 = Template(wraptext("foo"), [
pgens("1 ", "a"), pgenh("1", "b"), pgenh("2", "c")])
node20 = Template(wraptext("foo"), [
pgens("1 ", "a"), pgenh("1", "b"), pgenh("2", "c")])
node21 = Template(wraptext("foo"), [
pgens("a", "b"), pgens("c", "d"), pgens("e", "f"), pgens("a", "b"),
pgens("a", "b")])
node22 = Template(wraptext("foo"), [
pgens("a", "b"), pgens("c", "d"), pgens("e", "f"), pgens("a", "b"),
pgens("a", "b")])
node23 = Template(wraptext("foo"), [
pgens("a", "b"), pgens("c", "d"), pgens("e", "f"), pgens("a", "b"),
pgens("a", "b")])
node24 = Template(wraptext("foo"), [
pgens("a", "b"), pgens("c", "d"), pgens("e", "f"), pgens("a", "b"),
pgens("a", "b")])
node25 = Template(wraptext("foo"), [
pgens("a", "b"), pgens("c", "d"), pgens("e", "f"), pgens("a", "b"),
pgens("a", "b")])
node26 = Template(wraptext("foo"), [
pgens("a", "b"), pgens("c", "d"), pgens("e", "f"), pgens("a", "b"),
pgens("a", "b")])


node2.remove("1") node2.remove("1")
node2.remove("abc") node2.remove("abc")
@@ -346,6 +384,22 @@ class TestTemplate(TreeEqualityTestCase):
node8.remove(1, keep_field=False) node8.remove(1, keep_field=False)
node9.remove(1, keep_field=True) node9.remove(1, keep_field=True)
node10.remove(1, keep_field=False) node10.remove(1, keep_field=False)
node11.remove(node11.params[0], keep_field=False)
node12.remove(node12.params[0], keep_field=True)
node13.remove(node13.params[1], keep_field=False)
node14.remove(node14.params[1], keep_field=True)
node15.remove(node15.params[2], keep_field=False)
node16.remove(node16.params[2], keep_field=True)
node17.remove(node17.params[0], keep_field=False)
node18.remove(node18.params[0], keep_field=True)
node19.remove(node19.params[1], keep_field=False)
node20.remove(node20.params[1], keep_field=True)
node21.remove("a", keep_field=False)
node22.remove("a", keep_field=True)
node23.remove(node23.params[0], keep_field=False)
node24.remove(node24.params[0], keep_field=True)
node25.remove(node25.params[3], keep_field=False)
node26.remove(node26.params[3], keep_field=True)


self.assertRaises(ValueError, node1.remove, 1) self.assertRaises(ValueError, node1.remove, 1)
self.assertRaises(ValueError, node1.remove, "a") self.assertRaises(ValueError, node1.remove, "a")
@@ -359,6 +413,22 @@ class TestTemplate(TreeEqualityTestCase):
self.assertEqual("{{foo|2=c}}", node8) self.assertEqual("{{foo|2=c}}", node8)
self.assertEqual("{{foo||c}}", node9) self.assertEqual("{{foo||c}}", node9)
self.assertEqual("{{foo||c}}", node10) self.assertEqual("{{foo||c}}", node10)
self.assertEqual("{{foo|b=c|a =d}}", node11)
self.assertEqual("{{foo| a=|b=c|a =d}}", node12)
self.assertEqual("{{foo| a=b|a =d}}", node13)
self.assertEqual("{{foo| a=b|b=|a =d}}", node14)
self.assertEqual("{{foo| a=b|b=c}}", node15)
self.assertEqual("{{foo| a=b|b=c|a =}}", node16)
self.assertEqual("{{foo|b|c}}", node17)
self.assertEqual("{{foo|1 =|b|c}}", node18)
self.assertEqual("{{foo|1 =a||c}}", node19)
self.assertEqual("{{foo|1 =a||c}}", node20)
self.assertEqual("{{foo|c=d|e=f}}", node21)
self.assertEqual("{{foo|a=|c=d|e=f}}", node22)
self.assertEqual("{{foo|c=d|e=f|a=b|a=b}}", node23)
self.assertEqual("{{foo|a=|c=d|e=f|a=b|a=b}}", node24)
self.assertEqual("{{foo|a=b|c=d|e=f|a=b}}", node25)
self.assertEqual("{{foo|a=b|c=d|e=f|a=|a=b}}", node26)


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

+ 1
- 1
tests/tokenizer/tags.mwtest View File

@@ -470,7 +470,7 @@ output: [TemplateOpen(), Text(text="t1"), TemplateClose(), TagOpenOpen(), Text(t
name: unparsable_attributed name: unparsable_attributed
label: a tag that should not be put through the normal parser; parsed attributes label: a tag that should not be put through the normal parser; parsed attributes
input: "{{t1}}<nowiki attr=val attr2="{{val2}}">{{t2}}</nowiki>{{t3}}" input: "{{t1}}<nowiki attr=val attr2="{{val2}}">{{t2}}</nowiki>{{t3}}"
output: [TemplateOpen(), Text(text=u't1'), TemplateClose(), TagOpenOpen(), Text(text="nowiki"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="attr"), TagAttrEquals(), Text(text="val"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="attr2"), TagAttrEquals(), TagAttrQuote(), TemplateOpen(), Text(text="val2"), TemplateClose(), TagCloseOpen(padding=""), Text(text="{{t2}}"), TagOpenClose(), Text(text="nowiki"), TagCloseClose(), TemplateOpen(), Text(text="t3"), TemplateClose()]
output: [TemplateOpen(), Text(text="t1"), TemplateClose(), TagOpenOpen(), Text(text="nowiki"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="attr"), TagAttrEquals(), Text(text="val"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="attr2"), TagAttrEquals(), TagAttrQuote(), TemplateOpen(), Text(text="val2"), TemplateClose(), TagCloseOpen(padding=""), Text(text="{{t2}}"), TagOpenClose(), Text(text="nowiki"), TagCloseClose(), TemplateOpen(), Text(text="t3"), TemplateClose()]


--- ---




Loading…
Cancel
Save