Просмотр исходного кода

Add more type hints (#338)

* Fix formatting

* Add quick mypy config file

* Add type hints: mwparserfromhell.definitions

* Add type hints: mwparserfromhell.wikicode

* Add type hints: mwparserfromhell.nodes._base

* Add type hints: mwparserfromhell.nodes.argument

* Add type hints: mwparserfromhell.nodes.comment

* Add type hints: mwparserfromhell.nodes.external_link

* Add type hints: mwparserfromhell.nodes.heading

* Add type hints: mwparserfromhell.nodes.html_entity

* Add type hints: mwparserfromhell.nodes.tag

* Add type hints: mwparserfromhell.nodes.template

* Add type hints: mwparserfromhell.nodes.text

* Add type hints: mwparserfromhell.nodes.wikilink

* Add type hints: mwparserfromhell.nodes.extras.attribute

* Add type hints: mwparserfromhell.nodes.extras.parameter

* Fix typo in docstring

---------

Co-authored-by: Jakub Klinkovský <1289205+lahwaacz@users.noreply.github.com>
tags/v0.7.0
Jakub Klinkovský 1 год назад
committed by GitHub
Родитель
Сommit
3e9da21a84
Не найден GPG ключ соответствующий данной подписи Идентификатор GPG ключа: B5690EEEBB952194
15 измененных файлов: 533 добавлений и 343 удалений
  1. +9
    -0
      mypy.ini
  2. +6
    -6
      src/mwparserfromhell/definitions.py
  3. +14
    -4
      src/mwparserfromhell/nodes/_base.py
  4. +22
    -12
      src/mwparserfromhell/nodes/argument.py
  5. +6
    -4
      src/mwparserfromhell/nodes/comment.py
  6. +35
    -19
      src/mwparserfromhell/nodes/external_link.py
  7. +58
    -48
      src/mwparserfromhell/nodes/extras/attribute.py
  8. +21
    -16
      src/mwparserfromhell/nodes/extras/parameter.py
  9. +21
    -13
      src/mwparserfromhell/nodes/heading.py
  10. +47
    -38
      src/mwparserfromhell/nodes/html_entity.py
  11. +98
    -85
      src/mwparserfromhell/nodes/tag.py
  12. +62
    -36
      src/mwparserfromhell/nodes/template.py
  13. +17
    -7
      src/mwparserfromhell/nodes/text.py
  14. +22
    -12
      src/mwparserfromhell/nodes/wikilink.py
  15. +95
    -43
      src/mwparserfromhell/wikicode.py

+ 9
- 0
mypy.ini Просмотреть файл

@@ -0,0 +1,9 @@
# Configuration file for mypy
# https://mypy.readthedocs.io/en/stable/config_file.html

[mypy]
disallow_incomplete_defs = true
;disallow_untyped_defs = True
warn_return_any = true
warn_unused_configs = true
warn_unused_ignores = true

+ 6
- 6
src/mwparserfromhell/definitions.py Просмотреть файл

@@ -113,32 +113,32 @@ MARKUP_TO_HTML = {
} }




def get_html_tag(markup):
def get_html_tag(markup: str) -> str:
"""Return the HTML tag associated with the given wiki-markup.""" """Return the HTML tag associated with the given wiki-markup."""
return MARKUP_TO_HTML[markup] return MARKUP_TO_HTML[markup]




def is_parsable(tag):
def is_parsable(tag: str) -> bool:
"""Return if the given *tag*'s contents should be passed to the parser.""" """Return if the given *tag*'s contents should be passed to the parser."""
return tag.lower() not in PARSER_BLACKLIST return tag.lower() not in PARSER_BLACKLIST




def is_visible(tag):
def is_visible(tag: str) -> bool:
"""Return whether or not the given *tag* contains visible text.""" """Return whether or not the given *tag* contains visible text."""
return tag.lower() not in INVISIBLE_TAGS return tag.lower() not in INVISIBLE_TAGS




def is_single(tag):
def is_single(tag: str) -> bool:
"""Return whether or not the given *tag* can exist without a close tag.""" """Return whether or not the given *tag* can exist without a close tag."""
return tag.lower() in SINGLE return tag.lower() in SINGLE




def is_single_only(tag):
def is_single_only(tag: str) -> bool:
"""Return whether or not the given *tag* must exist without a close tag.""" """Return whether or not the given *tag* must exist without a close tag."""
return tag.lower() in SINGLE_ONLY return tag.lower() in SINGLE_ONLY




def is_scheme(scheme, slashes=True):
def is_scheme(scheme: str, slashes: bool = True) -> bool:
"""Return whether *scheme* is valid for external links.""" """Return whether *scheme* is valid for external links."""
scheme = scheme.lower() scheme = scheme.lower()
if slashes: if slashes:


+ 14
- 4
src/mwparserfromhell/nodes/_base.py Просмотреть файл

@@ -18,8 +18,13 @@
# 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 typing import TYPE_CHECKING, Any, Generator, Optional, Callable

from ..string_mixin import StringMixIn from ..string_mixin import StringMixIn


if TYPE_CHECKING:
from ..wikicode import Wikicode

__all__ = ["Node"] __all__ = ["Node"]




@@ -37,16 +42,21 @@ class Node(StringMixIn):
of the node, if desired, for :meth:`~.Wikicode.get_tree`. of the node, if desired, for :meth:`~.Wikicode.get_tree`.
""" """


def __str__(self):
def __str__(self) -> str:
raise NotImplementedError() raise NotImplementedError()


def __children__(self):
def __children__(self) -> Generator["Wikicode"]:
return return
# pylint: disable=unreachable # pylint: disable=unreachable
yield # pragma: no cover (this is a generator that yields nothing) yield # pragma: no cover (this is a generator that yields nothing)


def __strip__(self, **kwargs):
def __strip__(self, **kwargs: Any) -> Optional[str]:
return None return None


def __showtree__(self, write, get, mark):
def __showtree__(
self,
write: Callable[[str], None],
get: Callable[["Wikicode"], None],
mark: Callable[[], None],
) -> None:
write(str(self)) write(str(self))

+ 22
- 12
src/mwparserfromhell/nodes/argument.py Просмотреть файл

@@ -19,37 +19,47 @@
# SOFTWARE. # SOFTWARE.




from typing import TYPE_CHECKING, Any, Generator, Optional, Callable

from ._base import Node from ._base import Node
from ..utils import parse_anything from ..utils import parse_anything


if TYPE_CHECKING:
from ..wikicode import Wikicode

__all__ = ["Argument"] __all__ = ["Argument"]




class Argument(Node): class Argument(Node):
"""Represents a template argument substitution, like ``{{{foo}}}``.""" """Represents a template argument substitution, like ``{{{foo}}}``."""


def __init__(self, name, default=None):
def __init__(self, name: Any, default: Any = None):
super().__init__() super().__init__()
self.name = name self.name = name
self.default = default self.default = default


def __str__(self):
def __str__(self) -> str:
start = "{{{" + str(self.name) start = "{{{" + str(self.name)
if self.default is not None: if self.default is not None:
return start + "|" + str(self.default) + "}}}" return start + "|" + str(self.default) + "}}}"
return start + "}}}" return start + "}}}"


def __children__(self):
def __children__(self) -> Generator["Wikicode"]:
yield self.name yield self.name
if self.default is not None: if self.default is not None:
yield self.default yield self.default


def __strip__(self, **kwargs):
def __strip__(self, **kwargs: Any) -> Optional[str]:
if self.default is not None: if self.default is not None:
return self.default.strip_code(**kwargs) return self.default.strip_code(**kwargs)
return None return None


def __showtree__(self, write, get, mark):
def __showtree__(
self,
write: Callable[[str], None],
get: Callable[["Wikicode"], None],
mark: Callable[[], None],
) -> None:
write("{{{") write("{{{")
get(self.name) get(self.name)
if self.default is not None: if self.default is not None:
@@ -59,12 +69,16 @@ class Argument(Node):
write("}}}") write("}}}")


@property @property
def name(self):
def name(self) -> "Wikicode":
"""The name of the argument to substitute.""" """The name of the argument to substitute."""
return self._name return self._name


@name.setter
def name(self, value: Any) -> None:
self._name = parse_anything(value)

@property @property
def default(self):
def default(self) -> Optional["Wikicode"]:
"""The default value to substitute if none is passed. """The default value to substitute if none is passed.


This will be ``None`` if the argument wasn't defined with one. The This will be ``None`` if the argument wasn't defined with one. The
@@ -74,12 +88,8 @@ class Argument(Node):
""" """
return self._default return self._default


@name.setter
def name(self, value):
self._name = parse_anything(value)

@default.setter @default.setter
def default(self, default):
def default(self, default: Any) -> None:
if default is None: if default is None:
self._default = None self._default = None
else: else:


+ 6
- 4
src/mwparserfromhell/nodes/comment.py Просмотреть файл

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




from typing import Any

from ._base import Node from ._base import Node


__all__ = ["Comment"] __all__ = ["Comment"]
@@ -27,18 +29,18 @@ __all__ = ["Comment"]
class Comment(Node): class Comment(Node):
"""Represents a hidden HTML comment, like ``<!-- foobar -->``.""" """Represents a hidden HTML comment, like ``<!-- foobar -->``."""


def __init__(self, contents):
def __init__(self, contents: str):
super().__init__() super().__init__()
self.contents = contents self.contents = contents


def __str__(self):
def __str__(self) -> str:
return "<!--" + self.contents + "-->" return "<!--" + self.contents + "-->"


