| @@ -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,6 +38,9 @@ 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 | ||||
| @@ -36,34 +51,36 @@ _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: | ||||
| @@ -76,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 (``=``) | ||||
| @@ -89,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 | ||||
| @@ -105,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 | ||||
| @@ -120,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 | ||||
| @@ -150,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: | ||||
| @@ -169,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. | ||||
| @@ -183,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 | ||||
| @@ -212,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 | ||||
| @@ -233,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 | ||||
| @@ -329,7 +354,7 @@ 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. | ||||
| @@ -340,10 +365,10 @@ class Template(Node): | |||||
| 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, | ||||
| @@ -390,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) | ||||