| @@ -32,5 +32,11 @@ __license__ = "MIT License" | |||||
| __version__ = "0.1.dev" | __version__ = "0.1.dev" | ||||
| __email__ = "ben.kurtovic@verizon.net" | __email__ = "ben.kurtovic@verizon.net" | ||||
| from mwparserfromhell import parameter, parser, template | |||||
| from mwparserfromhell.node import Node | |||||
| from mwparserfromhell.parameter import Parameter | |||||
| from mwparserfromhell.parser import Parser | from mwparserfromhell.parser import Parser | ||||
| from mwparserfromhell.template import Template | |||||
| from mwparserfromhell.text import Text | |||||
| from mwparserfromhell.wikicode import Wikicode | |||||
| parse = Parser().parse | |||||
| @@ -0,0 +1,26 @@ | |||||
| # -*- coding: utf-8 -*- | |||||
| # | |||||
| # Copyright (C) 2012 by Ben Kurtovic <ben.kurtovic@verizon.net> | |||||
| # | |||||
| # Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
| # of this software and associated documentation files (the "Software"), to deal | |||||
| # in the Software without restriction, including without limitation the rights | |||||
| # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
| # copies of the Software, and to permit persons to whom the Software is | |||||
| # furnished to do so, subject to the following conditions: | |||||
| # | |||||
| # The above copyright notice and this permission notice shall be included in | |||||
| # all copies or substantial portions of the Software. | |||||
| # | |||||
| # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
| # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
| # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
| # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
| # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
| # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||||
| # SOFTWARE. | |||||
| __all__ = ["Node"] | |||||
| class Node(object): | |||||
| pass | |||||
| @@ -20,108 +20,20 @@ | |||||
| # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
| # SOFTWARE. | # SOFTWARE. | ||||
| from mwparserfromhell.template import Template | |||||
| from mwparserfromhell.string_mixin import StringMixin | |||||
| __all__ = ["Parameter"] | __all__ = ["Parameter"] | ||||
| class Parameter(object): | |||||
| def __init__(self, name, value, templates=None): | |||||
| class Parameter(StringMixin): | |||||
| def __init__(self, name, value=None, showkey=True): | |||||
| self._name = name | self._name = name | ||||
| self._value = value | self._value = value | ||||
| if templates: | |||||
| self._templates = templates | |||||
| else: | |||||
| self._templates = [] | |||||
| self._showkey = showkey | |||||
| def __repr__(self): | |||||
| return repr(self.value) | |||||
| def __str__(self): | |||||
| return self.value | |||||
| def __lt__(self, other): | |||||
| if isinstance(other, Parameter): | |||||
| return self.value < other.value | |||||
| return self.value < other | |||||
| def __le__(self, other): | |||||
| if isinstance(other, Parameter): | |||||
| return self.value <= other.value | |||||
| return self.value <= other | |||||
| def __eq__(self, other): | |||||
| if isinstance(other, Parameter): | |||||
| return (self.name == other.name and self.value == other.value and | |||||
| self.templates == other.templates) | |||||
| return self.value == other | |||||
| def __ne__(self, other): | |||||
| if isinstance(other, Parameter): | |||||
| return (self.name != other.name or self.value != other.value or | |||||
| self.templates != other.templates) | |||||
| return self.value != other | |||||
| def __gt__(self, other): | |||||
| if isinstance(other, Parameter): | |||||
| return self.value > other.value | |||||
| return self.value > other | |||||
| def __ge__(self, other): | |||||
| if isinstance(other, Parameter): | |||||
| return self.value >= other.value | |||||
| return self.value >= other | |||||
| def __nonzero__(self): | |||||
| return bool(self.value) | |||||
| def __len__(self): | |||||
| return len(self.value) | |||||
| def __iter__(self): | |||||
| for char in self.value: | |||||
| yield char | |||||
| def __getitem__(self, key): | |||||
| return self.value[key] | |||||
| def __contains__(self, item): | |||||
| return item in self.value or item in self.templates | |||||
| def __add__(self, other): | |||||
| if isinstance(other, Parameter): | |||||
| return Parameter(self.name, self.value + other.value, | |||||
| self.templates + other.templates) | |||||
| if isinstance(other, Template): | |||||
| return Parameter(self.name, self.value + other.render(), | |||||
| self.templates + [other]) | |||||
| return self.value + other | |||||
| def __radd__(self, other): | |||||
| if isinstance(other, Template): | |||||
| return Template(other.name, other.params + [self]) | |||||
| return other + self.value | |||||
| def __iadd__(self, other): | |||||
| if isinstance(other, Parameter): | |||||
| self.value += other.value | |||||
| self.templates += other.templates | |||||
| elif isinstance(other, Template): | |||||
| self.value += other.render() | |||||
| self.templates.append(other) | |||||
| else: | |||||
| self.value += other | |||||
| return self | |||||
| def __mul__(self, other): | |||||
| return Parameter(self.name, self.value * other, self.templates * other) | |||||
| def __rmul__(self, other): | |||||
| return Parameter(self.name, other * self.value, other * self.templates) | |||||
| def __imul__(self, other): | |||||
| self.value *= other | |||||
| self.templates *= other | |||||
| return self | |||||
| def __unicode__(self): | |||||
| if self.showkey: | |||||
| return unicode(self.name) + "=" + unicode(self.value) | |||||
| return unicode(self.value) | |||||
| @property | @property | ||||
| def name(self): | def name(self): | ||||
| @@ -132,5 +44,5 @@ class Parameter(object): | |||||
| return self._value | return self._value | ||||
| @property | @property | ||||
| def templates(self): | |||||
| return self._templates | |||||
| def showkey(self): | |||||
| return self._showkey | |||||
| @@ -22,15 +22,28 @@ | |||||
| from mwparserfromhell.parameter import Parameter | from mwparserfromhell.parameter import Parameter | ||||
| from mwparserfromhell.template import Template | from mwparserfromhell.template import Template | ||||
| from mwparserfromhell.text import Text | |||||
| from mwparserfromhell.wikicode import Wikicode | |||||
| __all__ = ["Parser"] | __all__ = ["Parser"] | ||||
| class Parser(object): | class Parser(object): | ||||
| def _tokenize(self, text): | def _tokenize(self, text): | ||||
| return text | return text | ||||
| def parse(self, text): | def parse(self, text): | ||||
| tokens = self._tokenize(text) | |||||
| params = [Parameter("1", "bar"), Parameter("2", "baz")] | |||||
| templates = [Template(name="foo", params=params)] | |||||
| return templates | |||||
| text = u"This is a {{test}} message with a {{template|with|foo={{params}}}}." | |||||
| node1 = Text(u"This is a ") | |||||
| node2 = Template(Wikicode([Text(u"test")])) | |||||
| node3 = Text(u" message with a ") | |||||
| node4_param1_name = Wikicode([Text(u"1")]) | |||||
| node4_param1_value = Wikicode([Text(u"with")]) | |||||
| node4_param1 = Parameter(node4_param1_name, node4_param1_value, showkey=False) | |||||
| node4_param2_name = Wikicode([Text(u"foo")]) | |||||
| node4_param2_value = Wikicode([Template(Wikicode([Text(u"params")]))]) | |||||
| node4_param2 = Parameter(node4_param2_name, node4_param2_value, showkey=True) | |||||
| node4 = Template(Wikicode([Text(u"template")]), [node4_param1, node4_param2]) | |||||
| node5 = Text(u".") | |||||
| parsed = Wikicode([node1, node2, node3, node4, node5]) | |||||
| return parsed | |||||
| @@ -0,0 +1,63 @@ | |||||
| # -*- coding: utf-8 -*- | |||||
| # | |||||
| # Copyright (C) 2012 by Ben Kurtovic <ben.kurtovic@verizon.net> | |||||
| # | |||||
| # Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
| # of this software and associated documentation files (the "Software"), to deal | |||||
| # in the Software without reunicodeiction, including without limitation the rights | |||||
| # to use, copy, modify, merge, publish, diunicodeibute, sublicense, and/or sell | |||||
| # copies of the Software, and to permit persons to whom the Software is | |||||
| # furnished to do so, subject to the following conditions: | |||||
| # | |||||
| # The above copyright notice and this permission notice shall be included in | |||||
| # all copies or substantial portions of the Software. | |||||
| # | |||||
| # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
| # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
| # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
| # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
| # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
| # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||||
| # SOFTWARE. | |||||
| __all__ = ["StringMixin"] | |||||
| class StringMixin(object): | |||||
| def __str__(self): | |||||
| return str(unicode(self)) | |||||
| def __repr__(self): | |||||
| return repr(unicode(self)) | |||||
| def __lt__(self, other): | |||||
| if isinstance(other, unicodeingMixin): | |||||
| return unicode(self) < unicode(other) | |||||
| return unicode(self) < other | |||||
| def __le__(self, other): | |||||
| if isinstance(other, unicodeingMixin): | |||||
| return unicode(self) <= unicode(other) | |||||
| return unicode(self) <= other | |||||
| def __eq__(self, other): | |||||
| if isinstance(other, unicodeingMixin): | |||||
| return unicode(self) == unicode(other) | |||||
| return unicode(self) == other | |||||
| def __ne__(self, other): | |||||
| if isinstance(other, unicodeingMixin): | |||||
| return unicode(self) != unicode(other) | |||||
| return unicode(self) != other | |||||
| def __gt__(self, other): | |||||
| if isinstance(other, unicodeingMixin): | |||||
| return unicode(self) > unicode(other) | |||||
| return unicode(self) > other | |||||
| def __ge__(self, other): | |||||
| if isinstance(other, unicodeingMixin): | |||||
| return unicode(self) >= unicode(other) | |||||
| return unicode(self) >= other | |||||
| def __getitem__(self, index): | |||||
| return unicode(self)[index] | |||||
| @@ -20,51 +20,25 @@ | |||||
| # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
| # SOFTWARE. | # SOFTWARE. | ||||
| from collections import OrderedDict | |||||
| from mwparserfromhell.node import Node | |||||
| from mwparserfromhell.string_mixin import StringMixin | |||||
| __all__ = ["Template"] | __all__ = ["Template"] | ||||
| class Template(object): | |||||
| class Template(Node, StringMixin): | |||||
| def __init__(self, name, params=None): | def __init__(self, name, params=None): | ||||
| self._name = name | self._name = name | ||||
| self._params = OrderedDict() | |||||
| if params: | if params: | ||||
| for param in params: | |||||
| self._params[param.name] = param | |||||
| def __repr__(self): | |||||
| paramlist = [] | |||||
| for name, param in self._params.iteritems(): | |||||
| paramlist.append('"{0}": "{1}"'.format(name, str(param))) | |||||
| params = "{" + ", ".join(paramlist) + "}" | |||||
| return "Template(name={0}, params={1})".format(self.name, params) | |||||
| def __eq__(self, other): | |||||
| if isinstance(other, Template): | |||||
| return self.name == other.name and self.params == other.params | |||||
| return self.render() == other | |||||
| def __ne__(self, other): | |||||
| if isinstance(other, Template): | |||||
| return self.name != other.name or self.params != other.params | |||||
| return self.render() != other | |||||
| def __getitem__(self, key): | |||||
| try: | |||||
| return self._params[key] | |||||
| except KeyError: # Try lookup by order in param list | |||||
| return self._params.values()[key] | |||||
| self._params = params | |||||
| else: | |||||
| self._params = [] | |||||
| def __setitem__(self, key, value): | |||||
| if isinstance(key, int): | |||||
| if key > len(self._params): | |||||
| raise IndexError("Index is too large") | |||||
| elif key == len(self._params): # Simple addition to the end | |||||
| self._params[key] = value | |||||
| else: # We'll need to rebuild the OrderedDict | |||||
| self._params | |||||
| def __unicode__(self): | |||||
| if self.params: | |||||
| params = u"|".join([unicode(param) for param in self.params]) | |||||
| return "{{" + unicode(self.name) + "|" + params + "}}" | |||||
| else: | else: | ||||
| self._params[key] = value | |||||
| return "{{" + unicode(self.name) + "}}" | |||||
| @property | @property | ||||
| def name(self): | def name(self): | ||||
| @@ -72,13 +46,16 @@ class Template(object): | |||||
| @property | @property | ||||
| def params(self): | def params(self): | ||||
| return self._params.values() | |||||
| return self._params | |||||
| def has_param(self): | |||||
| pass | |||||
| def get_param(self): | |||||
| pass | |||||
| def add_param(self): | |||||
| pass | |||||
| def render(self): | |||||
| params = "" | |||||
| for param in self.params: | |||||
| if param.name.isdigit() and "=" not in param.value: | |||||
| params += "|" + param.value | |||||
| else: | |||||
| params += "|" + param.name + "=" + param.value | |||||
| return "{{" + self.name + params + "}}" | |||||
| def remove_param(self): | |||||
| pass | |||||
| @@ -0,0 +1,37 @@ | |||||
| # -*- coding: utf-8 -*- | |||||
| # | |||||
| # Copyright (C) 2012 by Ben Kurtovic <ben.kurtovic@verizon.net> | |||||
| # | |||||
| # Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
| # of this software and associated documentation files (the "Software"), to deal | |||||
| # in the Software without restriction, including without limitation the rights | |||||
| # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
| # copies of the Software, and to permit persons to whom the Software is | |||||
| # furnished to do so, subject to the following conditions: | |||||
| # | |||||
| # The above copyright notice and this permission notice shall be included in | |||||
| # all copies or substantial portions of the Software. | |||||
| # | |||||
| # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
| # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
| # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
| # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
| # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
| # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||||
| # SOFTWARE. | |||||
| from mwparserfromhell.node import Node | |||||
| from mwparserfromhell.string_mixin import StringMixin | |||||
| __all__ = ["Text"] | |||||
| class Text(Node, StringMixin): | |||||
| def __init__(self, value): | |||||
| self._value = value | |||||
| def __unicode__(self): | |||||
| return unicode(self.value) | |||||
| @property | |||||
| def value(self): | |||||
| return self._value | |||||
| @@ -0,0 +1,171 @@ | |||||
| # -*- coding: utf-8 -*- | |||||
| # | |||||
| # Copyright (C) 2012 by Ben Kurtovic <ben.kurtovic@verizon.net> | |||||
| # | |||||
| # Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
| # of this software and associated documentation files (the "Software"), to deal | |||||
| # in the Software without restriction, including without limitation the rights | |||||
| # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
| # copies of the Software, and to permit persons to whom the Software is | |||||
| # furnished to do so, subject to the following conditions: | |||||
| # | |||||
| # The above copyright notice and this permission notice shall be included in | |||||
| # all copies or substantial portions of the Software. | |||||
| # | |||||
| # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
| # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
| # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
| # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
| # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
| # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||||
| # SOFTWARE. | |||||
| import re | |||||
| import mwparserfromhell | |||||
| from mwparserfromhell.node import Node | |||||
| from mwparserfromhell.string_mixin import StringMixin | |||||
| from mwparserfromhell.template import Template | |||||
| from mwparserfromhell.text import Text | |||||
| __all__ = ["Wikicode"] | |||||
| FLAGS = re.I | re.S | re.U | |||||
| class Wikicode(StringMixin): | |||||
| def __init__(self, nodes): | |||||
| self._nodes = nodes | |||||
| def __unicode__(self): | |||||
| return "".join([unicode(node) for node in self.nodes]) | |||||
| def _nodify(self, value): | |||||
| if isinstance(value, Wikicode): | |||||
| return value.nodes | |||||
| if isinstance(value, Node): | |||||
| return [value] | |||||
| if isinstance(value, str) or isinstance(value, unicode): | |||||
| return mwparserfromhell.parse(value).nodes | |||||
| error = "Needs string, Node, or Wikicode object, but got {0}: {1}" | |||||
| raise ValueError(error.format(type(value), value)) | |||||
| def _get_all_nodes(self, code): | |||||
| for node in code.nodes: | |||||
| yield node | |||||
| if isinstance(node, Template): | |||||
| for child in self._get_all_nodes(node.name): | |||||
| yield child | |||||
| for param in node.params: | |||||
| if param.showkey: | |||||
| for child in self._get_all_nodes(param.name): | |||||
| yield child | |||||
| for child in self._get_all_nodes(param.value): | |||||
| yield child | |||||
| def _show_tree(self, code, lines, marker=None, indent=0): | |||||
| def write(*args): | |||||
| if lines and lines[-1] is marker: # Continue from the last line | |||||
| lines.pop() # Remove the marker | |||||
| last = lines.pop() | |||||
| lines.append(last + " ".join(args)) | |||||
| else: | |||||
| lines.append(" " * indent + " ".join(args)) | |||||
| for node in code.nodes: | |||||
| if isinstance(node, Template): | |||||
| write("{{", ) | |||||
| self._show_tree(node.name, lines, marker, indent + 1) | |||||
| for param in node.params: | |||||
| write(" | ") | |||||
| lines.append(marker) # Continue from this line | |||||
| self._show_tree(param.name, lines, marker, indent + 1) | |||||
| write(" = ") | |||||
| lines.append(marker) # Continue from this line | |||||
| self._show_tree(param.value, lines, marker, indent + 1) | |||||
| write("}}") | |||||
| elif isinstance(node, Text): | |||||
| write(unicode(node)) | |||||
| else: | |||||
| raise NotImplementedError(node) | |||||
| return lines | |||||
| @property | |||||
| def nodes(self): | |||||
| return self._nodes | |||||
| def get(self, index): | |||||
| return self.nodes[index] | |||||
| def set(self, index, value): | |||||
| nodes = self._nodify(value) | |||||
| if len(nodes) > 1: | |||||
| raise ValueError("Cannot coerce multiple nodes into one index") | |||||
| if index >= len(self.nodes) or -1 * index > len(self.nodes): | |||||
| raise IndexError("List assignment index out of range") | |||||
| self.nodex.pop(index) | |||||
| if nodes: | |||||
| self.nodes[index] = nodes[0] | |||||
| def index(self, obj): | |||||
| if obj not in self.nodes: | |||||
| raise ValueError(obj) | |||||
| if isinstance(obj, Node): | |||||
| for i, node in enumerate(self.nodes): | |||||
| if node is obj: | |||||
| return i | |||||
| raise ValueError(obj) | |||||
| return self.nodes.index(obj) | |||||
| def insert(self, index, value, recursive=True): | |||||
| nodes = self._nodify(value) | |||||
| for node in reversed(nodes): | |||||
| self.nodes.insert(index, node) | |||||
| def insert_before(self, obj, value): | |||||
| if obj not in self.nodes: | |||||
| raise KeyError(obj) | |||||
| self.insert(self.index(obj), value) | |||||
| def insert_after(self, obj, value): | |||||
| if obj not in self.nodes: | |||||
| raise KeyError(obj) | |||||
| self.insert(self.index(obj) + 1, value) | |||||
| def append(self, value): | |||||
| nodes = self._nodify(value) | |||||
| for node in nodes: | |||||
| self.nodes.append(node) | |||||
| def remove(self, node): | |||||
| self.nodes.pop(self.index(node)) | |||||
| def ifilter(self, recursive=False, matches=None, flags=FLAGS, | |||||
| forcetype=None): | |||||
| if recursive: | |||||
| nodes = self._get_all_nodes(self) | |||||
| else: | |||||
| nodes = self.nodes | |||||
| for node in nodes: | |||||
| if not forcetype or isinstance(node, forcetype): | |||||
| if not matches or re.search(matches, unicode(node), flags): | |||||
| yield node | |||||
| def ifilter_templates(self, recursive=False, matches=None, flags=FLAGS): | |||||
| return self.filter(recursive, matches, flags, forcetype=Template) | |||||
| def ifilter_text(self, recursive=False, matches=None, flags=FLAGS): | |||||
| return self.filter(recursive, matches, flags, forcetype=Text) | |||||
| def filter(self, recursive=False, matches=None, flags=FLAGS, | |||||
| forcetype=None): | |||||
| return list(self.ifilter(recursive, matches, flags, forcetype)) | |||||
| def filter_templates(self, recursive=False, matches=None, flags=FLAGS): | |||||
| return list(self.ifilter_templates(recursive, matches, flags)) | |||||
| def filter_text(self, recursive=False, matches=None, flags=FLAGS): | |||||
| return list(self.ifilter_text(recursive, matches, flags)) | |||||
| def show_tree(self): | |||||
| marker = object() # Random object we can find with certainty in a list | |||||
| print "\n".join(self._show_tree(self, [], marker)) | |||||