ソースを参照

Add type hints: mwparserfromhell.wikicode

pull/338/head
Jakub Klinkovský 1年前
親
コミット
c35f6c71ec
1個のファイルの変更、95行の追加、43行の削除
  1. +95
    -43
      src/mwparserfromhell/wikicode.py

+ 95
- 43
src/mwparserfromhell/wikicode.py ファイルの表示

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

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

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

RECURSE_OTHERS = 2

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

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

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

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

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

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

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

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

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

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

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

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

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

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

raise TypeError(obj)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


読み込み中…
キャンセル
保存