| @@ -1,6 +1,7 @@ | |||
| language: python | |||
| python: | |||
| - "2.7" | |||
| - "3.2" | |||
| - "3.3" | |||
| install: python setup.py build | |||
| script: python setup.py test -q | |||
| @@ -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): | |||
| - Fixed a parser bug involving URLs nested inside other markup. | |||
| @@ -1,6 +1,17 @@ | |||
| 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 | |||
| ------ | |||
| @@ -31,7 +31,7 @@ from __future__ import unicode_literals | |||
| __author__ = "Ben Kurtovic" | |||
| __copyright__ = "Copyright (C) 2012, 2013 Ben Kurtovic" | |||
| __license__ = "MIT License" | |||
| __version__ = "0.3.1" | |||
| __version__ = "0.3.2" | |||
| __email__ = "ben.kurtovic@verizon.net" | |||
| from . import (compat, definitions, nodes, parser, smart_list, string_mixin, | |||
| @@ -10,7 +10,8 @@ types are meant to be imported directly from within the parser's modules. | |||
| import sys | |||
| py3k = sys.version_info[0] == 3 | |||
| py3k = sys.version_info.major == 3 | |||
| py32 = py3k and sys.version_info.minor == 2 | |||
| if py3k: | |||
| bytes = bytes | |||
| @@ -150,6 +150,16 @@ class Template(Node): | |||
| return False | |||
| 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 | |||
| def name(self): | |||
| """The name of the template, as a :py:class:`~.Wikicode` object.""" | |||
| @@ -180,7 +190,8 @@ class Template(Node): | |||
| return True | |||
| 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`." | |||
| def get(self, name): | |||
| @@ -280,8 +291,12 @@ class Template(Node): | |||
| self.params.append(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 | |||
| 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 | |||
| 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 | |||
| to_remove = [] | |||
| for i, param in enumerate(self.params): | |||
| @@ -304,15 +321,15 @@ class Template(Node): | |||
| self._blank_param_value(param.value) | |||
| keep_field = False | |||
| else: | |||
| to_remove.append(param) | |||
| to_remove.append(i) | |||
| else: | |||
| if self._remove_without_field(param, i): | |||
| to_remove.append(param) | |||
| to_remove.append(i) | |||
| else: | |||
| self._blank_param_value(param.value) | |||
| if not removed: | |||
| removed = True | |||
| if not removed: | |||
| raise ValueError(name) | |||
| for param in to_remove: | |||
| self.params.remove(param) | |||
| for i in reversed(to_remove): | |||
| self.params.pop(i) | |||
| @@ -27,7 +27,7 @@ interface for the ``unicode`` type (``str`` on py3k) in a dynamic manner. | |||
| from __future__ import unicode_literals | |||
| from .compat import py3k, str | |||
| from .compat import py3k, py32, str | |||
| __all__ = ["StringMixIn"] | |||
| @@ -125,7 +125,7 @@ class StringMixIn(object): | |||
| def capitalize(self): | |||
| return self.__unicode__().capitalize() | |||
| if py3k: | |||
| if py3k and not py32: | |||
| @inheritdoc | |||
| def casefold(self): | |||
| return self.__unicode__().casefold() | |||
| @@ -288,7 +288,7 @@ class StringMixIn(object): | |||
| def rpartition(self, sep): | |||
| return self.__unicode__().rpartition(sep) | |||
| if py3k: | |||
| if py3k and not py32: | |||
| @inheritdoc | |||
| def rsplit(self, sep=None, maxsplit=None): | |||
| kwargs = {} | |||
| @@ -310,7 +310,7 @@ class StringMixIn(object): | |||
| def rstrip(self, chars=None): | |||
| return self.__unicode__().rstrip(chars) | |||
| if py3k: | |||
| if py3k and not py32: | |||
| @inheritdoc | |||
| def split(self, sep=None, maxsplit=None): | |||
| kwargs = {} | |||
| @@ -54,6 +54,7 @@ setup( | |||
| "Operating System :: OS Independent", | |||
| "Programming Language :: Python :: 2.7", | |||
| "Programming Language :: Python :: 3", | |||
| "Programming Language :: Python :: 3.2", | |||
| "Programming Language :: Python :: 3.3", | |||
| "Topic :: Text Processing :: Markup" | |||
| ], | |||
| @@ -25,7 +25,7 @@ from sys import getdefaultencoding | |||
| from types import GeneratorType | |||
| import unittest | |||
| from mwparserfromhell.compat import bytes, py3k, str | |||
| from mwparserfromhell.compat import bytes, py3k, py32, str | |||
| from mwparserfromhell.string_mixin import StringMixIn | |||
| from .compat import range | |||
| @@ -52,8 +52,10 @@ class TestStringMixIn(unittest.TestCase): | |||
| "rsplit", "rstrip", "split", "splitlines", "startswith", "strip", | |||
| "swapcase", "title", "translate", "upper", "zfill"] | |||
| if py3k: | |||
| methods.extend(["casefold", "format_map", "isidentifier", | |||
| "isprintable", "maketrans"]) | |||
| if not py32: | |||
| methods.append("casefold") | |||
| methods.extend(["format_map", "isidentifier", "isprintable", | |||
| "maketrans"]) | |||
| else: | |||
| methods.append("decode") | |||
| for meth in methods: | |||
| @@ -325,7 +327,7 @@ class TestStringMixIn(unittest.TestCase): | |||
| self.assertEqual("", str15.lower()) | |||
| self.assertEqual("foobar", str16.lower()) | |||
| self.assertEqual("ß", str22.lower()) | |||
| if py3k: | |||
| if py3k and not py32: | |||
| self.assertEqual("", str15.casefold()) | |||
| self.assertEqual("foobar", str16.casefold()) | |||
| self.assertEqual("ss", str22.casefold()) | |||
| @@ -316,12 +316,12 @@ class TestTemplate(TreeEqualityTestCase): | |||
| def test_remove(self): | |||
| """test Template.remove()""" | |||
| 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"), [ | |||
| pgens(" a", "b"), pgens("b", "c"), pgens("a ", "d")]) | |||
| node6 = Template(wraptext("foo"), [ | |||
| @@ -334,6 +334,44 @@ class TestTemplate(TreeEqualityTestCase): | |||
| pgens("1 ", "a"), pgenh("1", "b"), pgenh("2", "c")]) | |||
| node10 = Template(wraptext("foo"), [ | |||
| 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("abc") | |||
| @@ -346,6 +384,22 @@ class TestTemplate(TreeEqualityTestCase): | |||
| node8.remove(1, keep_field=False) | |||
| node9.remove(1, keep_field=True) | |||
| 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, "a") | |||
| @@ -359,6 +413,22 @@ class TestTemplate(TreeEqualityTestCase): | |||
| self.assertEqual("{{foo|2=c}}", node8) | |||
| self.assertEqual("{{foo||c}}", node9) | |||
| 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__": | |||
| unittest.main(verbosity=2) | |||
| @@ -470,7 +470,7 @@ output: [TemplateOpen(), Text(text="t1"), TemplateClose(), TagOpenOpen(), Text(t | |||
| name: unparsable_attributed | |||
| label: a tag that should not be put through the normal parser; parsed attributes | |||
| 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()] | |||
| --- | |||