@property @property
def contents(self):
def contents(self) -> str:
"""The hidden text contained between ``<!--`` and ``-->``.""" """The hidden text contained between ``<!--`` and ``-->``."""
return self._contents return self._contents


@contents.setter @contents.setter
def contents(self, value):
def contents(self, value: Any) -> None:
self._contents = str(value) self._contents = str(value)

+ 35
- 19
src/mwparserfromhell/nodes/external_link.py Просмотреть файл

@@ -19,23 +19,34 @@
# SOFTWARE. # SOFTWARE.




from typing import TYPE_CHECKING, Any, Optional, Generator, Callable

from ._base import Node from ._base import Node
from ..utils import parse_anything from ..utils import parse_anything


if TYPE_CHECKING:
from ..wikicode import Wikicode

__all__ = ["ExternalLink"] __all__ = ["ExternalLink"]




class ExternalLink(Node): class ExternalLink(Node):
"""Represents an external link, like ``[http://example.com/ Example]``.""" """Represents an external link, like ``[http://example.com/ Example]``."""


def __init__(self, url, title=None, brackets=True, suppress_space=False):
def __init__(
self,
url: Any,
title: Any = None,
brackets: bool = True,
suppress_space: bool = False,
):
super().__init__() super().__init__()
self.url = url self.url = url
self.title = title self.title = title
self.brackets = brackets self.brackets = brackets
self.suppress_space = suppress_space self.suppress_space = suppress_space


def __str__(self):
def __str__(self) -> str:
if self.brackets: if self.brackets:
if self.title is not None: if self.title is not None:
if self.suppress_space is True: if self.suppress_space is True:
@@ -44,19 +55,24 @@ class ExternalLink(Node):
return "[" + str(self.url) + "]" return "[" + str(self.url) + "]"
return str(self.url) return str(self.url)


def __children__(self):
def __children__(self) -> Generator["Wikicode"]:
yield self.url yield self.url
if self.title is not None: if self.title is not None:
yield self.title yield self.title


def __strip__(self, **kwargs):
def __strip__(self, **kwargs: Any) -> Optional[str]:
if self.brackets: if self.brackets:
if self.title: if self.title:
return self.title.strip_code(**kwargs) return self.title.strip_code(**kwargs)
return None return None
return self.url.strip_code(**kwargs) return self.url.strip_code(**kwargs)


def __showtree__(self, write, get, mark):
def __showtree__(
self,
write: Callable[[str], None],
get: Callable[["Wikicode"], None],
mark: Callable[[], None],
) -> None:
if self.brackets: if self.brackets:
write("[") write("[")
get(self.url) get(self.url)
@@ -66,31 +82,31 @@ class ExternalLink(Node):
write("]") write("]")


@property @property
def url(self):
def url(self) -> "Wikicode":
"""The URL of the link target, as a :class:`.Wikicode` object.""" """The URL of the link target, as a :class:`.Wikicode` object."""
return self._url return self._url


@property
def title(self):
"""The link title (if given), as a :class:`.Wikicode` object."""
return self._title

@property
def brackets(self):
"""Whether to enclose the URL in brackets or display it straight."""
return self._brackets

@url.setter @url.setter
def url(self, value):
def url(self, value: Any) -> None:
# pylint: disable=import-outside-toplevel # pylint: disable=import-outside-toplevel
from ..parser import contexts from ..parser import contexts


self._url = parse_anything(value, contexts.EXT_LINK_URI) self._url = parse_anything(value, contexts.EXT_LINK_URI)


@property
def title(self) -> Optional["Wikicode"]:
"""The link title (if given), as a :class:`.Wikicode` object."""
return self._title

@title.setter @title.setter
def title(self, value):
def title(self, value: Any) -> None:
self._title = None if value is None else parse_anything(value) self._title = None if value is None else parse_anything(value)


@property
def brackets(self) -> bool:
"""Whether to enclose the URL in brackets or display it straight."""
return self._brackets

@brackets.setter @brackets.setter
def brackets(self, value):
def brackets(self, value: bool) -> None:
self._brackets = bool(value) self._brackets = bool(value)

+ 58
- 48
src/mwparserfromhell/nodes/extras/attribute.py Просмотреть файл

@@ -19,9 +19,14 @@
# SOFTWARE. # SOFTWARE.




from typing import TYPE_CHECKING, Any, Optional

from ...string_mixin import StringMixIn from ...string_mixin import StringMixIn
from ...utils import parse_anything from ...utils import parse_anything


if TYPE_CHECKING:
from ...wikicode import Wikicode

__all__ = ["Attribute"] __all__ = ["Attribute"]




@@ -35,23 +40,28 @@ class Attribute(StringMixIn):


def __init__( def __init__(
self, self,
name,
value=None,
quotes='"',
pad_first=" ",
pad_before_eq="",
pad_after_eq="",
name: Any,
value: Any = None,
quotes: Optional[str] = '"',
pad_first: str = " ",
pad_before_eq: str = "",
pad_after_eq: str = "",
): ):
super().__init__() super().__init__()

self._pad_first: str
self._pad_before_eq: str
self._pad_after_eq: str

self.name = name self.name = name
self._quotes = None
self._quotes: Optional[str] = None
self.value = value self.value = value
self.quotes = quotes self.quotes = quotes
self.pad_first = pad_first self.pad_first = pad_first
self.pad_before_eq = pad_before_eq self.pad_before_eq = pad_before_eq
self.pad_after_eq = pad_after_eq self.pad_after_eq = pad_after_eq


def __str__(self):
def __str__(self) -> str:
result = self.pad_first + str(self.name) + self.pad_before_eq result = self.pad_first + str(self.name) + self.pad_before_eq
if self.value is not None: if self.value is not None:
result += "=" + self.pad_after_eq result += "=" + self.pad_after_eq
@@ -61,11 +71,11 @@ class Attribute(StringMixIn):
return result return result


@staticmethod @staticmethod
def _value_needs_quotes(val):
def _value_needs_quotes(value: "Wikicode") -> Optional[str]:
"""Return valid quotes for the given value, or None if unneeded.""" """Return valid quotes for the given value, or None if unneeded."""
if not val:
if not value:
return None return None
val = "".join(str(node) for node in val.filter_text(recursive=False))
val = "".join(str(node) for node in value.filter_text(recursive=False))
if not any(char.isspace() for char in val): if not any(char.isspace() for char in val):
return None return None
if "'" in val and '"' not in val: if "'" in val and '"' not in val:
@@ -74,7 +84,7 @@ class Attribute(StringMixIn):
return "'" return "'"
return "\"'" # Either acceptable, " preferred over ' return "\"'" # Either acceptable, " preferred over '


def _set_padding(self, attr, value):
def _set_padding(self, attr: str, value: str) -> None:
"""Setter for the value of a padding attribute.""" """Setter for the value of a padding attribute."""
if not value: if not value:
setattr(self, attr, "") setattr(self, attr, "")
@@ -85,49 +95,29 @@ class Attribute(StringMixIn):
setattr(self, attr, value) setattr(self, attr, value)


@staticmethod @staticmethod
def coerce_quotes(quotes):
def coerce_quotes(quotes: Any) -> Optional[str]:
"""Coerce a quote type into an acceptable value, or raise an error.""" """Coerce a quote type into an acceptable value, or raise an error."""
orig, quotes = quotes, str(quotes) if quotes else None
if quotes not in [None, '"', "'"]:
raise ValueError("{!r} is not a valid quote type".format(orig))
return quotes
coerced_quotes = str(quotes) if quotes else None
if coerced_quotes not in [None, '"', "'"]:
raise ValueError("{!r} is not a valid quote type".format(quotes))
return coerced_quotes


@property @property
def name(self):
def name(self) -> "Wikicode":
"""The name of the attribute as a :class:`.Wikicode` object.""" """The name of the attribute as a :class:`.Wikicode` object."""
return self._name return self._name


@name.setter
def name(self, value: Any) -> None:
self._name = parse_anything(value)

@property @property
def value(self):
def value(self) -> Optional["Wikicode"]:
"""The value of the attribute as a :class:`.Wikicode` object.""" """The value of the attribute as a :class:`.Wikicode` object."""
return self._value return self._value


@property
def quotes(self):
"""How to enclose the attribute value. ``"``, ``'``, or ``None``."""
return self._quotes

@property
def pad_first(self):
"""Spacing to insert right before the attribute."""
return self._pad_first

@property
def pad_before_eq(self):
"""Spacing to insert right before the equal sign."""
return self._pad_before_eq

@property
def pad_after_eq(self):
"""Spacing to insert right after the equal sign."""
return self._pad_after_eq

@name.setter
def name(self, value):
self._name = parse_anything(value)

@value.setter @value.setter
def value(self, newval):
def value(self, newval: Any) -> None:
if newval is None: if newval is None:
self._value = None self._value = None
else: else:
@@ -137,21 +127,41 @@ class Attribute(StringMixIn):
self._quotes = quotes[0] self._quotes = quotes[0]
self._value = code self._value = code


@property
def quotes(self) -> Optional[str]:
"""How to enclose the attribute value. ``"``, ``'``, or ``None``."""
return self._quotes

@quotes.setter @quotes.setter
def quotes(self, value):
def quotes(self, value: Any) -> None:
value = self.coerce_quotes(value) value = self.coerce_quotes(value)
if not value and self._value_needs_quotes(self.value): if not value and self._value_needs_quotes(self.value):
raise ValueError("attribute value requires quotes") raise ValueError("attribute value requires quotes")
self._quotes = value self._quotes = value


@property
def pad_first(self) -> str:
"""Spacing to insert right before the attribute."""
return self._pad_first

