| @@ -1,5 +1,6 @@ | |||
| v0.6 (unreleased): | |||
| - Fixed manual construction of Node objects, previously unsupported. (#214) | |||
| - Fixed Wikicode transformation methods (replace(), remove(), etc.) when passed | |||
| an empty section as an argument. (#212) | |||
| @@ -7,6 +7,8 @@ v0.6 | |||
| Unreleased | |||
| (`changes <https://github.com/earwig/mwparserfromhell/compare/v0.5.2...develop>`__): | |||
| - Fixed manual construction of Node objects, previously unsupported. | |||
| (`#214 <https://github.com/earwig/mwparserfromhell/issues/214>`_) | |||
| - Fixed :class:`.Wikicode` transformation methods (:meth:`.Wikicode.replace`, | |||
| :meth:`.Wikicode.remove`, etc.) when passed an empty section as an argument. | |||
| (`#212 <https://github.com/earwig/mwparserfromhell/issues/212>`_) | |||
| @@ -1,6 +1,6 @@ | |||
| # -*- coding: utf-8 -*- | |||
| # | |||
| # Copyright (C) 2012-2016 Ben Kurtovic <ben.kurtovic@gmail.com> | |||
| # Copyright (C) 2012-2019 Ben Kurtovic <ben.kurtovic@gmail.com> | |||
| # | |||
| # Permission is hereby granted, free of charge, to any person obtaining a copy | |||
| # of this software and associated documentation files (the "Software"), to deal | |||
| @@ -33,8 +33,8 @@ class Argument(Node): | |||
| def __init__(self, name, default=None): | |||
| super(Argument, self).__init__() | |||
| self._name = name | |||
| self._default = default | |||
| self.name = name | |||
| self.default = default | |||
| def __unicode__(self): | |||
| start = "{{{" + str(self.name) | |||
| @@ -1,6 +1,6 @@ | |||
| # -*- coding: utf-8 -*- | |||
| # | |||
| # Copyright (C) 2012-2016 Ben Kurtovic <ben.kurtovic@gmail.com> | |||
| # Copyright (C) 2012-2019 Ben Kurtovic <ben.kurtovic@gmail.com> | |||
| # | |||
| # Permission is hereby granted, free of charge, to any person obtaining a copy | |||
| # of this software and associated documentation files (the "Software"), to deal | |||
| @@ -32,10 +32,10 @@ class Comment(Node): | |||
| def __init__(self, contents): | |||
| super(Comment, self).__init__() | |||
| self._contents = contents | |||
| self.contents = contents | |||
| def __unicode__(self): | |||
| return "<!--" + str(self.contents) + "-->" | |||
| return "<!--" + self.contents + "-->" | |||
| @property | |||
| def contents(self): | |||
| @@ -1,6 +1,6 @@ | |||
| # -*- coding: utf-8 -*- | |||
| # | |||
| # Copyright (C) 2012-2016 Ben Kurtovic <ben.kurtovic@gmail.com> | |||
| # Copyright (C) 2012-2019 Ben Kurtovic <ben.kurtovic@gmail.com> | |||
| # | |||
| # Permission is hereby granted, free of charge, to any person obtaining a copy | |||
| # of this software and associated documentation files (the "Software"), to deal | |||
| @@ -33,9 +33,9 @@ class ExternalLink(Node): | |||
| def __init__(self, url, title=None, brackets=True): | |||
| super(ExternalLink, self).__init__() | |||
| self._url = url | |||
| self._title = title | |||
| self._brackets = brackets | |||
| self.url = url | |||
| self.title = title | |||
| self.brackets = brackets | |||
| def __unicode__(self): | |||
| if self.brackets: | |||
| @@ -1,6 +1,6 @@ | |||
| # -*- coding: utf-8 -*- | |||
| # | |||
| # Copyright (C) 2012-2016 Ben Kurtovic <ben.kurtovic@gmail.com> | |||
| # Copyright (C) 2012-2019 Ben Kurtovic <ben.kurtovic@gmail.com> | |||
| # | |||
| # Permission is hereby granted, free of charge, to any person obtaining a copy | |||
| # of this software and associated documentation files (the "Software"), to deal | |||
| @@ -37,16 +37,15 @@ class Attribute(StringMixIn): | |||
| """ | |||
| def __init__(self, name, value=None, quotes='"', pad_first=" ", | |||
| pad_before_eq="", pad_after_eq="", check_quotes=True): | |||
| pad_before_eq="", pad_after_eq=""): | |||
| super(Attribute, self).__init__() | |||
| if check_quotes and not quotes and self._value_needs_quotes(value): | |||
| raise ValueError("given value {!r} requires quotes".format(value)) | |||
| self._name = name | |||
| self._value = value | |||
| self._quotes = quotes | |||
| self._pad_first = pad_first | |||
| self._pad_before_eq = pad_before_eq | |||
| self._pad_after_eq = pad_after_eq | |||
| self.name = name | |||
| self._quotes = None | |||
| self.value = value | |||
| self.quotes = quotes | |||
| self.pad_first = pad_first | |||
| self.pad_before_eq = pad_before_eq | |||
| self.pad_after_eq = pad_after_eq | |||
| def __unicode__(self): | |||
| result = self.pad_first + str(self.name) + self.pad_before_eq | |||
| @@ -59,10 +58,17 @@ class Attribute(StringMixIn): | |||
| @staticmethod | |||
| def _value_needs_quotes(val): | |||
| """Return the preferred quotes for the given value, or None.""" | |||
| if val and any(char.isspace() for char in val): | |||
| return ('"' in val and "'" in val) or ("'" if '"' in val else '"') | |||
| return None | |||
| """Return valid quotes for the given value, or None if unneeded.""" | |||
| if not val: | |||
| return None | |||
| val = "".join(str(node) for node in val.filter_text(recursive=False)) | |||
| if not any(char.isspace() for char in val): | |||
| return None | |||
| if "'" in val and '"' not in val: | |||
| return '"' | |||
| if '"' in val and "'" not in val: | |||
| return "'" | |||
| return "\"'" # Either acceptable, " preferred over ' | |||
| def _set_padding(self, attr, value): | |||
| """Setter for the value of a padding attribute.""" | |||
| @@ -123,8 +129,8 @@ class Attribute(StringMixIn): | |||
| else: | |||
| code = parse_anything(newval) | |||
| quotes = self._value_needs_quotes(code) | |||
| if quotes in ['"', "'"] or (quotes is True and not self.quotes): | |||
| self._quotes = quotes | |||
| if quotes and (not self.quotes or self.quotes not in quotes): | |||
| self._quotes = quotes[0] | |||
| self._value = code | |||
| @quotes.setter | |||
| @@ -1,6 +1,6 @@ | |||
| # -*- coding: utf-8 -*- | |||
| # | |||
| # Copyright (C) 2012-2016 Ben Kurtovic <ben.kurtovic@gmail.com> | |||
| # Copyright (C) 2012-2019 Ben Kurtovic <ben.kurtovic@gmail.com> | |||
| # | |||
| # Permission is hereby granted, free of charge, to any person obtaining a copy | |||
| # of this software and associated documentation files (the "Software"), to deal | |||
| @@ -40,11 +40,9 @@ class Parameter(StringMixIn): | |||
| def __init__(self, name, value, showkey=True): | |||
| super(Parameter, self).__init__() | |||
| if not showkey and not self.can_hide_key(name): | |||
| raise ValueError("key {!r} cannot be hidden".format(name)) | |||
| self._name = name | |||
| self._value = value | |||
| self._showkey = showkey | |||
| self.name = name | |||
| self.value = value | |||
| self.showkey = showkey | |||
| def __unicode__(self): | |||
| if self.showkey: | |||
| @@ -83,5 +81,6 @@ class Parameter(StringMixIn): | |||
| def showkey(self, newval): | |||
| newval = bool(newval) | |||
| if not newval and not self.can_hide_key(self.name): | |||
| raise ValueError("parameter key cannot be hidden") | |||
| raise ValueError("parameter key {!r} cannot be hidden".format( | |||
| self.name)) | |||
| self._showkey = newval | |||
| @@ -1,6 +1,6 @@ | |||
| # -*- coding: utf-8 -*- | |||
| # | |||
| # Copyright (C) 2012-2016 Ben Kurtovic <ben.kurtovic@gmail.com> | |||
| # Copyright (C) 2012-2019 Ben Kurtovic <ben.kurtovic@gmail.com> | |||
| # | |||
| # Permission is hereby granted, free of charge, to any person obtaining a copy | |||
| # of this software and associated documentation files (the "Software"), to deal | |||
| @@ -33,8 +33,8 @@ class Heading(Node): | |||
| def __init__(self, title, level): | |||
| super(Heading, self).__init__() | |||
| self._title = title | |||
| self._level = level | |||
| self.title = title | |||
| self.level = level | |||
| def __unicode__(self): | |||
| return ("=" * self.level) + str(self.title) + ("=" * self.level) | |||
| @@ -1,6 +1,6 @@ | |||
| # -*- coding: utf-8 -*- | |||
| # | |||
| # Copyright (C) 2012-2016 Ben Kurtovic <ben.kurtovic@gmail.com> | |||
| # Copyright (C) 2012-2019 Ben Kurtovic <ben.kurtovic@gmail.com> | |||
| # | |||
| # Permission is hereby granted, free of charge, to any person obtaining a copy | |||
| # of this software and associated documentation files (the "Software"), to deal | |||
| @@ -38,28 +38,20 @@ class Tag(Node): | |||
| closing_tag=None, wiki_style_separator=None, | |||
| closing_wiki_markup=None): | |||
| super(Tag, self).__init__() | |||
| self._tag = tag | |||
| if contents is None and not self_closing: | |||
| self._contents = parse_anything("") | |||
| else: | |||
| self._contents = contents | |||
| self.tag = tag | |||
| self.contents = contents | |||
| self._attrs = attrs if attrs else [] | |||
| self._wiki_markup = wiki_markup | |||
| self._self_closing = self_closing | |||
| self._invalid = invalid | |||
| self._implicit = implicit | |||
| self._padding = padding | |||
| if closing_tag: | |||
| self._closing_tag = closing_tag | |||
| else: | |||
| self._closing_tag = tag | |||
| self._wiki_style_separator = wiki_style_separator | |||
| self._closing_wiki_markup = None | |||
| self.wiki_markup = wiki_markup | |||
| self.self_closing = self_closing | |||
| self.invalid = invalid | |||
| self.implicit = implicit | |||
| self.padding = padding | |||
| if closing_tag is not None: | |||
| self.closing_tag = closing_tag | |||
| self.wiki_style_separator = wiki_style_separator | |||
| if closing_wiki_markup is not None: | |||
| self._closing_wiki_markup = closing_wiki_markup | |||
| elif wiki_markup and not self_closing: | |||
| self._closing_wiki_markup = wiki_markup | |||
| else: | |||
| self._closing_wiki_markup = None | |||
| self.closing_wiki_markup = closing_wiki_markup | |||
| def __unicode__(self): | |||
| if self.wiki_markup: | |||
| @@ -69,10 +61,10 @@ class Tag(Node): | |||
| attrs = "" | |||
| padding = self.padding or "" | |||
| separator = self.wiki_style_separator or "" | |||
| close = self.closing_wiki_markup or "" | |||
| if self.self_closing: | |||
| return self.wiki_markup + attrs + padding + separator | |||
| else: | |||
| close = self.closing_wiki_markup or "" | |||
| return self.wiki_markup + attrs + padding + separator + \ | |||
| str(self.contents) + close | |||
| @@ -93,10 +85,10 @@ class Tag(Node): | |||
| yield attr.name | |||
| if attr.value is not None: | |||
| yield attr.value | |||
| if self.contents: | |||
| if not self.self_closing: | |||
| yield self.contents | |||
| if not self.self_closing and not self.wiki_markup and self.closing_tag: | |||
| yield self.closing_tag | |||
| if not self.wiki_markup and self.closing_tag: | |||
| yield self.closing_tag | |||
| def __strip__(self, **kwargs): | |||
| if self.contents and is_visible(self.tag): | |||
| @@ -1,6 +1,6 @@ | |||
| # -*- coding: utf-8 -*- | |||
| # | |||
| # Copyright (C) 2012-2017 Ben Kurtovic <ben.kurtovic@gmail.com> | |||
| # Copyright (C) 2012-2019 Ben Kurtovic <ben.kurtovic@gmail.com> | |||
| # | |||
| # Permission is hereby granted, free of charge, to any person obtaining a copy | |||
| # of this software and associated documentation files (the "Software"), to deal | |||
| @@ -38,7 +38,7 @@ class Template(Node): | |||
| def __init__(self, name, params=None): | |||
| super(Template, self).__init__() | |||
| self._name = name | |||
| self.name = name | |||
| if params: | |||
| self._params = params | |||
| else: | |||
| @@ -108,7 +108,7 @@ class Template(Node): | |||
| def _blank_param_value(value): | |||
| """Remove the content from *value* while keeping its whitespace. | |||
| Replace *value*\ 's nodes with two text nodes, the first containing | |||
| Replace *value*\\ 's nodes with two text nodes, the first containing | |||
| whitespace from before its content and the second containing whitespace | |||
| from after its content. | |||
| """ | |||
| @@ -1,6 +1,6 @@ | |||
| # -*- coding: utf-8 -*- | |||
| # | |||
| # Copyright (C) 2012-2016 Ben Kurtovic <ben.kurtovic@gmail.com> | |||
| # Copyright (C) 2012-2019 Ben Kurtovic <ben.kurtovic@gmail.com> | |||
| # | |||
| # Permission is hereby granted, free of charge, to any person obtaining a copy | |||
| # of this software and associated documentation files (the "Software"), to deal | |||
| @@ -32,7 +32,7 @@ class Text(Node): | |||
| def __init__(self, value): | |||
| super(Text, self).__init__() | |||
| self._value = value | |||
| self.value = value | |||
| def __unicode__(self): | |||
| return self.value | |||
| @@ -1,6 +1,6 @@ | |||
| # -*- coding: utf-8 -*- | |||
| # | |||
| # Copyright (C) 2012-2016 Ben Kurtovic <ben.kurtovic@gmail.com> | |||
| # Copyright (C) 2012-2019 Ben Kurtovic <ben.kurtovic@gmail.com> | |||
| # | |||
| # Permission is hereby granted, free of charge, to any person obtaining a copy | |||
| # of this software and associated documentation files (the "Software"), to deal | |||
| @@ -33,8 +33,8 @@ class Wikilink(Node): | |||
| def __init__(self, title, text=None): | |||
| super(Wikilink, self).__init__() | |||
| self._title = title | |||
| self._text = text | |||
| self.title = title | |||
| self.text = text | |||
| def __unicode__(self): | |||
| if self.text is not None: | |||
| @@ -48,7 +48,7 @@ def _add_handler(token_type): | |||
| class Builder(object): | |||
| """Builds a tree of nodes out of a sequence of tokens. | |||
| To use, pass a list of :class:`.Token`\ s to the :meth:`build` method. The | |||
| To use, pass a list of :class:`.Token`\\ s to the :meth:`build` method. The | |||
| list will be exhausted as it is parsed and a :class:`.Wikicode` object | |||
| containing the node tree will be returned. | |||
| """ | |||
| @@ -237,8 +237,7 @@ class Builder(object): | |||
| else: | |||
| name, value = self._pop(), None | |||
| return Attribute(name, value, quotes, start.pad_first, | |||
| start.pad_before_eq, start.pad_after_eq, | |||
| check_quotes=False) | |||
| start.pad_before_eq, start.pad_after_eq) | |||
| else: | |||
| self._write(self._handle_token(token)) | |||
| raise ParserError("_handle_attribute() missed a close token") | |||
| @@ -1,6 +1,6 @@ | |||
| # -*- coding: utf-8 -*- | |||
| # | |||
| # Copyright (C) 2012-2016 Ben Kurtovic <ben.kurtovic@gmail.com> | |||
| # Copyright (C) 2012-2019 Ben Kurtovic <ben.kurtovic@gmail.com> | |||
| # | |||
| # Permission is hereby granted, free of charge, to any person obtaining a copy | |||
| # of this software and associated documentation files (the "Software"), to deal | |||
| @@ -70,5 +70,5 @@ def parse_anything(value, context=0, skip_style_tags=False): | |||
| nodelist += parse_anything(item, context, skip_style_tags).nodes | |||
| return Wikicode(nodelist) | |||
| except TypeError: | |||
| error = "Needs string, Node, Wikicode, int, None, or iterable of these, but got {0}: {1}" | |||
| error = "Needs string, Node, Wikicode, file, int, None, or iterable of these, but got {0}: {1}" | |||
| raise ValueError(error.format(type(value).__name__, value)) | |||
| @@ -1,6 +1,6 @@ | |||
| # -*- coding: utf-8 -*- | |||
| # | |||
| # Copyright (C) 2012-2016 Ben Kurtovic <ben.kurtovic@gmail.com> | |||
| # Copyright (C) 2012-2019 Ben Kurtovic <ben.kurtovic@gmail.com> | |||
| # | |||
| # Permission is hereby granted, free of charge, to any person obtaining a copy | |||
| # of this software and associated documentation files (the "Software"), to deal | |||
| @@ -67,7 +67,7 @@ class TreeEqualityTestCase(TestCase): | |||
| def assertCommentNodeEqual(self, expected, actual): | |||
| """Assert that two Comment nodes have the same data.""" | |||
| self.assertWikicodeEqual(expected.contents, actual.contents) | |||
| self.assertEqual(expected.contents, actual.contents) | |||
| def assertHeadingNodeEqual(self, expected, actual): | |||
| """Assert that two Heading nodes have the same data.""" | |||
| @@ -1,6 +1,6 @@ | |||
| # -*- coding: utf-8 -*- | |||
| # | |||
| # Copyright (C) 2012-2016 Ben Kurtovic <ben.kurtovic@gmail.com> | |||
| # Copyright (C) 2012-2019 Ben Kurtovic <ben.kurtovic@gmail.com> | |||
| # | |||
| # Permission is hereby granted, free of charge, to any person obtaining a copy | |||
| # of this software and associated documentation files (the "Software"), to deal | |||
| @@ -1,6 +1,6 @@ | |||
| # -*- coding: utf-8 -*- | |||
| # | |||
| # Copyright (C) 2012-2016 Ben Kurtovic <ben.kurtovic@gmail.com> | |||
| # Copyright (C) 2012-2019 Ben Kurtovic <ben.kurtovic@gmail.com> | |||
| # | |||
| # Permission is hereby granted, free of charge, to any person obtaining a copy | |||
| # of this software and associated documentation files (the "Software"), to deal | |||
| @@ -232,11 +232,11 @@ class TestBuilder(TreeEqualityTestCase): | |||
| tests = [ | |||
| ([tokens.CommentStart(), tokens.Text(text="foobar"), | |||
| tokens.CommentEnd()], | |||
| wrap([Comment(wraptext("foobar"))])), | |||
| wrap([Comment("foobar")])), | |||
| ([tokens.CommentStart(), tokens.Text(text="spam"), | |||
| tokens.Text(text="eggs"), tokens.CommentEnd()], | |||
| wrap([Comment(wraptext("spam", "eggs"))])), | |||
| wrap([Comment("spameggs")])), | |||
| ] | |||
| for test, valid in tests: | |||
| self.assertWikicodeEqual(valid, self.builder.build(test)) | |||
| @@ -412,7 +412,7 @@ class TestBuilder(TreeEqualityTestCase): | |||
| wraptext("c"), params=[Parameter(wraptext("1"), wrap([Wikilink( | |||
| wraptext("d")), Argument(wraptext("e"))]), showkey=False)])]), | |||
| showkey=False)]), Wikilink(wraptext("f"), wrap([Argument(wraptext( | |||
| "g")), Comment(wraptext("h"))])), Template(wraptext("i"), params=[ | |||
| "g")), Comment("h")])), Template(wraptext("i"), params=[ | |||
| Parameter(wraptext("j"), wrap([HTMLEntity("nbsp", | |||
| named=True)]))])]) | |||
| self.assertWikicodeEqual(valid, self.builder.build(test)) | |||