diff --git a/mypy.ini b/mypy.ini
new file mode 100644
index 0000000..622c3c6
--- /dev/null
+++ b/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
diff --git a/src/mwparserfromhell/definitions.py b/src/mwparserfromhell/definitions.py
index 3258063..14c4ade 100644
--- a/src/mwparserfromhell/definitions.py
+++ b/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 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 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 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 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 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."""
scheme = scheme.lower()
if slashes:
diff --git a/src/mwparserfromhell/nodes/_base.py b/src/mwparserfromhell/nodes/_base.py
index 8ea36d3..f34e78d 100644
--- a/src/mwparserfromhell/nodes/_base.py
+++ b/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
# SOFTWARE.
+from typing import TYPE_CHECKING, Any, Generator, Optional, Callable
+
from ..string_mixin import StringMixIn
+if TYPE_CHECKING:
+ from ..wikicode import Wikicode
+
__all__ = ["Node"]
@@ -37,16 +42,21 @@ class Node(StringMixIn):
of the node, if desired, for :meth:`~.Wikicode.get_tree`.
"""
- def __str__(self):
+ def __str__(self) -> str:
raise NotImplementedError()
- def __children__(self):
+ def __children__(self) -> Generator["Wikicode"]:
return
# pylint: disable=unreachable
yield # pragma: no cover (this is a generator that yields nothing)
- def __strip__(self, **kwargs):
+ def __strip__(self, **kwargs: Any) -> Optional[str]:
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))
diff --git a/src/mwparserfromhell/nodes/argument.py b/src/mwparserfromhell/nodes/argument.py
index f590245..48e9514 100644
--- a/src/mwparserfromhell/nodes/argument.py
+++ b/src/mwparserfromhell/nodes/argument.py
@@ -19,37 +19,47 @@
# SOFTWARE.
+from typing import TYPE_CHECKING, Any, Generator, Optional, Callable
+
from ._base import Node
from ..utils import parse_anything
+if TYPE_CHECKING:
+ from ..wikicode import Wikicode
+
__all__ = ["Argument"]
class Argument(Node):
"""Represents a template argument substitution, like ``{{{foo}}}``."""
- def __init__(self, name, default=None):
+ def __init__(self, name: Any, default: Any = None):
super().__init__()
self.name = name
self.default = default
- def __str__(self):
+ def __str__(self) -> str:
start = "{{{" + str(self.name)
if self.default is not None:
return start + "|" + str(self.default) + "}}}"
return start + "}}}"
- def __children__(self):
+ def __children__(self) -> Generator["Wikicode"]:
yield self.name
if self.default is not None:
yield self.default
- def __strip__(self, **kwargs):
+ def __strip__(self, **kwargs: Any) -> Optional[str]:
if self.default is not None:
return self.default.strip_code(**kwargs)
return None
- def __showtree__(self, write, get, mark):
+ def __showtree__(
+ self,
+ write: Callable[[str], None],
+ get: Callable[["Wikicode"], None],
+ mark: Callable[[], None],
+ ) -> None:
write("{{{")
get(self.name)
if self.default is not None:
@@ -59,12 +69,16 @@ class Argument(Node):
write("}}}")
@property
- def name(self):
+ def name(self) -> "Wikicode":
"""The name of the argument to substitute."""
return self._name
+ @name.setter
+ def name(self, value: Any) -> None:
+ self._name = parse_anything(value)
+
@property
- def default(self):
+ def default(self) -> Optional["Wikicode"]:
"""The default value to substitute if none is passed.
This will be ``None`` if the argument wasn't defined with one. The
@@ -74,12 +88,8 @@ class Argument(Node):
"""
return self._default
- @name.setter
- def name(self, value):
- self._name = parse_anything(value)
-
@default.setter
- def default(self, default):
+ def default(self, default: Any) -> None:
if default is None:
self._default = None
else:
diff --git a/src/mwparserfromhell/nodes/comment.py b/src/mwparserfromhell/nodes/comment.py
index f2a6a6d..2631efc 100644
--- a/src/mwparserfromhell/nodes/comment.py
+++ b/src/mwparserfromhell/nodes/comment.py
@@ -19,6 +19,8 @@
# SOFTWARE.
+from typing import Any
+
from ._base import Node
__all__ = ["Comment"]
@@ -27,18 +29,18 @@ __all__ = ["Comment"]
class Comment(Node):
"""Represents a hidden HTML comment, like ````."""
- def __init__(self, contents):
+ def __init__(self, contents: str):
super().__init__()
self.contents = contents
- def __str__(self):
+ def __str__(self) -> str:
return ""
@property
- def contents(self):
+ def contents(self) -> str:
"""The hidden text contained between ````."""
return self._contents
@contents.setter
- def contents(self, value):
+ def contents(self, value: Any) -> None:
self._contents = str(value)
diff --git a/src/mwparserfromhell/nodes/external_link.py b/src/mwparserfromhell/nodes/external_link.py
index 85f1fae..70a23dd 100644
--- a/src/mwparserfromhell/nodes/external_link.py
+++ b/src/mwparserfromhell/nodes/external_link.py
@@ -19,23 +19,34 @@
# SOFTWARE.
+from typing import TYPE_CHECKING, Any, Optional, Generator, Callable
+
from ._base import Node
from ..utils import parse_anything
+if TYPE_CHECKING:
+ from ..wikicode import Wikicode
+
__all__ = ["ExternalLink"]
class ExternalLink(Node):
"""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__()
self.url = url
self.title = title
self.brackets = brackets
self.suppress_space = suppress_space
- def __str__(self):
+ def __str__(self) -> str:
if self.brackets:
if self.title is not None:
if self.suppress_space is True:
@@ -44,19 +55,24 @@ class ExternalLink(Node):
return "[" + str(self.url) + "]"
return str(self.url)
- def __children__(self):
+ def __children__(self) -> Generator["Wikicode"]:
yield self.url
if self.title is not None:
yield self.title
- def __strip__(self, **kwargs):
+ def __strip__(self, **kwargs: Any) -> Optional[str]:
if self.brackets:
if self.title:
return self.title.strip_code(**kwargs)
return None
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:
write("[")
get(self.url)
@@ -66,31 +82,31 @@ class ExternalLink(Node):
write("]")
@property
- def url(self):
+ def url(self) -> "Wikicode":
"""The URL of the link target, as a :class:`.Wikicode` object."""
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
- def url(self, value):
+ def url(self, value: Any) -> None:
# pylint: disable=import-outside-toplevel
from ..parser import contexts
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
- def title(self, value):
+ def title(self, value: Any) -> None:
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
- def brackets(self, value):
+ def brackets(self, value: bool) -> None:
self._brackets = bool(value)
diff --git a/src/mwparserfromhell/nodes/extras/attribute.py b/src/mwparserfromhell/nodes/extras/attribute.py
index fc3421a..98f8c13 100644
--- a/src/mwparserfromhell/nodes/extras/attribute.py
+++ b/src/mwparserfromhell/nodes/extras/attribute.py
@@ -19,9 +19,14 @@
# SOFTWARE.
+from typing import TYPE_CHECKING, Any, Optional
+
from ...string_mixin import StringMixIn
from ...utils import parse_anything
+if TYPE_CHECKING:
+ from ...wikicode import Wikicode
+
__all__ = ["Attribute"]
@@ -35,23 +40,28 @@ class Attribute(StringMixIn):
def __init__(
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__()
+
+ self._pad_first: str
+ self._pad_before_eq: str
+ self._pad_after_eq: str
+
self.name = name
- self._quotes = None
+ self._quotes: Optional[str] = None
self.value = value
self.quotes = quotes
self.pad_first = pad_first
self.pad_before_eq = pad_before_eq
self.pad_after_eq = pad_after_eq
- def __str__(self):
+ def __str__(self) -> str:
result = self.pad_first + str(self.name) + self.pad_before_eq
if self.value is not None:
result += "=" + self.pad_after_eq
@@ -61,11 +71,11 @@ class Attribute(StringMixIn):
return result
@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."""
- if not val:
+ if not value:
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):
return None
if "'" in val and '"' not in val:
@@ -74,7 +84,7 @@ class Attribute(StringMixIn):
return "'"
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."""
if not value:
setattr(self, attr, "")
@@ -85,49 +95,29 @@ class Attribute(StringMixIn):
setattr(self, attr, value)
@staticmethod
- def coerce_quotes(quotes):
+ def coerce_quotes(quotes: Any) -> Optional[str]:
"""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
- def name(self):
+ def name(self) -> "Wikicode":
"""The name of the attribute as a :class:`.Wikicode` object."""
return self._name
+ @name.setter
+ def name(self, value: Any) -> None:
+ self._name = parse_anything(value)
+
@property
- def value(self):
+ def value(self) -> Optional["Wikicode"]:
"""The value of the attribute as a :class:`.Wikicode` object."""
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
- def value(self, newval):
+ def value(self, newval: Any) -> None:
if newval is None:
self._value = None
else:
@@ -137,21 +127,41 @@ class Attribute(StringMixIn):
self._quotes = quotes[0]
self._value = code
+ @property
+ def quotes(self) -> Optional[str]:
+ """How to enclose the attribute value. ``"``, ``'``, or ``None``."""
+ return self._quotes
+
@quotes.setter
- def quotes(self, value):
+ def quotes(self, value: Any) -> None:
value = self.coerce_quotes(value)
if not value and self._value_needs_quotes(self.value):
raise ValueError("attribute value requires quotes")
self._quotes = value
+ @property
+ def pad_first(self) -> str:
+ """Spacing to insert right before the attribute."""
+ return self._pad_first
+
@pad_first.setter
- def pad_first(self, value):
+ def pad_first(self, value: str) -> None:
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
- def pad_before_eq(self, value):
+ def pad_before_eq(self, value: str) -> None:
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
- def pad_after_eq(self, value):
+ def pad_after_eq(self, value: str) -> None:
self._set_padding("_pad_after_eq", value)
diff --git a/src/mwparserfromhell/nodes/extras/parameter.py b/src/mwparserfromhell/nodes/extras/parameter.py
index 6352f5f..dac3ecc 100644
--- a/src/mwparserfromhell/nodes/extras/parameter.py
+++ b/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
# SOFTWARE.
+from typing import TYPE_CHECKING, Any, Union
+
import re
from ...string_mixin import StringMixIn
from ...utils import parse_anything
+if TYPE_CHECKING:
+ from ...wikicode import Wikicode
+
__all__ = ["Parameter"]
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
Parameters: one whose name is ``"1"``, value is ``"bar"``, and ``showkey``
@@ -35,47 +40,47 @@ class Parameter(StringMixIn):
``showkey`` is ``True``.
"""
- def __init__(self, name, value, showkey=True):
+ def __init__(self, name: Any, value: Any, showkey: bool = True) -> None:
super().__init__()
self.name = name
self.value = value
self.showkey = showkey
- def __str__(self):
+ def __str__(self) -> str:
if self.showkey:
return str(self.name) + "=" + str(self.value)
return str(self.value)
@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 re.match(r"[1-9][0-9]*$", str(key).strip())
@property
- def name(self):
+ def name(self) -> "Wikicode":
"""The name of the parameter as a :class:`.Wikicode` object."""
return self._name
+ @name.setter
+ def name(self, newval: Any) -> None:
+ self._name = parse_anything(newval)
+
@property
- def value(self):
+ def value(self) -> "Wikicode":
"""The value of the parameter as a :class:`.Wikicode` object."""
return self._value
+ @value.setter
+ def value(self, newval: Any) -> None:
+ self._value = parse_anything(newval)
+
@property
- def showkey(self):
+ def showkey(self) -> bool:
"""Whether to show the parameter's key (i.e., its "name")."""
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
- def showkey(self, newval):
+ def showkey(self, newval: Any) -> None:
newval = bool(newval)
if not newval and not self.can_hide_key(self.name):
raise ValueError("parameter key {!r} cannot be hidden".format(self.name))
diff --git a/src/mwparserfromhell/nodes/heading.py b/src/mwparserfromhell/nodes/heading.py
index b65cf29..9c1b122 100644
--- a/src/mwparserfromhell/nodes/heading.py
+++ b/src/mwparserfromhell/nodes/heading.py
@@ -19,50 +19,58 @@
# SOFTWARE.
+from typing import TYPE_CHECKING, Any, Generator, Callable, Optional
+
from ._base import Node
from ..utils import parse_anything
-__all__ = ["Heading"]
+if TYPE_CHECKING:
+ from ..wikicode import Wikicode
class Heading(Node):
"""Represents a section heading in wikicode, like ``== Foo ==``."""
- def __init__(self, title, level):
+ def __init__(self, title: Any, level: int):
super().__init__()
self.title = title
self.level = level
- def __str__(self):
+ def __str__(self) -> str:
return ("=" * self.level) + str(self.title) + ("=" * self.level)
- def __children__(self):
+ def __children__(self) -> Generator["Wikicode", None, None]:
yield self.title
- def __strip__(self, **kwargs):
+ def __strip__(self, **kwargs: Any) -> Optional[str]:
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)
get(self.title)
write("=" * self.level)
@property
- def title(self):
+ def title(self) -> "Wikicode":
"""The title of the heading, as a :class:`.Wikicode` object."""
return self._title
+ @title.setter
+ def title(self, value: Any) -> None:
+ self._title = parse_anything(value)
+
@property
- def level(self):
+ def level(self) -> int:
"""The heading level, as an integer between 1 and 6, inclusive."""
return self._level
- @title.setter
- def title(self, value):
- self._title = parse_anything(value)
-
@level.setter
- def level(self, value):
+ def level(self, value: int) -> None:
value = int(value)
if value < 1 or value > 6:
raise ValueError(value)
diff --git a/src/mwparserfromhell/nodes/html_entity.py b/src/mwparserfromhell/nodes/html_entity.py
index 2322e38..2e77120 100644
--- a/src/mwparserfromhell/nodes/html_entity.py
+++ b/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
# SOFTWARE.
+from typing import Any, Optional
import html.entities as htmlentities
from ._base import Node
@@ -28,9 +29,15 @@ __all__ = ["HTMLEntity"]
class HTMLEntity(Node):
"""Represents an HTML entity, like `` ``, 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__()
- self._value = value
+ self._value = str(value)
if named is None: # Try to guess whether or not the entity is named
try:
int(value)
@@ -49,50 +56,25 @@ class HTMLEntity(Node):
self._hexadecimal = hexadecimal
self._hex_char = hex_char
- def __str__(self):
+ def __str__(self) -> str:
if self.named:
return "&{};".format(self.value)
if self.hexadecimal:
return "{}{};".format(self.hex_char, self.value)
return "{};".format(self.value)
- def __strip__(self, **kwargs):
+ def __strip__(self, **kwargs: Any) -> Optional[str]:
if kwargs.get("normalize"):
return self.normalize()
- return self
+ return str(self)
@property
- def value(self):
+ def value(self) -> str:
"""The string value of the HTML entity."""
return self._value
- @property
- def named(self):
- """Whether the entity is a string name for a codepoint or an integer.
-
- For example, ``Σ``, ``Σ``, and ``Σ`` 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 ``"ሴ"`` is ``"x"``, whereas the
- hex_char of ``"ሴ"`` is ``"X"``. Lowercase and uppercase ``x``
- are the only values supported.
- """
- return self._hex_char
-
@value.setter
- def value(self, newval):
+ def value(self, newval: Any) -> None:
newval = str(newval)
try:
int(newval)
@@ -122,36 +104,63 @@ class HTMLEntity(Node):
self._named = False
self._value = newval
+ @property
+ def named(self) -> bool:
+ """Whether the entity is a string name for a codepoint or an integer.
+
+ For example, ``Σ``, ``Σ``, and ``Σ`` 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
- def named(self, newval):
+ def named(self, newval: bool) -> None:
newval = bool(newval)
if newval and self.value not in htmlentities.entitydefs:
raise ValueError("entity value {!r} is not a valid name".format(self.value))
+
if not newval:
try:
int(self.value, 16)
except ValueError as exc:
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
self._named = newval
+ @property
+ def hexadecimal(self) -> bool:
+ """If unnamed, this is whether the value is hexadecimal or decimal."""
+ return self._hexadecimal
+
@hexadecimal.setter
- def hexadecimal(self, newval):
+ def hexadecimal(self, newval: bool) -> None:
newval = bool(newval)
if newval and self.named:
raise ValueError("a named entity cannot be hexadecimal")
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 ``"ሴ"`` is ``"x"``, whereas the
+ hex_char of ``"ሴ"`` is ``"X"``. Lowercase and uppercase ``x``
+ are the only values supported.
+ """
+ return self._hex_char
+
@hex_char.setter
- def hex_char(self, newval):
+ def hex_char(self, newval: str) -> None:
newval = str(newval)
if newval not in ("x", "X"):
raise ValueError(newval)
self._hex_char = newval
- def normalize(self):
+ def normalize(self) -> str:
"""Return the unicode character represented by the HTML entity."""
if self.named:
return chr(htmlentities.name2codepoint[self.value])
diff --git a/src/mwparserfromhell/nodes/tag.py b/src/mwparserfromhell/nodes/tag.py
index 027b09c..bf4ad40 100644
--- a/src/mwparserfromhell/nodes/tag.py
+++ b/src/mwparserfromhell/nodes/tag.py
@@ -19,11 +19,16 @@
# SOFTWARE.
+from typing import TYPE_CHECKING, Any, Callable, Optional, List, Generator
+
from ._base import Node
from .extras import Attribute
from ..definitions import is_visible
from ..utils import parse_anything
+if TYPE_CHECKING:
+ from ..wikicode import Wikicode
+
__all__ = ["Tag"]
@@ -32,19 +37,22 @@ class Tag(Node):
def __init__(
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__()
+ self._attrs: List[Attribute]
+ self._closing_wiki_markup: Optional[str]
+
self.tag = tag
self.contents = contents
self._attrs = attrs if attrs else []
@@ -60,7 +68,7 @@ class Tag(Node):
if closing_wiki_markup is not None:
self.closing_wiki_markup = closing_wiki_markup
- def __str__(self):
+ def __str__(self) -> str:
if self.wiki_markup:
if self.attributes:
attrs = "".join([str(attr) for attr in self.attributes])
@@ -90,7 +98,7 @@ class Tag(Node):
result += "" + str(self.closing_tag) + ">"
return result
- def __children__(self):
+ def __children__(self) -> Generator["Wikicode"]:
if not self.wiki_markup:
yield self.tag
for attr in self.attributes:
@@ -102,12 +110,17 @@ class Tag(Node):
if not self.wiki_markup and self.closing_tag:
yield self.closing_tag
- def __strip__(self, **kwargs):
- if self.contents and is_visible(self.tag):
+ def __strip__(self, **kwargs: Any) -> Optional[str]:
+ if self.contents and is_visible(str(self.tag)):
return self.contents.strip_code(**kwargs)
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 "<")
get(self.tag)
for attr in self.attributes:
@@ -127,17 +140,25 @@ class Tag(Node):
write(">")
@property
- def tag(self):
+ def tag(self) -> "Wikicode":
"""The tag itself, as a :class:`.Wikicode` object."""
return self._tag
+ @tag.setter
+ def tag(self, value: Any) -> None:
+ self._tag = self._closing_tag = parse_anything(value)
+
@property
- def contents(self):
+ def contents(self) -> "Wikicode":
"""The contents of the tag, as a :class:`.Wikicode` object."""
return self._contents
+ @contents.setter
+ def contents(self, value: Any) -> None:
+ self._contents = parse_anything(value)
+
@property
- def attributes(self):
+ def attributes(self) -> List[Attribute]:
"""The list of attributes affecting the tag.
Each attribute is an instance of :class:`.Attribute`.
@@ -145,7 +166,7 @@ class Tag(Node):
return self._attrs
@property
- def wiki_markup(self):
+ def wiki_markup(self) -> Optional[str]:
"""The wikified version of a tag to show instead of HTML.
If set to a value, this will be displayed instead of the brackets.
@@ -154,13 +175,23 @@ class Tag(Node):
"""
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
- def self_closing(self):
+ def self_closing(self) -> bool:
"""Whether the tag is self-closing with no content (like ``
``)."""
return self._self_closing
+ @self_closing.setter
+ def self_closing(self, value: bool) -> None:
+ self._self_closing = bool(value)
+
@property
- def invalid(self):
+ def invalid(self) -> bool:
"""Whether the tag starts with a backslash after the opening bracket.
This makes the tag look like a lone close tag. It is technically
@@ -170,8 +201,12 @@ class Tag(Node):
"""
return self._invalid
+ @invalid.setter
+ def invalid(self, value: bool) -> None:
+ self._invalid = bool(value)
+
@property
- def implicit(self):
+ def implicit(self) -> bool:
"""Whether the tag is implicitly self-closing, with no ending slash.
This is only possible for specific "single" tags like ``
`` and
@@ -180,13 +215,27 @@ class Tag(Node):
"""
return self._implicit
+ @implicit.setter
+ def implicit(self, value: bool) -> None:
+ self._implicit = bool(value)
+
@property
- def padding(self):
+ def padding(self) -> str:
"""Spacing to insert before the first closing ``>``."""
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
- def closing_tag(self):
+ def closing_tag(self) -> "Wikicode":
"""The closing tag, as a :class:`.Wikicode` object.
This will usually equal :attr:`tag`, unless there is additional
@@ -194,76 +243,40 @@ class Tag(Node):
"""
return self._closing_tag
+ @closing_tag.setter
+ def closing_tag(self, value: Any) -> None:
+ self._closing_tag = parse_anything(value)
+
@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.
Essentially the wiki equivalent of the TagCloseOpen.
"""
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
- def closing_wiki_markup(self):
+ def closing_wiki_markup(self) -> Optional[str]:
"""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
- 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
``None``.
"""
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
- 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
- def has(self, name):
+ def has(self, name: str) -> bool:
"""Return whether any attribute in the tag has the given *name*.
Note that a tag may have multiple attributes with the same name, but
@@ -274,7 +287,7 @@ class Tag(Node):
return True
return False
- def get(self, name):
+ def get(self, name: str) -> Attribute:
"""Get the attribute with the given *name*.
The returned object is a :class:`.Attribute` instance. Raises
@@ -289,13 +302,13 @@ class Tag(Node):
def add(
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*.
*name* and *value* can be anything parsable by
@@ -318,7 +331,7 @@ class Tag(Node):
self.attributes.append(attr)
return attr
- def remove(self, name):
+ def remove(self, name: str) -> None:
"""Remove all attributes with the given *name*.
Raises :exc:`ValueError` if none were found.
diff --git a/src/mwparserfromhell/nodes/template.py b/src/mwparserfromhell/nodes/template.py
index e3abe79..50f4193 100644
--- a/src/mwparserfromhell/nodes/template.py
+++ b/src/mwparserfromhell/nodes/template.py
@@ -19,6 +19,18 @@
# SOFTWARE.
from collections import defaultdict
import re
+from typing import (
+ TYPE_CHECKING,
+ Any,
+ Optional,
+ Union,
+ List,
+ Dict,
+ Callable,
+ Generator,
+ Mapping,
+ cast,
+)
from ._base import Node
from .html_entity import HTMLEntity
@@ -26,43 +38,49 @@ from .text import Text
from .extras import Parameter
from ..utils import parse_anything
+if TYPE_CHECKING:
+ from ..wikicode import Wikicode
+
__all__ = ["Template"]
FLAGS = re.DOTALL | re.UNICODE
# Used to allow None as a valid fallback value
_UNSET = object()
+
class Template(Node):
"""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__()
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:
params = "|".join([str(param) for param in self.params])
return "{{" + str(self.name) + "|" + params + "}}"
return "{{" + str(self.name) + "}}"
- def __children__(self):
+ def __children__(self) -> Generator["Wikicode"]:
yield self.name
for param in self.params:
if param.showkey:
yield param.name
yield param.value
- def __strip__(self, **kwargs):
+ def __strip__(self, **kwargs: Any) -> Optional[str]:
if kwargs.get("keep_template_params"):
parts = [param.value.strip_code(**kwargs) for param in self.params]
return " ".join(part for part in parts if part)
return None
- def __showtree__(self, write, get, mark):
+ def __showtree__(
+ self,
+ write: Callable[[str], None],
+ get: Callable[["Wikicode"], None],
+ mark: Callable[[], None],
+ ) -> None:
write("{{")
get(self.name)
for param in self.params:
@@ -75,7 +93,7 @@ class Template(Node):
write("}}")
@staticmethod
- def _surface_escape(code, char):
+ def _surface_escape(code: "Wikicode", char: str) -> None:
"""Return *code* with *char* escaped as an HTML entity.
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)
@staticmethod
- def _select_theory(theories):
+ def _select_theory(theories: Dict[str, int]) -> Optional[str]:
"""Return the most likely spacing convention given different options.
Given a dictionary of convention options as keys and their occurrence
@@ -104,7 +122,7 @@ class Template(Node):
return None
@staticmethod
- def _blank_param_value(value):
+ def _blank_param_value(value: "Wikicode") -> None:
"""Remove the content from *value* while keeping its whitespace.
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)
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.
This will examine the existing parameters and use
:meth:`_select_theory` to determine if there are any preferred styles
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:
if not param.showkey:
continue
@@ -149,14 +169,14 @@ class Template(Node):
after = self._select_theory(after_theories)
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*."""
if not self.params[i].showkey:
for param in self.params[i + 1 :]:
if not param.showkey:
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."""
for i, param in enumerate(self.params):
if param is needle:
@@ -168,7 +188,7 @@ class Template(Node):
return
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.
If one exists, we should remove the given one rather than blanking it.
@@ -182,20 +202,20 @@ class Template(Node):
return False
@property
- def name(self):
+ def name(self) -> "Wikicode":
"""The name of the template, as a :class:`.Wikicode` object."""
return self._name
+ @name.setter
+ def name(self, value: Any) -> None:
+ self._name = parse_anything(value)
+
@property
- def params(self):
+ def params(self) -> List[Parameter]:
"""The list of parameters contained within the template."""
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*.
With *ignore_empty*, ``False`` will be returned even if the template
@@ -211,11 +231,11 @@ class Template(Node):
return True
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`."""
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*.
The returned object is a :class:`.Parameter` instance. Raises
@@ -232,12 +252,18 @@ class Template(Node):
raise ValueError(name)
return default
- def __getitem__(self, name):
+ def __getitem__(self, name: Union[str, Any]) -> Parameter:
return self.get(name)
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*.
*name* and *value* can be anything parsable by
@@ -328,21 +354,21 @@ class Template(Node):
self.params.append(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.
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,
- matching the same arguments in :meth:`add` (showkey, before,
+ matching the same arguments in :meth:`add` (showkey, before,
after, preserve_spacing)
"""
for name, value in params.items():
self.add(name, value, **kwargs)
- def __setitem__(self, name, value):
+ def __setitem__(self, name: Any, value: Any) -> Parameter:
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*.
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):
self.params.pop(i)
- def __delitem__(self, param):
+ def __delitem__(self, param: Union[Parameter, str]) -> None:
return self.remove(param)
diff --git a/src/mwparserfromhell/nodes/text.py b/src/mwparserfromhell/nodes/text.py
index 6fea574..6847645 100644
--- a/src/mwparserfromhell/nodes/text.py
+++ b/src/mwparserfromhell/nodes/text.py
@@ -19,32 +19,42 @@
# SOFTWARE.
+from typing import TYPE_CHECKING, Any, Callable
+
from ._base import Node
+if TYPE_CHECKING:
+ from ..wikicode import Wikicode
+
__all__ = ["Text"]
class Text(Node):
"""Represents ordinary, unformatted text with no special properties."""
- def __init__(self, value):
+ def __init__(self, value: Any):
super().__init__()
self.value = value
- def __str__(self):
+ def __str__(self) -> str:
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"))
@property
- def value(self):
+ def value(self) -> str:
"""The actual text itself."""
return self._value
@value.setter
- def value(self, newval):
+ def value(self, newval: Any) -> None:
self._value = str(newval)
diff --git a/src/mwparserfromhell/nodes/wikilink.py b/src/mwparserfromhell/nodes/wikilink.py
index 870cba9..3378778 100644
--- a/src/mwparserfromhell/nodes/wikilink.py
+++ b/src/mwparserfromhell/nodes/wikilink.py
@@ -19,36 +19,46 @@
# SOFTWARE.
+from typing import TYPE_CHECKING, Any, Optional, Generator, Callable
+
from ._base import Node
from ..utils import parse_anything
+if TYPE_CHECKING:
+ from ..wikicode import Wikicode
+
__all__ = ["Wikilink"]
class Wikilink(Node):
"""Represents an internal wikilink, like ``[[Foo|Bar]]``."""
- def __init__(self, title, text=None):
+ def __init__(self, title: Any, text: Any = None):
super().__init__()
self.title = title
self.text = text
- def __str__(self):
+ def __str__(self) -> str:
if self.text is not None:
return "[[" + str(self.title) + "|" + str(self.text) + "]]"
return "[[" + str(self.title) + "]]"
- def __children__(self):
+ def __children__(self) -> Generator["Wikicode", None, None]:
yield self.title
if self.text is not None:
yield self.text
- def __strip__(self, **kwargs):
+ def __strip__(self, **kwargs: Any) -> Optional[str]:
if self.text is not None:
return self.text.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("[[")
get(self.title)
if self.text is not None:
@@ -58,21 +68,21 @@ class Wikilink(Node):
write("]]")
@property
- def title(self):
+ def title(self) -> "Wikicode":
"""The title of the linked page, as a :class:`.Wikicode` object."""
return self._title
+ @title.setter
+ def title(self, value: Any) -> None:
+ self._title = parse_anything(value)
+
@property
- def text(self):
+ def text(self) -> Optional["Wikicode"]:
"""The text to display (if any), as a :class:`.Wikicode` object."""
return self._text
- @title.setter
- def title(self, value):
- self._title = parse_anything(value)
-
@text.setter
- def text(self, value):
+ def text(self, value: Any) -> None:
if value is None:
self._text = None
else:
diff --git a/src/mwparserfromhell/wikicode.py b/src/mwparserfromhell/wikicode.py
index 4d4f9b3..a9cdce9 100644
--- a/src/mwparserfromhell/wikicode.py
+++ b/src/mwparserfromhell/wikicode.py
@@ -20,6 +20,7 @@
import re
from itertools import chain
+from typing import Any, Callable, Generator, Iterable, Optional, Union, cast, overload
from .nodes import (
Argument,
@@ -55,15 +56,20 @@ class Wikicode(StringMixIn):
RECURSE_OTHERS = 2
- def __init__(self, nodes):
+ def __init__(self, nodes: list[Node]):
super().__init__()
self._nodes = nodes
- def __str__(self):
+ def __str__(self) -> str:
return "".join([str(node) for node in self.nodes])
@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*."""
yield (parent, node) if contexts else node
if restrict and isinstance(node, restrict):
@@ -74,14 +80,19 @@ class Wikicode(StringMixIn):
yield from sub
@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*."""
nodes = [str(node) for node in code.get(index)]
substring = "".join(nodes).replace(old, new)
code.nodes[index] = parse_anything(substring).nodes
@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.
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
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.
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.
"""
match = self._build_matcher(matches, flags)
+ inodes: Iterable[tuple[int, Node]]
if recursive:
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):
- yield (i, ch)
+ yield (i, cast(Node, ch))
inodes = chain(*(getter(i, n) for i, n in enumerate(self.nodes)))
else:
@@ -119,7 +135,7 @@ class Wikicode(StringMixIn):
if (not forcetype or isinstance(node, forcetype)) and match(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."""
def deref(nodes):
@@ -140,7 +156,9 @@ class Wikicode(StringMixIn):
todo += list(node.__children__())
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.
*obj* can be either a :class:`.Node` or a :class:`.Wikicode` object. If
@@ -171,7 +189,9 @@ class Wikicode(StringMixIn):
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.
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:
raise ValueError(obj)
results = []
- contexts = [self]
+ contexts: list["Wikicode"] = [self]
while contexts:
context = contexts.pop()
i = len(context.nodes) - 1
@@ -215,7 +235,9 @@ class Wikicode(StringMixIn):
results.append((False, self, slice(0, len(self.nodes))))
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.
The method that builds the actual tree is ``__showtree__`` of ``Node``
@@ -227,7 +249,7 @@ class Wikicode(StringMixIn):
the starting indentation.
"""
- def write(*args):
+ def write(*args: str) -> None:
"""Write a new line following the proper indentation rules."""
if lines and lines[-1] is marker: # Continue from the last line
lines.pop() # Remove the marker
@@ -275,7 +297,7 @@ class Wikicode(StringMixIn):
setattr(cls, "filter_" + name, filt)
@property
- def nodes(self):
+ def nodes(self) -> list[Node]:
"""A list of :class:`.Node` objects.
This is the internal data actually stored within a :class:`.Wikicode`
@@ -284,16 +306,22 @@ class Wikicode(StringMixIn):
return self._nodes
@nodes.setter
- def nodes(self, value):
+ def nodes(self, value: Union[list[Node], Any]) -> None:
if not isinstance(value, list):
value = parse_anything(value).nodes
self._nodes = value
+ @overload
+ def get(self, index: int) -> Node: ...
+
+ @overload
+ def get(self, index: slice) -> list[Node]: ...
+
def get(self, index):
"""Return the *index*\\ th node within the list of nodes."""
return self.nodes[index]
- def set(self, index, value):
+ def set(self, index: int, value: Any) -> None:
"""Set the ``Node`` at *index* to *value*.
Raises :exc:`IndexError` if *index* is out of range, or
@@ -311,7 +339,7 @@ class Wikicode(StringMixIn):
else:
self.nodes.pop(index)
- def contains(self, obj):
+ def contains(self, obj: Union[Node, "Wikicode", str]) -> bool:
"""Return whether this Wikicode object contains *obj*.
If *obj* is a :class:`.Node` or :class:`.Wikicode` object, then we
@@ -326,7 +354,7 @@ class Wikicode(StringMixIn):
return False
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.
Raises :exc:`ValueError` if *obj* is not found. If *recursive* is
@@ -345,7 +373,7 @@ class Wikicode(StringMixIn):
return i
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*.
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.
"""
- def _get_ancestors(code, needle):
+ def _get_ancestors(code: "Wikicode", needle: Node) -> Union[list[Node], None]:
for node in code.nodes:
if node is needle:
return []
@@ -382,7 +410,7 @@ class Wikicode(StringMixIn):
raise ValueError(obj)
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*.
This function is equivalent to calling :meth:`.get_ancestors` and
@@ -393,7 +421,7 @@ class Wikicode(StringMixIn):
ancestors = self.get_ancestors(obj)
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.
*value* can be anything parsable by :func:`.parse_anything`, which
@@ -403,7 +431,9 @@ class Wikicode(StringMixIn):
for node in reversed(nodes):
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*.
*obj* can be either a string, a :class:`.Node`, or another
@@ -426,7 +456,9 @@ class Wikicode(StringMixIn):
obj = str(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*.
*obj* can be either a string, a :class:`.Node`, or another
@@ -449,7 +481,9 @@ class Wikicode(StringMixIn):
obj = str(obj)
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*.
*obj* can be either a string, a :class:`.Node`, or another
@@ -475,7 +509,7 @@ class Wikicode(StringMixIn):
else:
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.
*value* can be anything parsable by :func:`.parse_anything`.
@@ -484,7 +518,7 @@ class Wikicode(StringMixIn):
for node in nodes:
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.
*obj* can be either a string, a :class:`.Node`, or another
@@ -508,7 +542,12 @@ class Wikicode(StringMixIn):
else:
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.
*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
``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())
if isinstance(other, (str, bytes, Wikicode, Node)):
@@ -530,7 +571,13 @@ class Wikicode(StringMixIn):
return True
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.
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)
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.
This is equivalent to calling :func:`list` on :meth:`ifilter`.
@@ -566,13 +613,13 @@ class Wikicode(StringMixIn):
def get_sections(
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.
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)
# Tuples of (index, heading), where index and heading.level are both
# monotonically increasing
- open_headings = []
+ open_headings: list[tuple[int, Heading]] = []
# Add the lead section if appropriate:
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:
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.
The way a node is stripped is handled by the
@@ -674,7 +726,7 @@ class Wikicode(StringMixIn):
return stripped
return "".join(nodes)
- def get_tree(self):
+ def get_tree(self) -> str:
"""Return a hierarchical tree representation of the object.
The representation is a string makes the most sense printed. It is