@pad_first.setter @pad_first.setter
def pad_first(self, value):
def pad_first(self, value: str) -> None:
self._set_padding("_pad_first", value) self._set_padding("_pad_first", value)


@property
def pad_before_eq(self) -> str:
"""Spacing to insert right before the equal sign."""
return self._pad_before_eq

@pad_before_eq.setter @pad_before_eq.setter
def pad_before_eq(self, value):
def pad_before_eq(self, value: str) -> None:
self._set_padding("_pad_before_eq", value) self._set_padding("_pad_before_eq", value)


@property
def pad_after_eq(self) -> str:
"""Spacing to insert right after the equal sign."""
return self._pad_after_eq

@pad_after_eq.setter @pad_after_eq.setter
def pad_after_eq(self, value):
def pad_after_eq(self, value: str) -> None:
self._set_padding("_pad_after_eq", value) self._set_padding("_pad_after_eq", value)

+ 21
- 16
src/mwparserfromhell/nodes/extras/parameter.py Просмотреть файл

@@ -18,16 +18,21 @@
# 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 typing import TYPE_CHECKING, Any, Union

import re import re


from ...string_mixin import StringMixIn from ...string_mixin import StringMixIn
from ...utils import parse_anything from ...utils import parse_anything


if TYPE_CHECKING:
from ...wikicode import Wikicode

__all__ = ["Parameter"] __all__ = ["Parameter"]




class Parameter(StringMixIn): class Parameter(StringMixIn):
"""Represents a paramater of a template.
"""Represents a parameter of a template.


For example, the template ``{{foo|bar|spam=eggs}}`` contains two For example, the template ``{{foo|bar|spam=eggs}}`` contains two
Parameters: one whose name is ``"1"``, value is ``"bar"``, and ``showkey`` Parameters: one whose name is ``"1"``, value is ``"bar"``, and ``showkey``
@@ -35,47 +40,47 @@ class Parameter(StringMixIn):
``showkey`` is ``True``. ``showkey`` is ``True``.
""" """


def __init__(self, name, value, showkey=True):
def __init__(self, name: Any, value: Any, showkey: bool = True) -> None:
super().__init__() super().__init__()
self.name = name self.name = name
self.value = value self.value = value
self.showkey = showkey self.showkey = showkey


def __str__(self):
def __str__(self) -> str:
if self.showkey: if self.showkey:
return str(self.name) + "=" + str(self.value) return str(self.name) + "=" + str(self.value)
return str(self.value) return str(self.value)


@staticmethod @staticmethod
def can_hide_key(key):
def can_hide_key(key: Any) -> Union[re.Match, None]:
"""Return whether or not the given key can be hidden.""" """Return whether or not the given key can be hidden."""
return re.match(r"[1-9][0-9]*$", str(key).strip()) return re.match(r"[1-9][0-9]*$", str(key).strip())


@property @property
def name(self):
def name(self) -> "Wikicode":
"""The name of the parameter as a :class:`.Wikicode` object.""" """The name of the parameter as a :class:`.Wikicode` object."""
return self._name return self._name


@name.setter
def name(self, newval: Any) -> None:
self._name = parse_anything(newval)

@property @property
def value(self):
def value(self) -> "Wikicode":
"""The value of the parameter as a :class:`.Wikicode` object.""" """The value of the parameter as a :class:`.Wikicode` object."""
return self._value return self._value


@value.setter
def value(self, newval: Any) -> None:
self._value = parse_anything(newval)

@property @property
def showkey(self):
def showkey(self) -> bool:
"""Whether to show the parameter's key (i.e., its "name").""" """Whether to show the parameter's key (i.e., its "name")."""
return self._showkey return self._showkey


@name.setter
def name(self, newval):
self._name = parse_anything(newval)

@value.setter
def value(self, newval):
self._value = parse_anything(newval)

@showkey.setter @showkey.setter
def showkey(self, newval):
def showkey(self, newval: Any) -> None:
newval = bool(newval) newval = bool(newval)
if not newval and not self.can_hide_key(self.name): if not newval and not self.can_hide_key(self.name):
raise ValueError("parameter key {!r} cannot be hidden".format(self.name)) raise ValueError("parameter key {!r} cannot be hidden".format(self.name))


+ 21
- 13
src/mwparserfromhell/nodes/heading.py Просмотреть файл

@@ -19,50 +19,58 @@
# SOFTWARE. # SOFTWARE.




from typing import TYPE_CHECKING, Any, Generator, Callable, Optional

from ._base import Node from ._base import Node
from ..utils import parse_anything from ..utils import parse_anything


__all__ = ["Heading"]
if TYPE_CHECKING:
from ..wikicode import Wikicode




class Heading(Node): class Heading(Node):
"""Represents a section heading in wikicode, like ``== Foo ==``.""" """Represents a section heading in wikicode, like ``== Foo ==``."""


def __init__(self, title, level):
def __init__(self, title: Any, level: int):
super().__init__() super().__init__()
self.title = title self.title = title
self.level = level self.level = level


def __str__(self):
def __str__(self) -> str:
return ("=" * self.level) + str(self.title) + ("=" * self.level) return ("=" * self.level) + str(self.title) + ("=" * self.level)


def __children__(self):
def __children__(self) -> Generator["Wikicode", None, None]:
yield self.title yield self.title


def __strip__(self, **kwargs):
def __strip__(self, **kwargs: Any) -> Optional[str]:
return self.title.strip_code(**kwargs) return self.title.strip_code(**kwargs)


def __showtree__(self, write, get, mark):
def __showtree__(
self,
write: Callable[[str], None],
get: Callable[["Wikicode"], None],
mark: Callable[[], None],
) -> None:
write("=" * self.level) write("=" * self.level)
get(self.title) get(self.title)
write("=" * self.level) write("=" * self.level)


@property @property
def title(self):
def title(self) -> "Wikicode":
"""The title of the heading, as a :class:`.Wikicode` object.""" """The title of the heading, as a :class:`.Wikicode` object."""
return self._title return self._title


@title.setter
def title(self, value: Any) -> None:
self._title = parse_anything(value)

@property @property
def level(self):
def level(self) -> int:
"""The heading level, as an integer between 1 and 6, inclusive.""" """The heading level, as an integer between 1 and 6, inclusive."""
return self._level return self._level


@title.setter
def title(self, value):
self._title = parse_anything(value)

@level.setter @level.setter
def level(self, value):
def level(self, value: int) -> None:
value = int(value) value = int(value)
if value < 1 or value > 6: if value < 1 or value > 6:
raise ValueError(value) raise ValueError(value)


+ 47
- 38
src/mwparserfromhell/nodes/html_entity.py Просмотреть файл

@@ -18,6 +18,7 @@
# 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 typing import Any, Optional
import html.entities as htmlentities import html.entities as htmlentities


from ._base import Node from ._base import Node
@@ -28,9 +29,15 @@ __all__ = ["HTMLEntity"]
class HTMLEntity(Node): class HTMLEntity(Node):
"""Represents an HTML entity, like ``&nbsp;``, either named or unnamed.""" """Represents an HTML entity, like ``&nbsp;``, either named or unnamed."""


def __init__(self, value, named=None, hexadecimal=False, hex_char="x"):
def __init__(
self,
value: Any,
named: Optional[bool] = None,
hexadecimal: bool = False,
hex_char: str = "x",
):
super().__init__() super().__init__()
self._value = value
self._value = str(value)
if named is None: # Try to guess whether or not the entity is named if named is None: # Try to guess whether or not the entity is named
try: try:
int(value) int(value)
@@ -49,50 +56,25 @@ class HTMLEntity(Node):
self._hexadecimal = hexadecimal self._hexadecimal = hexadecimal
self._hex_char = hex_char self._hex_char = hex_char


def __str__(self):
def __str__(self) -> str:
if self.named: if self.named:
return "&{};".format(self.value) return "&{};".format(self.value)
if self.hexadecimal: if self.hexadecimal:
return "&#{}{};".format(self.hex_char, self.value) return "&#{}{};".format(self.hex_char, self.value)
return "&#{};".format(self.value) return "&#{};".format(self.value)


def __strip__(self, **kwargs):
def __strip__(self, **kwargs: Any) -> Optional[str]:
if kwargs.get("normalize"): if kwargs.get("normalize"):
return self.normalize() return self.normalize()
return self
return str(self)


@property @property
def value(self):
def value(self) -> str:
"""The string value of the HTML entity.""" """The string value of the HTML entity."""
return self._value return self._value


@property
def named(self):
"""Whether the entity is a string name for a codepoint or an integer.

For example, ``&Sigma;``, ``&#931;``, and ``&#x3a3;`` refer to the same
character, but only the first is "named", while the others are integer
representations of the codepoint.
"""
return self._named

@property
def hexadecimal(self):
"""If unnamed, this is whether the value is hexadecimal or decimal."""
return self._hexadecimal

@property
def hex_char(self):
"""If the value is hexadecimal, this is the letter denoting that.

For example, the hex_char of ``"&#x1234;"`` is ``"x"``, whereas the
hex_char of ``"&#X1234;"`` is ``"X"``. Lowercase and uppercase ``x``
are the only values supported.
"""
return self._hex_char

@value.setter @value.setter
def value(self, newval):
def value(self, newval: Any) -> None:
newval = str(newval) newval = str(newval)
try: try:
int(newval) int(newval)
@@ -122,36 +104,63 @@ class HTMLEntity(Node):
self._named = False self._named = False
self._value = newval self._value = newval


