* 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
| @@ -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 | |||||
| @@ -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: | ||||
| @@ -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)) | ||||
| @@ -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: | ||||
| @@ -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) | ||||
| @@ -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) | ||||
| @@ -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) | ||||
| @@ -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)) | ||||
| @@ -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) | ||||
| @@ -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 `` ``, either named or unnamed.""" | """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__() | 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, ``Σ``, ``Σ``, 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 | @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, ``Σ``, ``Σ``, 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 | @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 ``"ሴ"`` 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 | @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]) | ||||
| @@ -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. | ||||
| @@ -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) | ||||
| @@ -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) | ||||
| @@ -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: | ||||
| @@ -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 | ||||