* 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 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: | |||
| @@ -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)) | |||
| @@ -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: | |||
| @@ -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 ``<!-- foobar -->``.""" | |||
| def __init__(self, contents): | |||
| def __init__(self, contents: str): | |||
| super().__init__() | |||
| self.contents = contents | |||
| def __str__(self): | |||
| def __str__(self) -> str: | |||
| return "<!--" + self.contents + "-->" | |||
| @property | |||
| def contents(self): | |||
| def contents(self) -> str: | |||
| """The hidden text contained between ``<!--`` and ``-->``.""" | |||
| return self._contents | |||
| @contents.setter | |||
| def contents(self, value): | |||
| def contents(self, value: Any) -> None: | |||
| self._contents = str(value) | |||
| @@ -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) | |||
| @@ -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) | |||
| @@ -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)) | |||
| @@ -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) | |||
| @@ -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]) | |||
| @@ -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 ``<br/>``).""" | |||
| 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 ``<br>`` 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. | |||
| @@ -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) | |||
| @@ -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) | |||
| @@ -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: | |||
| @@ -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 | |||