@property
def named(self) -> bool:
"""Whether the entity is a string name for a codepoint or an integer.

For example, ``&Sigma;``, ``&#931;``, and ``&#x3a3;`` refer to the same
character, but only the first is "named", while the others are integer
representations of the codepoint.
"""
return self._named

@named.setter @named.setter
def named(self, newval):
def named(self, newval: bool) -> None:
newval = bool(newval) newval = bool(newval)
if newval and self.value not in htmlentities.entitydefs: if newval and self.value not in htmlentities.entitydefs:
raise ValueError("entity value {!r} is not a valid name".format(self.value)) raise ValueError("entity value {!r} is not a valid name".format(self.value))

if not newval: if not newval:
try: try:
int(self.value, 16) int(self.value, 16)
except ValueError as exc: except ValueError as exc:
raise ValueError( raise ValueError(
"current entity value {!r} is not a valid "
"Unicode codepoint".format(self.value)
"current entity value {!r} is not a valid Unicode codepoint".format(
self.value
)
) from exc ) from exc
self._named = newval self._named = newval


@property
def hexadecimal(self) -> bool:
"""If unnamed, this is whether the value is hexadecimal or decimal."""
return self._hexadecimal

@hexadecimal.setter @hexadecimal.setter
def hexadecimal(self, newval):
def hexadecimal(self, newval: bool) -> None:
newval = bool(newval) newval = bool(newval)
if newval and self.named: if newval and self.named:
raise ValueError("a named entity cannot be hexadecimal") raise ValueError("a named entity cannot be hexadecimal")
self._hexadecimal = newval self._hexadecimal = newval


@property
def hex_char(self) -> str:
"""If the value is hexadecimal, this is the letter denoting that.

For example, the hex_char of ``"&#x1234;"`` is ``"x"``, whereas the
hex_char of ``"&#X1234;"`` is ``"X"``. Lowercase and uppercase ``x``
are the only values supported.
"""
return self._hex_char

@hex_char.setter @hex_char.setter
def hex_char(self, newval):
def hex_char(self, newval: str) -> None:
newval = str(newval) newval = str(newval)
if newval not in ("x", "X"): if newval not in ("x", "X"):
raise ValueError(newval) raise ValueError(newval)
self._hex_char = newval self._hex_char = newval


def normalize(self):
def normalize(self) -> str:
"""Return the unicode character represented by the HTML entity.""" """Return the unicode character represented by the HTML entity."""
if self.named: if self.named:
return chr(htmlentities.name2codepoint[self.value]) return chr(htmlentities.name2codepoint[self.value])


+ 98
- 85
src/mwparserfromhell/nodes/tag.py Просмотреть файл

@@ -19,11 +19,16 @@
# SOFTWARE. # SOFTWARE.




from typing import TYPE_CHECKING, Any, Callable, Optional, List, Generator

from ._base import Node from ._base import Node
from .extras import Attribute from .extras import Attribute
from ..definitions import is_visible from ..definitions import is_visible
from ..utils import parse_anything from ..utils import parse_anything


if TYPE_CHECKING:
from ..wikicode import Wikicode

__all__ = ["Tag"] __all__ = ["Tag"]




@@ -32,19 +37,22 @@ class Tag(Node):


def __init__( def __init__(
self, self,
tag,
contents=None,
attrs=None,
wiki_markup=None,
self_closing=False,
invalid=False,
implicit=False,
padding="",
closing_tag=None,
wiki_style_separator=None,
closing_wiki_markup=None,
tag: Any,
contents: Any = None,
attrs: Optional[List[Attribute]] = None,
wiki_markup: Optional[str] = None,
self_closing: bool = False,
invalid: bool = False,
implicit: bool = False,
padding: str = "",
closing_tag: Optional["Wikicode"] = None,
wiki_style_separator: Optional[str] = None,
closing_wiki_markup: Optional[str] = None,
): ):
super().__init__() super().__init__()
self._attrs: List[Attribute]
self._closing_wiki_markup: Optional[str]

self.tag = tag self.tag = tag
self.contents = contents self.contents = contents
self._attrs = attrs if attrs else [] self._attrs = attrs if attrs else []
@@ -60,7 +68,7 @@ class Tag(Node):
if closing_wiki_markup is not None: if closing_wiki_markup is not None:
self.closing_wiki_markup = closing_wiki_markup self.closing_wiki_markup = closing_wiki_markup


def __str__(self):
def __str__(self) -> str:
if self.wiki_markup: if self.wiki_markup:
if self.attributes: if self.attributes:
attrs = "".join([str(attr) for attr in self.attributes]) attrs = "".join([str(attr) for attr in self.attributes])
@@ -90,7 +98,7 @@ class Tag(Node):
result += "</" + str(self.closing_tag) + ">" result += "</" + str(self.closing_tag) + ">"
return result return result


def __children__(self):
def __children__(self) -> Generator["Wikicode"]:
if not self.wiki_markup: if not self.wiki_markup:
yield self.tag yield self.tag
for attr in self.attributes: for attr in self.attributes:
@@ -102,12 +110,17 @@ class Tag(Node):
if not self.wiki_markup and self.closing_tag: if not self.wiki_markup and self.closing_tag:
yield self.closing_tag yield self.closing_tag


def __strip__(self, **kwargs):
if self.contents and is_visible(self.tag):
def __strip__(self, **kwargs: Any) -> Optional[str]:
if self.contents and is_visible(str(self.tag)):
return self.contents.strip_code(**kwargs) return self.contents.strip_code(**kwargs)
return None return None


def __showtree__(self, write, get, mark):
def __showtree__(
self,
write: Callable[[str], None],
get: Callable[["Wikicode"], None],
mark: Callable[[], None],
) -> None:
write("</" if self.invalid else "<") write("</" if self.invalid else "<")
get(self.tag) get(self.tag)
for attr in self.attributes: for attr in self.attributes:
@@ -127,17 +140,25 @@ class Tag(Node):
write(">") write(">")


@property @property
def tag(self):
def tag(self) -> "Wikicode":
"""The tag itself, as a :class:`.Wikicode` object.""" """The tag itself, as a :class:`.Wikicode` object."""
return self._tag return self._tag


@tag.setter
def tag(self, value: Any) -> None:
self._tag = self._closing_tag = parse_anything(value)

@property @property
def contents(self):
def contents(self) -> "Wikicode":
"""The contents of the tag, as a :class:`.Wikicode` object.""" """The contents of the tag, as a :class:`.Wikicode` object."""
return self._contents return self._contents


@contents.setter
def contents(self, value: Any) -> None:
self._contents = parse_anything(value)

@property @property
def attributes(self):
def attributes(self) -> List[Attribute]:
"""The list of attributes affecting the tag. """The list of attributes affecting the tag.


Each attribute is an instance of :class:`.Attribute`. Each attribute is an instance of :class:`.Attribute`.
@@ -145,7 +166,7 @@ class Tag(Node):
return self._attrs return self._attrs


@property @property
def wiki_markup(self):
def wiki_markup(self) -> Optional[str]:
"""The wikified version of a tag to show instead of HTML. """The wikified version of a tag to show instead of HTML.


If set to a value, this will be displayed instead of the brackets. If set to a value, this will be displayed instead of the brackets.
@@ -154,13 +175,23 @@ class Tag(Node):
""" """
return self._wiki_markup return self._wiki_markup


@wiki_markup.setter
def wiki_markup(self, value: Optional[str]) -> None:
self._wiki_markup = str(value) if value else None
if not value or not self.closing_wiki_markup:
self._closing_wiki_markup = self._wiki_markup

@property @property
def self_closing(self):
def self_closing(self) -> bool:
"""Whether the tag is self-closing with no content (like ``<br/>``).""" """Whether the tag is self-closing with no content (like ``<br/>``)."""
return self._self_closing return self._self_closing


@self_closing.setter
def self_closing(self, value: bool) -> None:
self._self_closing = bool(value)

@property @property
def invalid(self):
def invalid(self) -> bool:
"""Whether the tag starts with a backslash after the opening bracket. """Whether the tag starts with a backslash after the opening bracket.


This makes the tag look like a lone close tag. It is technically This makes the tag look like a lone close tag. It is technically
@@ -170,8 +201,12 @@ class Tag(Node):
""" """
return self._invalid return self._invalid


@invalid.setter
def invalid(self, value: bool) -> None:
self._invalid = bool(value)

@property @property
def implicit(self):
def implicit(self) -> bool:
"""Whether the tag is implicitly self-closing, with no ending slash. """Whether the tag is implicitly self-closing, with no ending slash.


This is only possible for specific "single" tags like ``<br>`` and This is only possible for specific "single" tags like ``<br>`` and
@@ -180,13 +215,27 @@ class Tag(Node):
""" """
return self._implicit return self._implicit


@implicit.setter
def implicit(self, value: bool) -> None:
self._implicit = bool(value)

@property @property
def padding(self):
def padding(self) -> str:
"""Spacing to insert before the first closing ``>``.""" """Spacing to insert before the first closing ``>``."""
return self._padding return self._padding


@padding.setter
def padding(self, value: str) -> None:
if not value:
self._padding = ""
else:
value = str(value)
if not value.isspace():
raise ValueError("padding must be entirely whitespace")
self._padding = value

@property @property
def closing_tag(self):
def closing_tag(self) -> "Wikicode":
"""The closing tag, as a :class:`.Wikicode` object. """The closing tag, as a :class:`.Wikicode` object.


This will usually equal :attr:`tag`, unless there is additional This will usually equal :attr:`tag`, unless there is additional
@@ -194,76 +243,40 @@ class Tag(Node):
""" """
return self._closing_tag return self._closing_tag


@closing_tag.setter
def closing_tag(self, value: Any) -> None:
self._closing_tag = parse_anything(value)

@property @property
def wiki_style_separator(self):
def wiki_style_separator(self) -> Optional[str]:
"""The separator between the padding and content in a wiki markup tag. """The separator between the padding and content in a wiki markup tag.


Essentially the wiki equivalent of the TagCloseOpen. Essentially the wiki equivalent of the TagCloseOpen.
""" """
return self._wiki_style_separator return self._wiki_style_separator


@wiki_style_separator.setter
def wiki_style_separator(self, value: Optional[str]) -> None:
self._wiki_style_separator = str(value) if value else None

@property @property
def closing_wiki_markup(self):
def closing_wiki_markup(self) -> Optional[str]:
"""The wikified version of the closing tag to show instead of HTML. """The wikified version of the closing tag to show instead of HTML.


If set to a value, this will be displayed instead of the close tag If set to a value, this will be displayed instead of the close tag
brackets. If tag is :attr:`self_closing` is ``True`` then this is not
displayed. If :attr:`wiki_markup` is set and this has not been set, this
is set to the value of :attr:`wiki_markup`. If this has been set and
brackets. If the tag is :attr:`self_closing`, this is not displayed.
If :attr:`wiki_markup` is set and this has not been set, this is set
to the value of :attr:`wiki_markup`. If this has been set and
:attr:`wiki_markup` is set to a ``False`` value, this is set to :attr:`wiki_markup` is set to a ``False`` value, this is set to
``None``. ``None``.
""" """
return self._closing_wiki_markup return self._closing_wiki_markup


@tag.setter
def tag(self, value):
self._tag = self._closing_tag = parse_anything(value)

@contents.setter
def contents(self, value):
self._contents = parse_anything(value)

@wiki_markup.setter
def wiki_markup(self, value):
self._wiki_markup = str(value) if value else None
if not value or not self.closing_wiki_markup:
self._closing_wiki_markup = self._wiki_markup

@self_closing.setter
def self_closing(self, value):
self._self_closing = bool(value)

@invalid.setter
def invalid(self, value):
self._invalid = bool(value)

@implicit.setter
def implicit(self, value):
self._implicit = bool(value)

@padding.setter
def padding(self, value):
if not value:
self._padding = ""
else:
value = str(value)
if not value.isspace():
raise ValueError("padding must be entirely whitespace")
self._padding = value

@closing_tag.setter
def closing_tag(self, value):
self._closing_tag = parse_anything(value)

@wiki_style_separator.setter
def wiki_style_separator(self, value):
self._wiki_style_separator = str(value) if value else None

@closing_wiki_markup.setter @closing_wiki_markup.setter
def closing_wiki_markup(self, value):
def closing_wiki_markup(self, value: Optional[str]) -> None:
self._closing_wiki_markup = str(value) if value else None self._closing_wiki_markup = str(value) if value else None


def has(self, name):
def has(self, name: str) -> bool:
"""Return whether any attribute in the tag has the given *name*. """Return whether any attribute in the tag has the given *name*.


Note that a tag may have multiple attributes with the same name, but Note that a tag may have multiple attributes with the same name, but
@@ -274,7 +287,7 @@ class Tag(Node):
return True return True
return False return False


def get(self, name):
def get(self, name: str) -> Attribute:
"""Get the attribute with the given *name*. """Get the attribute with the given *name*.


The returned object is a :class:`.Attribute` instance. Raises The returned object is a :class:`.Attribute` instance. Raises
@@ -289,13 +302,13 @@ class Tag(Node):


def add( def add(
self, self,
name,
value=None,
quotes='"',
pad_first=" ",
pad_before_eq="",
pad_after_eq="",
):
name: Any,
value: Any = None,
quotes: Optional[str] = '"',
pad_first: str = " ",
pad_before_eq: str = "",
pad_after_eq: str = "",
) -> Attribute:
"""Add an attribute with the given *name* and *value*. """Add an attribute with the given *name* and *value*.


*name* and *value* can be anything parsable by *name* and *value* can be anything parsable by
@@ -318,7 +331,7 @@ class Tag(Node):
self.attributes.append(attr) self.attributes.append(attr)
return attr return attr


def remove(self, name):
def remove(self, name: str) -> None:
"""Remove all attributes with the given *name*. """Remove all attributes with the given *name*.


Raises :exc:`ValueError` if none were found. Raises :exc:`ValueError` if none were found.


+ 62
- 36
src/mwparserfromhell/nodes/template.py Просмотреть файл

@@ -19,6 +19,18 @@
# SOFTWARE. # SOFTWARE.
from collections import defaultdict from collections import defaultdict
import re import re
from typing import (
TYPE_CHECKING,
Any,
Optional,
Union,
List,
Dict,
Callable,
Generator,
Mapping,
cast,
)


from ._base import Node from ._base import Node
from .html_entity import HTMLEntity from .html_entity import HTMLEntity
@@ -26,43 +38,49 @@ from .text import Text
from .extras import Parameter from .extras import Parameter
from ..utils import parse_anything from ..utils import parse_anything


if TYPE_CHECKING:
from ..wikicode import Wikicode

__all__ = ["Template"] __all__ = ["Template"]


FLAGS = re.DOTALL | re.UNICODE FLAGS = re.DOTALL | re.UNICODE
# Used to allow None as a valid fallback value # Used to allow None as a valid fallback value
_UNSET = object() _UNSET = object()



class Template(Node): class Template(Node):
"""Represents a template in wikicode, like ``{{foo}}``.""" """Represents a template in wikicode, like ``{{foo}}``."""


def __init__(self, name, params=None):
def __init__(self, name: Any, params: Optional[List[Parameter]] = None):
super().__init__() super().__init__()
self.name = name self.name = name
if params:
self._params = params
else:
self._params = []
self._params: List[Parameter] = params or []


def __str__(self):
def __str__(self) -> str:
if self.params: if self.params:
params = "|".join([str(param) for param in self.params]) params = "|".join([str(param) for param in self.params])
return "{{" + str(self.name) + "|" + params + "}}" return "{{" + str(self.name) + "|" + params + "}}"
return "{{" + str(self.name) + "}}" return "{{" + str(self.name) + "}}"


def __children__(self):
def __children__(self) -> Generator["Wikicode"]:
yield self.name yield self.name
for param in self.params: for param in self.params:
if param.showkey: if param.showkey:
yield param.name yield param.name
yield param.value yield param.value


def __strip__(self, **kwargs):
def __strip__(self, **kwargs: Any) -> Optional[str]:
if kwargs.get("keep_template_params"): if kwargs.get("keep_template_params"):
parts = [param.value.strip_code(**kwargs) for param in self.params] parts = [param.value.strip_code(**kwargs) for param in self.params]
return " ".join(part for part in parts if part) return " ".join(part for part in parts if part)
return None return None


def __showtree__(self, write, get, mark):
def __showtree__(
self,
write: Callable[[str], None],
get: Callable[["Wikicode"], None],
mark: Callable[[], None],
) -> None:
write("{{") write("{{")
get(self.name) get(self.name)
for param in self.params: for param in self.params:
@@ -75,7 +93,7 @@ class Template(Node):
write("}}") write("}}")


@staticmethod @staticmethod
def _surface_escape(code, char):
def _surface_escape(code: "Wikicode", char: str) -> None:
"""Return *code* with *char* escaped as an HTML entity. """Return *code* with *char* escaped as an HTML entity.


The main use of this is to escape pipes (``|``) or equal signs (``=``) The main use of this is to escape pipes (``|``) or equal signs (``=``)
@@ -88,7 +106,7 @@ class Template(Node):
code.replace(node, node.replace(char, replacement), False) code.replace(node, node.replace(char, replacement), False)


@staticmethod @staticmethod
def _select_theory(theories):
def _select_theory(theories: Dict[str, int]) -> Optional[str]:
"""Return the most likely spacing convention given different options. """Return the most likely spacing convention given different options.


Given a dictionary of convention options as keys and their occurrence Given a dictionary of convention options as keys and their occurrence
@@ -104,7 +122,7 @@ class Template(Node):
return None return None


@staticmethod @staticmethod
def _blank_param_value(value):
def _blank_param_value(value: "Wikicode") -> None:
"""Remove the content from *value* while keeping its whitespace. """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
@@ -119,15 +137,17 @@ class Template(Node):
before, after = match.group(1), match.group(2) before, after = match.group(1), match.group(2)
value.nodes = [Text(before), Text(after)] value.nodes = [Text(before), Text(after)]


def _get_spacing_conventions(self, use_names):
def _get_spacing_conventions(
self, use_names: bool
) -> tuple[Optional[str], Optional[str]]:
"""Try to determine the whitespace conventions for parameters. """Try to determine the whitespace conventions for parameters.


This will examine the existing parameters and use This will examine the existing parameters and use
:meth:`_select_theory` to determine if there are any preferred styles :meth:`_select_theory` to determine if there are any preferred styles
for how much whitespace to put before or after the value. for how much whitespace to put before or after the value.
""" """
before_theories = defaultdict(lambda: 0)
after_theories = defaultdict(lambda: 0)
before_theories: defaultdict[str, int] = defaultdict(lambda: 0)
after_theories: defaultdict[str, int] = defaultdict(lambda: 0)
for param in self.params: for param in self.params:
if not param.showkey: if not param.showkey:
continue continue
@@ -149,14 +169,14 @@ class Template(Node):
after = self._select_theory(after_theories) after = self._select_theory(after_theories)
return before, after return before, after


def _fix_dependendent_params(self, i):
def _fix_dependendent_params(self, i: int) -> None:
"""Unhide keys if necessary after removing the param at index *i*.""" """Unhide keys if necessary after removing the param at index *i*."""
if not self.params[i].showkey: if not self.params[i].showkey:
for param in self.params[i + 1 :]: for param in self.params[i + 1 :]:
if not param.showkey: if not param.showkey:
param.showkey = True param.showkey = True


def _remove_exact(self, needle, keep_field):
def _remove_exact(self, needle: Parameter, keep_field: bool) -> None:
"""Remove a specific parameter, *needle*, from the template.""" """Remove a specific parameter, *needle*, from the template."""
for i, param in enumerate(self.params): for i, param in enumerate(self.params):
if param is needle: if param is needle:
@@ -168,7 +188,7 @@ class Template(Node):
return return
raise ValueError(needle) raise ValueError(needle)


def _should_remove(self, i, name):
def _should_remove(self, i: int, name: str) -> bool:
"""Look ahead for a parameter with the same name, but hidden. """Look ahead for a parameter with the same name, but hidden.


If one exists, we should remove the given one rather than blanking it. If one exists, we should remove the given one rather than blanking it.
@@ -182,20 +202,20 @@ class Template(Node):
return False return False


@property @property
def name(self):
def name(self) -> "Wikicode":
"""The name of the template, as a :class:`.Wikicode` object.""" """The name of the template, as a :class:`.Wikicode` object."""
return self._name return self._name


@name.setter
def name(self, value: Any) -> None:
self._name = parse_anything(value)

@property @property
def params(self):
def params(self) -> List[Parameter]:
"""The list of parameters contained within the template.""" """The list of parameters contained within the template."""
return self._params return self._params


@name.setter
def name(self, value):
self._name = parse_anything(value)

def has(self, name, ignore_empty=False):
def has(self, name: Union[str, Any], ignore_empty: bool = False) -> bool:
"""Return ``True`` if any parameter in the template is named *name*. """Return ``True`` if any parameter in the template is named *name*.


With *ignore_empty*, ``False`` will be returned even if the template With *ignore_empty*, ``False`` will be returned even if the template
@@ -211,11 +231,11 @@ class Template(Node):
return True return True
return False return False


def has_param(self, name, ignore_empty=False):
def has_param(self, name: Union[str, Any], ignore_empty: bool = False) -> bool:
"""Alias for :meth:`has`.""" """Alias for :meth:`has`."""
return self.has(name, ignore_empty) return self.has(name, ignore_empty)


def get(self, name, default=_UNSET):
def get(self, name: Union[str, Any], default: Any = _UNSET) -> Union[Parameter, Any]:
"""Get the parameter whose name is *name*. """Get the parameter whose name is *name*.


The returned object is a :class:`.Parameter` instance. Raises The returned object is a :class:`.Parameter` instance. Raises
@@ -232,12 +252,18 @@ class Template(Node):
raise ValueError(name) raise ValueError(name)
return default return default


def __getitem__(self, name):
def __getitem__(self, name: Union[str, Any]) -> Parameter:
return self.get(name) return self.get(name)


def add( def add(
self, name, value, showkey=None, before=None, after=None, preserve_spacing=True
):
self,
name: Any,
value: Any,
showkey: Optional[bool] = None,
before: Optional[Union[Parameter, str]] = None,
after: Optional[Union[Parameter, str]] = None,
preserve_spacing: bool = True,
) -> Parameter:
"""Add a parameter to the template with a given *name* and *value*. """Add a parameter to the template with a given *name* and *value*.


*name* and *value* can be anything parsable by *name* and *value* can be anything parsable by
@@ -328,21 +354,21 @@ class Template(Node):
self.params.append(param) self.params.append(param)
return param return param


def update(self, params, **kwargs):
def update(self, params: Mapping[Any, Any], **kwargs: Any) -> None:
"""Update the template with multiple parameters at once. """Update the template with multiple parameters at once.
Args: Args:
params: A dictionary mapping parameter names to values.
params: A dictionary mapping parameter names to values.
**kwargs: Optional arguments that will be applied to all parameters, **kwargs: Optional arguments that will be applied to all parameters,
matching the same arguments in :meth:`add` (showkey, before,
matching the same arguments in :meth:`add` (showkey, before,
after, preserve_spacing) after, preserve_spacing)
""" """
for name, value in params.items(): for name, value in params.items():
self.add(name, value, **kwargs) self.add(name, value, **kwargs)


def __setitem__(self, name, value):
def __setitem__(self, name: Any, value: Any) -> Parameter:
return self.add(name, value) return self.add(name, value)


def remove(self, param, keep_field=False):
def remove(self, param: Union[Parameter, str], keep_field: bool = False) -> None:
"""Remove a parameter from the template, identified by *param*. """Remove a parameter from the template, identified by *param*.


If *param* is a :class:`.Parameter` object, it will be matched exactly, If *param* is a :class:`.Parameter` object, it will be matched exactly,
@@ -389,5 +415,5 @@ class Template(Node):
for i in reversed(to_remove): for i in reversed(to_remove):
self.params.pop(i) self.params.pop(i)


def __delitem__(self, param):
def __delitem__(self, param: Union[Parameter, str]) -> None:
return self.remove(param) return self.remove(param)

+ 17
- 7
src/mwparserfromhell/nodes/text.py Просмотреть файл

@@ -19,32 +19,42 @@
# SOFTWARE. # SOFTWARE.




from typing import TYPE_CHECKING, Any, Callable

from ._base import Node from ._base import Node


if TYPE_CHECKING:
from ..wikicode import Wikicode

__all__ = ["Text"] __all__ = ["Text"]




class Text(Node): class Text(Node):
"""Represents ordinary, unformatted text with no special properties.""" """Represents ordinary, unformatted text with no special properties."""


def __init__(self, value):
def __init__(self, value: Any):
super().__init__() super().__init__()
self.value = value self.value = value


def __str__(self):
def __str__(self) -> str:
return self.value return self.value


def __strip__(self, **kwargs):
return self
def __strip__(self, **kwargs: Any) -> str:
return str(self)


def __showtree__(self, write, get, mark):
def __showtree__(
self,
write: Callable[[str], None],
get: Callable[["Wikicode"], None],
mark: Callable[[], None],
) -> None:
write(str(self).encode("unicode_escape").decode("utf8")) write(str(self).encode("unicode_escape").decode("utf8"))


@property @property
def value(self):
def value(self) -> str:
"""The actual text itself.""" """The actual text itself."""
return self._value return self._value


@value.setter @value.setter
def value(self, newval):
def value(self, newval: Any) -> None:
self._value = str(newval) self._value = str(newval)

+ 22
- 12
src/mwparserfromhell/nodes/wikilink.py Просмотреть файл

@@ -19,36 +19,46 @@
# SOFTWARE. # SOFTWARE.




from typing import TYPE_CHECKING, Any, Optional, Generator, Callable

from ._base import Node from ._base import Node
from ..utils import parse_anything from ..utils import parse_anything


if TYPE_CHECKING:
from ..wikicode import Wikicode

__all__ = ["Wikilink"] __all__ = ["Wikilink"]




class Wikilink(Node): class Wikilink(Node):
"""Represents an internal wikilink, like ``[[Foo|Bar]]``.""" """Represents an internal wikilink, like ``[[Foo|Bar]]``."""


def __init__(self, title, text=None):
def __init__(self, title: Any, text: Any = None):
super().__init__() super().__init__()
self.title = title self.title = title
self.text = text self.text = text


def __str__(self):
def __str__(self) -> str:
if self.text is not None: if self.text is not None:
return "[[" + str(self.title) + "|" + str(self.text) + "]]" return "[[" + str(self.title) + "|" + str(self.text) + "]]"
return "[[" + str(self.title) + "]]" return "[[" + str(self.title) + "]]"


def __children__(self):
def __children__(self) -> Generator["Wikicode", None, None]:
yield self.title yield self.title
if self.text is not None: if self.text is not None:
yield self.text yield self.text


def __strip__(self, **kwargs):
def __strip__(self, **kwargs: Any) -> Optional[str]:
if self.text is not None: if self.text is not None:
return self.text.strip_code(**kwargs) return self.text.strip_code(**kwargs)
return self.title.strip_code(**kwargs) return self.title.strip_code(**kwargs)


def __showtree__(self, write, get, mark):
def __showtree__(
self,
write: Callable[[str], None],
get: Callable[["Wikicode"], None],
mark: Callable[[], None],
) -> None:
write("[[") write("[[")
get(self.title) get(self.title)
if self.text is not None: if self.text is not None:
@@ -58,21 +68,21 @@ class Wikilink(Node):
write("]]") write("]]")


@property @property
def title(self):
def title(self) -> "Wikicode":
"""The title of the linked page, as a :class:`.Wikicode` object.""" """The title of the linked page, as a :class:`.Wikicode` object."""
return self._title return self._title


@title.setter
def title(self, value: Any) -> None:
self._title = parse_anything(value)

@property @property
def text(self):
def text(self) -> Optional["Wikicode"]:
"""The text to display (if any), as a :class:`.Wikicode` object.""" """The text to display (if any), as a :class:`.Wikicode` object."""
return self._text return self._text


@title.setter
def title(self, value):
self._title = parse_anything(value)

@text.setter @text.setter
def text(self, value):
def text(self, value: Any) -> None:
if value is None: if value is None:
self._text = None self._text = None
else: else:


+ 95
- 43
src/mwparserfromhell/wikicode.py Просмотреть файл

@@ -20,6 +20,7 @@


import re import re
from itertools import chain from itertools import chain
from typing import Any, Callable, Generator, Iterable, Optional, Union, cast, overload


from .nodes import ( from .nodes import (
Argument, Argument,
@@ -55,15 +56,20 @@ class Wikicode(StringMixIn):


RECURSE_OTHERS = 2 RECURSE_OTHERS = 2


def __init__(self, nodes):
def __init__(self, nodes: list[Node]):
super().__init__() super().__init__()
self._nodes = nodes self._nodes = nodes


def __str__(self):
def __str__(self) -> str:
return "".join([str(node) for node in self.nodes]) return "".join([str(node) for node in self.nodes])


@staticmethod @staticmethod
def _get_children(node, contexts=False, restrict=None, parent=None):
def _get_children(
node: Node,
contexts: bool = False,
restrict: Optional[type] = None,
parent: Optional["Wikicode"] = None,
) -> Generator[Union[tuple[Optional["Wikicode"], Node], Node]]:
"""Iterate over all child :class:`.Node`\\ s of a given *node*.""" """Iterate over all child :class:`.Node`\\ s of a given *node*."""
yield (parent, node) if contexts else node yield (parent, node) if contexts else node
if restrict and isinstance(node, restrict): if restrict and isinstance(node, restrict):
@@ -74,14 +80,19 @@ class Wikicode(StringMixIn):
yield from sub yield from sub


@staticmethod @staticmethod
def _slice_replace(code, index, old, new):
def _slice_replace(code: "Wikicode", index: slice, old: str, new: str) -> None:
"""Replace the string *old* with *new* across *index* in *code*.""" """Replace the string *old* with *new* across *index* in *code*."""
nodes = [str(node) for node in code.get(index)] nodes = [str(node) for node in code.get(index)]
substring = "".join(nodes).replace(old, new) substring = "".join(nodes).replace(old, new)
code.nodes[index] = parse_anything(substring).nodes code.nodes[index] = parse_anything(substring).nodes


@staticmethod @staticmethod
def _build_matcher(matches, flags):
def _build_matcher(
matches: Union[
Callable[[Node], Union[bool, re.Match[str], None]], re.Pattern, str, None
],
flags: int,
) -> Callable[[Node], Union[bool, re.Match[str], None]]:
"""Helper for :meth:`_indexed_ifilter` and others. """Helper for :meth:`_indexed_ifilter` and others.


If *matches* is a function, return it. If it's a regex, return a If *matches* is a function, return it. If it's a regex, return a
@@ -95,8 +106,12 @@ class Wikicode(StringMixIn):
return lambda obj: True return lambda obj: True


def _indexed_ifilter( def _indexed_ifilter(
self, recursive=True, matches=None, flags=FLAGS, forcetype=None
):
self,
recursive: bool = True,
matches: Union[Callable[[Node], bool], re.Pattern, str, None] = None,
flags: int = FLAGS,
forcetype: Optional[type] = None,
) -> Generator[tuple[int, Node]]:
"""Iterate over nodes and their corresponding indices in the node list. """Iterate over nodes and their corresponding indices in the node list.


The arguments are interpreted as for :meth:`ifilter`. For each tuple The arguments are interpreted as for :meth:`ifilter`. For each tuple
@@ -105,12 +120,13 @@ class Wikicode(StringMixIn):
node itself, but will still contain it. node itself, but will still contain it.
""" """
match = self._build_matcher(matches, flags) match = self._build_matcher(matches, flags)
inodes: Iterable[tuple[int, Node]]
if recursive: if recursive:
restrict = forcetype if recursive == self.RECURSE_OTHERS else None restrict = forcetype if recursive == self.RECURSE_OTHERS else None


def getter(i, node):
def getter(i: int, node: Node) -> Generator[tuple[int, Node]]:
for ch in self._get_children(node, restrict=restrict): for ch in self._get_children(node, restrict=restrict):
yield (i, ch)
yield (i, cast(Node, ch))


inodes = chain(*(getter(i, n) for i, n in enumerate(self.nodes))) inodes = chain(*(getter(i, n) for i, n in enumerate(self.nodes)))
else: else:
@@ -119,7 +135,7 @@ class Wikicode(StringMixIn):
if (not forcetype or isinstance(node, forcetype)) and match(node): if (not forcetype or isinstance(node, forcetype)) and match(node):
yield (i, node) yield (i, node)


def _is_child_wikicode(self, obj, recursive=True):
def _is_child_wikicode(self, obj: "Wikicode", recursive: bool = True) -> bool:
"""Return whether the given :class:`.Wikicode` is a descendant.""" """Return whether the given :class:`.Wikicode` is a descendant."""


def deref(nodes): def deref(nodes):
@@ -140,7 +156,9 @@ class Wikicode(StringMixIn):
todo += list(node.__children__()) todo += list(node.__children__())
return False return False


def _do_strong_search(self, obj, recursive=True):
def _do_strong_search(
self, obj: Union[Node, "Wikicode"], recursive: bool = True
) -> tuple["Wikicode", slice]:
"""Search for the specific element *obj* within the node list. """Search for the specific element *obj* within the node list.


*obj* can be either a :class:`.Node` or a :class:`.Wikicode` object. If *obj* can be either a :class:`.Node` or a :class:`.Wikicode` object. If
@@ -171,7 +189,9 @@ class Wikicode(StringMixIn):


raise TypeError(obj) raise TypeError(obj)


def _do_weak_search(self, obj, recursive):
def _do_weak_search(
self, obj: Any, recursive: bool
) -> list[tuple[bool, "Wikicode", slice]]:
"""Search for an element that looks like *obj* within the node list. """Search for an element that looks like *obj* within the node list.


This follows the same rules as :meth:`_do_strong_search` with some This follows the same rules as :meth:`_do_strong_search` with some
@@ -192,7 +212,7 @@ class Wikicode(StringMixIn):
if not obj or obj not in self: if not obj or obj not in self:
raise ValueError(obj) raise ValueError(obj)
results = [] results = []
contexts = [self]
contexts: list["Wikicode"] = [self]
while contexts: while contexts:
context = contexts.pop() context = contexts.pop()
i = len(context.nodes) - 1 i = len(context.nodes) - 1
@@ -215,7 +235,9 @@ class Wikicode(StringMixIn):
results.append((False, self, slice(0, len(self.nodes)))) results.append((False, self, slice(0, len(self.nodes))))
return results return results


def _get_tree(self, code, lines, marker, indent):
def _get_tree(
self, code: "Wikicode", lines: list[str], marker: Any, indent: int
) -> list[str]:
"""Build a tree to illustrate the way the Wikicode object was parsed. """Build a tree to illustrate the way the Wikicode object was parsed.


The method that builds the actual tree is ``__showtree__`` of ``Node`` The method that builds the actual tree is ``__showtree__`` of ``Node``
@@ -227,7 +249,7 @@ class Wikicode(StringMixIn):
the starting indentation. the starting indentation.
""" """


def write(*args):
def write(*args: str) -> None:
"""Write a new line following the proper indentation rules.""" """Write a new line following the proper indentation rules."""
if lines and lines[-1] is marker: # Continue from the last line if lines and lines[-1] is marker: # Continue from the last line
lines.pop() # Remove the marker lines.pop() # Remove the marker
@@ -275,7 +297,7 @@ class Wikicode(StringMixIn):
setattr(cls, "filter_" + name, filt) setattr(cls, "filter_" + name, filt)


@property @property
def nodes(self):
def nodes(self) -> list[Node]:
"""A list of :class:`.Node` objects. """A list of :class:`.Node` objects.


This is the internal data actually stored within a :class:`.Wikicode` This is the internal data actually stored within a :class:`.Wikicode`
@@ -284,16 +306,22 @@ class Wikicode(StringMixIn):
return self._nodes return self._nodes


@nodes.setter @nodes.setter
def nodes(self, value):
def nodes(self, value: Union[list[Node], Any]) -> None:
if not isinstance(value, list): if not isinstance(value, list):
value = parse_anything(value).nodes value = parse_anything(value).nodes
self._nodes = value self._nodes = value


@overload
def get(self, index: int) -> Node: ...

@overload
def get(self, index: slice) -> list[Node]: ...

def get(self, index): def get(self, index):
"""Return the *index*\\ th node within the list of nodes.""" """Return the *index*\\ th node within the list of nodes."""
return self.nodes[index] return self.nodes[index]


def set(self, index, value):
def set(self, index: int, value: Any) -> None:
"""Set the ``Node`` at *index* to *value*. """Set the ``Node`` at *index* to *value*.


Raises :exc:`IndexError` if *index* is out of range, or Raises :exc:`IndexError` if *index* is out of range, or
@@ -311,7 +339,7 @@ class Wikicode(StringMixIn):
else: else:
self.nodes.pop(index) self.nodes.pop(index)


def contains(self, obj):
def contains(self, obj: Union[Node, "Wikicode", str]) -> bool:
"""Return whether this Wikicode object contains *obj*. """Return whether this Wikicode object contains *obj*.


If *obj* is a :class:`.Node` or :class:`.Wikicode` object, then we If *obj* is a :class:`.Node` or :class:`.Wikicode` object, then we
@@ -326,7 +354,7 @@ class Wikicode(StringMixIn):
return False return False
return True return True


def index(self, obj, recursive=False):
def index(self, obj: Union[Node, "Wikicode", str], recursive: bool = False) -> int:
"""Return the index of *obj* in the list of nodes. """Return the index of *obj* in the list of nodes.


Raises :exc:`ValueError` if *obj* is not found. If *recursive* is Raises :exc:`ValueError` if *obj* is not found. If *recursive* is
@@ -345,7 +373,7 @@ class Wikicode(StringMixIn):
return i return i
raise ValueError(obj) raise ValueError(obj)


def get_ancestors(self, obj):
def get_ancestors(self, obj: Union[Node, "Wikicode"]) -> list[Node]:
"""Return a list of all ancestor nodes of the :class:`.Node` *obj*. """Return a list of all ancestor nodes of the :class:`.Node` *obj*.


The list is ordered from the most shallow ancestor (greatest great- The list is ordered from the most shallow ancestor (greatest great-
@@ -362,7 +390,7 @@ class Wikicode(StringMixIn):
object. Will raise :exc:`ValueError` if it wasn't found. object. Will raise :exc:`ValueError` if it wasn't found.
""" """


def _get_ancestors(code, needle):
def _get_ancestors(code: "Wikicode", needle: Node) -> Union[list[Node], None]:
for node in code.nodes: for node in code.nodes:
if node is needle: if node is needle:
return [] return []
@@ -382,7 +410,7 @@ class Wikicode(StringMixIn):
raise ValueError(obj) raise ValueError(obj)
return ancestors return ancestors


def get_parent(self, obj):
def get_parent(self, obj: Union[Node, "Wikicode"]) -> Union[Node, None]:
"""Return the direct parent node of the :class:`.Node` *obj*. """Return the direct parent node of the :class:`.Node` *obj*.


This function is equivalent to calling :meth:`.get_ancestors` and This function is equivalent to calling :meth:`.get_ancestors` and
@@ -393,7 +421,7 @@ class Wikicode(StringMixIn):
ancestors = self.get_ancestors(obj) ancestors = self.get_ancestors(obj)
return ancestors[-1] if ancestors else None return ancestors[-1] if ancestors else None


def insert(self, index, value):
def insert(self, index: int, value: Any) -> None:
"""Insert *value* at *index* in the list of nodes. """Insert *value* at *index* in the list of nodes.


*value* can be anything parsable by :func:`.parse_anything`, which *value* can be anything parsable by :func:`.parse_anything`, which
@@ -403,7 +431,9 @@ class Wikicode(StringMixIn):
for node in reversed(nodes): for node in reversed(nodes):
self.nodes.insert(index, node) self.nodes.insert(index, node)


def insert_before(self, obj, value, recursive=True):
def insert_before(
self, obj: Union[Node, "Wikicode", str], value: Any, recursive: bool = True
) -> None:
"""Insert *value* immediately before *obj*. """Insert *value* immediately before *obj*.


*obj* can be either a string, a :class:`.Node`, or another *obj* can be either a string, a :class:`.Node`, or another
@@ -426,7 +456,9 @@ class Wikicode(StringMixIn):
obj = str(obj) obj = str(obj)
self._slice_replace(context, index, obj, str(value) + obj) self._slice_replace(context, index, obj, str(value) + obj)


def insert_after(self, obj, value, recursive=True):
def insert_after(
self, obj: Union[Node, "Wikicode", str], value: Any, recursive: bool = True
) -> None:
"""Insert *value* immediately after *obj*. """Insert *value* immediately after *obj*.


*obj* can be either a string, a :class:`.Node`, or another *obj* can be either a string, a :class:`.Node`, or another
@@ -449,7 +481,9 @@ class Wikicode(StringMixIn):
obj = str(obj) obj = str(obj)
self._slice_replace(context, index, obj, obj + str(value)) self._slice_replace(context, index, obj, obj + str(value))


def replace(self, obj, value, recursive=True):
def replace(
self, obj: Union[Node, "Wikicode", str], value: Any, recursive: bool = True
) -> None:
"""Replace *obj* with *value*. """Replace *obj* with *value*.


*obj* can be either a string, a :class:`.Node`, or another *obj* can be either a string, a :class:`.Node`, or another
@@ -475,7 +509,7 @@ class Wikicode(StringMixIn):
else: else:
self._slice_replace(context, index, str(obj), str(value)) self._slice_replace(context, index, str(obj), str(value))


def append(self, value):
def append(self, value: Any) -> None:
"""Insert *value* at the end of the list of nodes. """Insert *value* at the end of the list of nodes.


*value* can be anything parsable by :func:`.parse_anything`. *value* can be anything parsable by :func:`.parse_anything`.
@@ -484,7 +518,7 @@ class Wikicode(StringMixIn):
for node in nodes: for node in nodes:
self.nodes.append(node) self.nodes.append(node)


def remove(self, obj, recursive=True):
def remove(self, obj: Union[Node, "Wikicode", str], recursive: bool = True) -> None:
"""Remove *obj* from the list of nodes. """Remove *obj* from the list of nodes.


*obj* can be either a string, a :class:`.Node`, or another *obj* can be either a string, a :class:`.Node`, or another
@@ -508,7 +542,12 @@ class Wikicode(StringMixIn):
else: else:
self._slice_replace(context, index, str(obj), "") self._slice_replace(context, index, str(obj), "")


def matches(self, other):
def matches(
self,
other: Union[
Node, "Wikicode", str, bytes, Iterable[Union[Node, "Wikicode", str, bytes]]
],
) -> bool:
"""Do a loose equivalency test suitable for comparing page names. """Do a loose equivalency test suitable for comparing page names.


*other* can be any string-like object, including :class:`.Wikicode`, or *other* can be any string-like object, including :class:`.Wikicode`, or
@@ -517,7 +556,9 @@ class Wikicode(StringMixIn):
letter's case is normalized. Typical usage is letter's case is normalized. Typical usage is
``if template.name.matches("stub"): ...``. ``if template.name.matches("stub"): ...``.
""" """
normalize = lambda s: (s[0].upper() + s[1:]).replace("_", " ") if s else s
def normalize(s: str) -> str:
return (s[0].upper() + s[1:]).replace("_", " ") if s else s

this = normalize(self.strip_code().strip()) this = normalize(self.strip_code().strip())


if isinstance(other, (str, bytes, Wikicode, Node)): if isinstance(other, (str, bytes, Wikicode, Node)):
@@ -530,7 +571,13 @@ class Wikicode(StringMixIn):
return True return True
return False return False


def ifilter(self, recursive=True, matches=None, flags=FLAGS, forcetype=None):
def ifilter(
self,
recursive: bool = True,
matches: Union[Callable[[Node], bool], re.Pattern, str, None] = None,
flags: int = FLAGS,
forcetype: Optional[type] = None,
) -> Generator[Node]:
"""Iterate over nodes in our list matching certain conditions. """Iterate over nodes in our list matching certain conditions.


If *forcetype* is given, only nodes that are instances of this type (or If *forcetype* is given, only nodes that are instances of this type (or
@@ -557,7 +604,7 @@ class Wikicode(StringMixIn):
gen = self._indexed_ifilter(recursive, matches, flags, forcetype) gen = self._indexed_ifilter(recursive, matches, flags, forcetype)
return (node for i, node in gen) return (node for i, node in gen)


def filter(self, *args, **kwargs):
def filter(self, *args: Any, **kwargs: Any) -> list[Node]:
"""Return a list of nodes within our list matching certain conditions. """Return a list of nodes within our list matching certain conditions.


This is equivalent to calling :func:`list` on :meth:`ifilter`. This is equivalent to calling :func:`list` on :meth:`ifilter`.
@@ -566,13 +613,13 @@ class Wikicode(StringMixIn):


def get_sections( def get_sections(
self, self,
levels=None,
matches=None,
flags=FLAGS,
flat=False,
include_lead=None,
include_headings=True,
):
levels: Optional[Iterable[int]] = None,
matches: Union[Callable[[Node], bool], re.Pattern, str, None] = None,
flags: int = FLAGS,
flat: bool = False,
include_lead: Optional[bool] = None,
include_headings: bool = True,
) -> list["Wikicode"]:
"""Return a list of sections within the page. """Return a list of sections within the page.


Sections are returned as :class:`.Wikicode` objects with a shared node Sections are returned as :class:`.Wikicode` objects with a shared node
@@ -601,7 +648,7 @@ class Wikicode(StringMixIn):
sections = [] # Tuples of (index_of_first_node, section) sections = [] # Tuples of (index_of_first_node, section)
# Tuples of (index, heading), where index and heading.level are both # Tuples of (index, heading), where index and heading.level are both
# monotonically increasing # monotonically increasing
open_headings = []
open_headings: list[tuple[int, Heading]] = []


# Add the lead section if appropriate: # Add the lead section if appropriate:
if include_lead or not (include_lead is not None or matches or levels): if include_lead or not (include_lead is not None or matches or levels):
@@ -638,7 +685,12 @@ class Wikicode(StringMixIn):
# Ensure that earlier sections are earlier in the returned list: # Ensure that earlier sections are earlier in the returned list:
return [section for i, section in sorted(sections)] return [section for i, section in sorted(sections)]


def strip_code(self, normalize=True, collapse=True, keep_template_params=False):
def strip_code(
self,
normalize: bool = True,
collapse: bool = True,
keep_template_params: bool = False,
) -> str:
"""Return a rendered string without unprintable code such as templates. """Return a rendered string without unprintable code such as templates.


The way a node is stripped is handled by the The way a node is stripped is handled by the
@@ -674,7 +726,7 @@ class Wikicode(StringMixIn):
return stripped return stripped
return "".join(nodes) return "".join(nodes)


def get_tree(self):
def get_tree(self) -> str:
"""Return a hierarchical tree representation of the object. """Return a hierarchical tree representation of the object.


The representation is a string makes the most sense printed. It is The representation is a string makes the most sense printed. It is


Загрузка…
Отмена
Сохранить