Add type hints for browser.hints
This commit is contained in:
parent
62dd08f6d6
commit
00c341fe6d
4
mypy.ini
4
mypy.ini
|
|
@ -58,6 +58,10 @@ disallow_subclassing_any = False
|
|||
disallow_untyped_defs = True
|
||||
disallow_incomplete_defs = True
|
||||
|
||||
[mypy-qutebrowser.browser.hints]
|
||||
disallow_untyped_defs = True
|
||||
disallow_incomplete_defs = True
|
||||
|
||||
[mypy-qutebrowser.misc.objects]
|
||||
disallow_untyped_defs = True
|
||||
disallow_incomplete_defs = True
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
"""A HintManager to draw hints over links."""
|
||||
|
||||
import collections
|
||||
import typing
|
||||
import functools
|
||||
import os
|
||||
import re
|
||||
|
|
@ -37,6 +38,8 @@ from qutebrowser.browser import webelem
|
|||
from qutebrowser.commands import userscripts, runners
|
||||
from qutebrowser.api import cmdutils
|
||||
from qutebrowser.utils import usertypes, log, qtutils, message, objreg, utils
|
||||
if typing.TYPE_CHECKING:
|
||||
from qutebrowser.browser import browsertab
|
||||
|
||||
|
||||
Target = enum.Enum('Target', ['normal', 'current', 'tab', 'tab_fg', 'tab_bg',
|
||||
|
|
@ -50,7 +53,7 @@ class HintingError(Exception):
|
|||
"""Exception raised on errors during hinting."""
|
||||
|
||||
|
||||
def on_mode_entered(mode, win_id):
|
||||
def on_mode_entered(mode: usertypes.KeyMode, win_id: int) -> None:
|
||||
"""Stop hinting when insert mode was entered."""
|
||||
if mode == usertypes.KeyMode.insert:
|
||||
modeman.leave(win_id, usertypes.KeyMode.hint, 'insert mode',
|
||||
|
|
@ -66,7 +69,8 @@ class HintLabel(QLabel):
|
|||
_context: The current hinting context.
|
||||
"""
|
||||
|
||||
def __init__(self, elem, context):
|
||||
def __init__(self, elem: webelem.AbstractWebElement,
|
||||
context: 'HintContext') -> None:
|
||||
super().__init__(parent=context.tab)
|
||||
self._context = context
|
||||
self.elem = elem
|
||||
|
|
@ -81,14 +85,14 @@ class HintLabel(QLabel):
|
|||
self._move_to_elem()
|
||||
self.show()
|
||||
|
||||
def __repr__(self):
|
||||
def __repr__(self) -> str:
|
||||
try:
|
||||
text = self.text()
|
||||
except RuntimeError:
|
||||
text = '<deleted>'
|
||||
return utils.get_repr(self, elem=self.elem, text=text)
|
||||
|
||||
def update_text(self, matched, unmatched):
|
||||
def update_text(self, matched: str, unmatched: str) -> None:
|
||||
"""Set the text for the hint.
|
||||
|
||||
Args:
|
||||
|
|
@ -112,7 +116,7 @@ class HintLabel(QLabel):
|
|||
self.adjustSize()
|
||||
|
||||
@pyqtSlot()
|
||||
def _move_to_elem(self):
|
||||
def _move_to_elem(self) -> None:
|
||||
"""Reposition the label to its element."""
|
||||
if not self.elem.has_frame():
|
||||
# This sometimes happens for some reason...
|
||||
|
|
@ -123,7 +127,7 @@ class HintLabel(QLabel):
|
|||
rect = self.elem.rect_on_view(no_js=no_js)
|
||||
self.move(rect.x(), rect.y())
|
||||
|
||||
def cleanup(self):
|
||||
def cleanup(self) -> None:
|
||||
"""Clean up this element and hide it."""
|
||||
self.hide()
|
||||
self.deleteLater()
|
||||
|
|
@ -159,22 +163,22 @@ class HintContext:
|
|||
group: The group of web elements to hint.
|
||||
"""
|
||||
|
||||
all_labels = attr.ib(attr.Factory(list))
|
||||
labels = attr.ib(attr.Factory(dict))
|
||||
target = attr.ib(None)
|
||||
baseurl = attr.ib(None)
|
||||
to_follow = attr.ib(None)
|
||||
rapid = attr.ib(False)
|
||||
first_run = attr.ib(True)
|
||||
add_history = attr.ib(False)
|
||||
filterstr = attr.ib(None)
|
||||
args = attr.ib(attr.Factory(list))
|
||||
tab = attr.ib(None)
|
||||
group = attr.ib(None)
|
||||
hint_mode = attr.ib(None)
|
||||
first = attr.ib(False)
|
||||
all_labels = attr.ib(attr.Factory(list)) # type: typing.List[HintLabel]
|
||||
labels = attr.ib(attr.Factory(dict)) # type: typing.Dict[str, HintLabel]
|
||||
target = attr.ib(None) # type: Target
|
||||
baseurl = attr.ib(None) # type: QUrl
|
||||
to_follow = attr.ib(None) # type: str
|
||||
rapid = attr.ib(False) # type: bool
|
||||
first_run = attr.ib(True) # type: bool
|
||||
add_history = attr.ib(False) # type: bool
|
||||
filterstr = attr.ib(None) # type: str
|
||||
args = attr.ib(attr.Factory(list)) # type: typing.List[str]
|
||||
tab = attr.ib(None) # type: browsertab.AbstractTab
|
||||
group = attr.ib(None) # type: str
|
||||
hint_mode = attr.ib(None) # type: str
|
||||
first = attr.ib(False) # type: bool
|
||||
|
||||
def get_args(self, urlstr):
|
||||
def get_args(self, urlstr: str) -> typing.Sequence[str]:
|
||||
"""Get the arguments, with {hint-url} replaced by the given URL."""
|
||||
args = []
|
||||
for arg in self.args:
|
||||
|
|
@ -187,16 +191,12 @@ class HintActions:
|
|||
|
||||
"""Actions which can be done after selecting a hint."""
|
||||
|
||||
def __init__(self, win_id):
|
||||
def __init__(self, win_id: int) -> None:
|
||||
self._win_id = win_id
|
||||
|
||||
def click(self, elem, context):
|
||||
"""Click an element.
|
||||
|
||||
Args:
|
||||
elem: The QWebElement to click.
|
||||
context: The HintContext to use.
|
||||
"""
|
||||
def click(self, elem: webelem.AbstractWebElement,
|
||||
context: HintContext) -> None:
|
||||
"""Click an element."""
|
||||
target_mapping = {
|
||||
Target.normal: usertypes.ClickTarget.normal,
|
||||
Target.current: usertypes.ClickTarget.normal,
|
||||
|
|
@ -225,20 +225,15 @@ class HintActions:
|
|||
except webelem.Error as e:
|
||||
raise HintingError(str(e))
|
||||
|
||||
def yank(self, url, context):
|
||||
"""Yank an element to the clipboard or primary selection.
|
||||
|
||||
Args:
|
||||
url: The URL to open as a QUrl.
|
||||
context: The HintContext to use.
|
||||
"""
|
||||
def yank(self, url: QUrl, context: HintContext) -> None:
|
||||
"""Yank an element to the clipboard or primary selection."""
|
||||
sel = (context.target == Target.yank_primary and
|
||||
utils.supports_selection())
|
||||
|
||||
flags = QUrl.FullyEncoded | QUrl.RemovePassword
|
||||
if url.scheme() == 'mailto':
|
||||
flags |= QUrl.RemoveScheme
|
||||
urlstr = url.toString(flags)
|
||||
urlstr = url.toString(flags) # type: ignore
|
||||
|
||||
new_content = urlstr
|
||||
|
||||
|
|
@ -257,26 +252,16 @@ class HintActions:
|
|||
urlstr)
|
||||
message.info(msg)
|
||||
|
||||
def run_cmd(self, url, context):
|
||||
"""Run the command based on a hint URL.
|
||||
|
||||
Args:
|
||||
url: The URL to open as a QUrl.
|
||||
context: The HintContext to use.
|
||||
"""
|
||||
urlstr = url.toString(QUrl.FullyEncoded)
|
||||
def run_cmd(self, url: QUrl, context: HintContext) -> None:
|
||||
"""Run the command based on a hint URL."""
|
||||
urlstr = url.toString(QUrl.FullyEncoded) # type: ignore
|
||||
args = context.get_args(urlstr)
|
||||
commandrunner = runners.CommandRunner(self._win_id)
|
||||
commandrunner.run_safely(' '.join(args))
|
||||
|
||||
def preset_cmd_text(self, url, context):
|
||||
"""Preset a commandline text based on a hint URL.
|
||||
|
||||
Args:
|
||||
url: The URL to open as a QUrl.
|
||||
context: The HintContext to use.
|
||||
"""
|
||||
urlstr = url.toDisplayString(QUrl.FullyEncoded)
|
||||
def preset_cmd_text(self, url: QUrl, context: HintContext) -> None:
|
||||
"""Preset a commandline text based on a hint URL."""
|
||||
urlstr = url.toDisplayString(QUrl.FullyEncoded) # type: ignore
|
||||
args = context.get_args(urlstr)
|
||||
text = ' '.join(args)
|
||||
if text[0] not in modeparsers.STARTCHARS:
|
||||
|
|
@ -285,7 +270,8 @@ class HintActions:
|
|||
cmd = objreg.get('status-command', scope='window', window=self._win_id)
|
||||
cmd.set_cmd_text(text)
|
||||
|
||||
def download(self, elem, context):
|
||||
def download(self, elem: webelem.AbstractWebElement,
|
||||
context: HintContext) -> None:
|
||||
"""Download a hint URL.
|
||||
|
||||
Args:
|
||||
|
|
@ -305,7 +291,8 @@ class HintActions:
|
|||
download_manager.get(url, qnam=qnam, user_agent=user_agent,
|
||||
prompt_download_directory=prompt)
|
||||
|
||||
def call_userscript(self, elem, context):
|
||||
def call_userscript(self, elem: webelem.AbstractWebElement,
|
||||
context: HintContext) -> None:
|
||||
"""Call a userscript from a hint.
|
||||
|
||||
Args:
|
||||
|
|
@ -321,7 +308,7 @@ class HintActions:
|
|||
}
|
||||
url = elem.resolve_url(context.baseurl)
|
||||
if url is not None:
|
||||
env['QUTE_URL'] = url.toString(QUrl.FullyEncoded)
|
||||
env['QUTE_URL'] = url.toString(QUrl.FullyEncoded) # type: ignore
|
||||
|
||||
try:
|
||||
userscripts.run_async(context.tab, cmd, *args, win_id=self._win_id,
|
||||
|
|
@ -329,22 +316,28 @@ class HintActions:
|
|||
except userscripts.Error as e:
|
||||
raise HintingError(str(e))
|
||||
|
||||
def delete(self, elem, _context):
|
||||
def delete(self, elem: webelem.AbstractWebElement,
|
||||
_context: HintContext) -> None:
|
||||
elem.delete()
|
||||
|
||||
def spawn(self, url, context):
|
||||
def spawn(self, url: QUrl, context: HintContext) -> None:
|
||||
"""Spawn a simple command from a hint.
|
||||
|
||||
Args:
|
||||
url: The URL to open as a QUrl.
|
||||
context: The HintContext to use.
|
||||
"""
|
||||
urlstr = url.toString(QUrl.FullyEncoded | QUrl.RemovePassword)
|
||||
urlstr = url.toString(
|
||||
QUrl.FullyEncoded | QUrl.RemovePassword) # type: ignore
|
||||
args = context.get_args(urlstr)
|
||||
commandrunner = runners.CommandRunner(self._win_id)
|
||||
commandrunner.run_safely('spawn ' + ' '.join(args))
|
||||
|
||||
|
||||
_ElemsType = typing.Sequence[webelem.AbstractWebElement]
|
||||
_HintStringsType = typing.MutableSequence[str]
|
||||
|
||||
|
||||
class HintManager(QObject):
|
||||
|
||||
"""Manage drawing hints over links or other elements.
|
||||
|
|
@ -379,11 +372,11 @@ class HintManager(QObject):
|
|||
Target.delete: "Delete an element",
|
||||
}
|
||||
|
||||
def __init__(self, win_id, parent=None):
|
||||
def __init__(self, win_id: int, parent: QObject = None) -> None:
|
||||
"""Constructor."""
|
||||
super().__init__(parent)
|
||||
self._win_id = win_id
|
||||
self._context = None
|
||||
self._context = None # type: typing.Optional[HintContext]
|
||||
self._word_hinter = WordHinter()
|
||||
|
||||
self._actions = HintActions(win_id)
|
||||
|
|
@ -392,16 +385,18 @@ class HintManager(QObject):
|
|||
window=win_id)
|
||||
mode_manager.left.connect(self.on_mode_left)
|
||||
|
||||
def _get_text(self):
|
||||
def _get_text(self) -> str:
|
||||
"""Get a hint text based on the current context."""
|
||||
assert self._context is not None
|
||||
text = self.HINT_TEXTS[self._context.target]
|
||||
if self._context.rapid:
|
||||
text += ' (rapid mode)'
|
||||
text += '...'
|
||||
return text
|
||||
|
||||
def _cleanup(self):
|
||||
def _cleanup(self) -> None:
|
||||
"""Clean up after hinting."""
|
||||
assert self._context is not None
|
||||
for label in self._context.all_labels:
|
||||
label.cleanup()
|
||||
|
||||
|
|
@ -411,7 +406,7 @@ class HintManager(QObject):
|
|||
message_bridge.maybe_reset_text(text)
|
||||
self._context = None
|
||||
|
||||
def _hint_strings(self, elems):
|
||||
def _hint_strings(self, elems: _ElemsType) -> _HintStringsType:
|
||||
"""Calculate the hint strings for elems.
|
||||
|
||||
Inspired by Vimium.
|
||||
|
|
@ -424,6 +419,8 @@ class HintManager(QObject):
|
|||
"""
|
||||
if not elems:
|
||||
return []
|
||||
|
||||
assert self._context is not None
|
||||
hint_mode = self._context.hint_mode
|
||||
if hint_mode == 'word':
|
||||
try:
|
||||
|
|
@ -441,7 +438,9 @@ class HintManager(QObject):
|
|||
else:
|
||||
return self._hint_linear(min_chars, chars, elems)
|
||||
|
||||
def _hint_scattered(self, min_chars, chars, elems):
|
||||
def _hint_scattered(self, min_chars: int,
|
||||
chars: str,
|
||||
elems: _ElemsType) -> _HintStringsType:
|
||||
"""Produce scattered hint labels with variable length (like Vimium).
|
||||
|
||||
Args:
|
||||
|
|
@ -478,7 +477,9 @@ class HintManager(QObject):
|
|||
|
||||
return self._shuffle_hints(strings, len(chars))
|
||||
|
||||
def _hint_linear(self, min_chars, chars, elems):
|
||||
def _hint_linear(self, min_chars: int,
|
||||
chars: str,
|
||||
elems: _ElemsType) -> _HintStringsType:
|
||||
"""Produce linear hint labels with constant length (like dwb).
|
||||
|
||||
Args:
|
||||
|
|
@ -492,7 +493,8 @@ class HintManager(QObject):
|
|||
strings.append(self._number_to_hint_str(i, chars, needed))
|
||||
return strings
|
||||
|
||||
def _shuffle_hints(self, hints, length):
|
||||
def _shuffle_hints(self, hints: _HintStringsType,
|
||||
length: int) -> _HintStringsType:
|
||||
"""Shuffle the given set of hints so that they're scattered.
|
||||
|
||||
Hints starting with the same character will be spread evenly throughout
|
||||
|
|
@ -507,15 +509,19 @@ class HintManager(QObject):
|
|||
Return:
|
||||
A list of shuffled hint strings.
|
||||
"""
|
||||
buckets = [[] for i in range(length)]
|
||||
buckets = [
|
||||
[] for i in range(length)
|
||||
] # type: typing.Sequence[_HintStringsType]
|
||||
for i, hint in enumerate(hints):
|
||||
buckets[i % len(buckets)].append(hint)
|
||||
result = []
|
||||
result = [] # type: _HintStringsType
|
||||
for bucket in buckets:
|
||||
result += bucket
|
||||
return result
|
||||
|
||||
def _number_to_hint_str(self, number, chars, digits=0):
|
||||
def _number_to_hint_str(self, number: int,
|
||||
chars: str,
|
||||
digits: int = 0) -> str:
|
||||
"""Convert a number like "8" into a hint string like "JK".
|
||||
|
||||
This is used to sequentially generate all of the hint text.
|
||||
|
|
@ -533,7 +539,7 @@ class HintManager(QObject):
|
|||
A hint string.
|
||||
"""
|
||||
base = len(chars)
|
||||
hintstr = []
|
||||
hintstr = [] # type: typing.MutableSequence[str]
|
||||
remainder = 0
|
||||
while True:
|
||||
remainder = number % base
|
||||
|
|
@ -547,7 +553,7 @@ class HintManager(QObject):
|
|||
hintstr.insert(0, chars[0])
|
||||
return ''.join(hintstr)
|
||||
|
||||
def _check_args(self, target, *args):
|
||||
def _check_args(self, target: Target, *args: str) -> None:
|
||||
"""Check the arguments passed to start() and raise if they're wrong.
|
||||
|
||||
Args:
|
||||
|
|
@ -567,7 +573,7 @@ class HintManager(QObject):
|
|||
raise cmdutils.CommandError(
|
||||
"'args' is only allowed with target userscript/spawn.")
|
||||
|
||||
def _filter_matches(self, filterstr, elemstr):
|
||||
def _filter_matches(self, filterstr: str, elemstr: str) -> bool:
|
||||
"""Return True if `filterstr` matches `elemstr`."""
|
||||
# Empty string and None always match
|
||||
if not filterstr:
|
||||
|
|
@ -577,7 +583,7 @@ class HintManager(QObject):
|
|||
# Do multi-word matching
|
||||
return all(word in elemstr for word in filterstr.split())
|
||||
|
||||
def _filter_matches_exactly(self, filterstr, elemstr):
|
||||
def _filter_matches_exactly(self, filterstr: str, elemstr: str) -> bool:
|
||||
"""Return True if `filterstr` exactly matches `elemstr`."""
|
||||
# Empty string and None never match
|
||||
if not filterstr:
|
||||
|
|
@ -586,7 +592,7 @@ class HintManager(QObject):
|
|||
elemstr = elemstr.casefold()
|
||||
return filterstr == elemstr
|
||||
|
||||
def _start_cb(self, elems):
|
||||
def _start_cb(self, elems: _ElemsType) -> None:
|
||||
"""Initialize the elements and labels based on the context set."""
|
||||
if self._context is None:
|
||||
log.hints.debug("In _start_cb without context!")
|
||||
|
|
@ -637,8 +643,13 @@ class HintManager(QObject):
|
|||
@cmdutils.register(instance='hintmanager', scope='window', name='hint',
|
||||
star_args_optional=True, maxsplit=2)
|
||||
def start(self, # pylint: disable=keyword-arg-before-vararg
|
||||
group='all', target=Target.normal, *args, mode=None,
|
||||
add_history=False, rapid=False, first=False):
|
||||
group: str = 'all',
|
||||
target: Target = Target.normal,
|
||||
*args: str,
|
||||
mode: str = None,
|
||||
add_history: bool = False,
|
||||
rapid: bool = False,
|
||||
first: bool = False) -> None:
|
||||
"""Start hinting.
|
||||
|
||||
Args:
|
||||
|
|
@ -754,7 +765,7 @@ class HintManager(QObject):
|
|||
error_cb=lambda err: message.error(str(err)),
|
||||
only_visible=True)
|
||||
|
||||
def _get_hint_mode(self, mode):
|
||||
def _get_hint_mode(self, mode: typing.Optional[str]) -> str:
|
||||
"""Get the hinting mode to use based on a mode argument."""
|
||||
if mode is None:
|
||||
return config.val.hints.mode
|
||||
|
|
@ -766,15 +777,22 @@ class HintManager(QObject):
|
|||
raise cmdutils.CommandError("Invalid mode: {}".format(e))
|
||||
return mode
|
||||
|
||||
def current_mode(self):
|
||||
def current_mode(self) -> typing.Optional[str]:
|
||||
"""Return the currently active hinting mode (or None otherwise)."""
|
||||
if self._context is None:
|
||||
return None
|
||||
|
||||
return self._context.hint_mode
|
||||
|
||||
def _handle_auto_follow(self, keystr="", filterstr="", visible=None):
|
||||
def _handle_auto_follow(
|
||||
self,
|
||||
keystr: str = "",
|
||||
filterstr: str = "",
|
||||
visible: typing.Mapping[str, HintLabel] = None
|
||||
) -> None:
|
||||
"""Handle the auto_follow option."""
|
||||
assert self._context is not None
|
||||
|
||||
if visible is None:
|
||||
visible = {string: label
|
||||
for string, label in self._context.labels.items()
|
||||
|
|
@ -788,7 +806,7 @@ class HintManager(QObject):
|
|||
if auto_follow == "always":
|
||||
follow = True
|
||||
elif auto_follow == "unique-match":
|
||||
follow = keystr or filterstr
|
||||
follow = bool(keystr or filterstr)
|
||||
elif auto_follow == "full-match":
|
||||
elemstr = str(list(visible.values())[0].elem)
|
||||
filter_match = self._filter_matches_exactly(filterstr, elemstr)
|
||||
|
|
@ -810,7 +828,7 @@ class HintManager(QObject):
|
|||
self._fire(*visible)
|
||||
|
||||
@pyqtSlot(str)
|
||||
def handle_partial_key(self, keystr):
|
||||
def handle_partial_key(self, keystr: str) -> None:
|
||||
"""Handle a new partial keypress."""
|
||||
if self._context is None:
|
||||
log.hints.debug("Got key without context!")
|
||||
|
|
@ -834,7 +852,7 @@ class HintManager(QObject):
|
|||
pass
|
||||
self._handle_auto_follow(keystr=keystr)
|
||||
|
||||
def filter_hints(self, filterstr):
|
||||
def filter_hints(self, filterstr: typing.Optional[str]) -> None:
|
||||
"""Filter displayed hints according to a text.
|
||||
|
||||
Args:
|
||||
|
|
@ -844,6 +862,8 @@ class HintManager(QObject):
|
|||
and `self._context.filterstr` are None, all hints are
|
||||
shown.
|
||||
"""
|
||||
assert self._context is not None
|
||||
|
||||
if filterstr is None:
|
||||
filterstr = self._context.filterstr
|
||||
else:
|
||||
|
|
@ -889,12 +909,13 @@ class HintManager(QObject):
|
|||
self._handle_auto_follow(filterstr=filterstr,
|
||||
visible=self._context.labels)
|
||||
|
||||
def _fire(self, keystr):
|
||||
def _fire(self, keystr: str) -> None:
|
||||
"""Fire a completed hint.
|
||||
|
||||
Args:
|
||||
keystr: The keychain string to follow.
|
||||
"""
|
||||
assert self._context is not None
|
||||
# Handlers which take a QWebElement
|
||||
elem_handlers = {
|
||||
Target.normal: self._actions.click,
|
||||
|
|
@ -958,13 +979,14 @@ class HintManager(QObject):
|
|||
|
||||
@cmdutils.register(instance='hintmanager', scope='window',
|
||||
modes=[usertypes.KeyMode.hint])
|
||||
def follow_hint(self, select=False, keystring=None):
|
||||
def follow_hint(self, select: bool = False, keystring: str = None) -> None:
|
||||
"""Follow a hint.
|
||||
|
||||
Args:
|
||||
select: Only select the given hint, don't necessarily follow it.
|
||||
keystring: The hint to follow, or None.
|
||||
"""
|
||||
assert self._context is not None
|
||||
if keystring is None:
|
||||
if self._context.to_follow is None:
|
||||
raise cmdutils.CommandError("No hint to follow")
|
||||
|
|
@ -980,7 +1002,7 @@ class HintManager(QObject):
|
|||
self._fire(keystring)
|
||||
|
||||
@pyqtSlot(usertypes.KeyMode)
|
||||
def on_mode_left(self, mode):
|
||||
def on_mode_left(self, mode: usertypes.KeyMode) -> None:
|
||||
"""Stop hinting when hinting mode was left."""
|
||||
if mode != usertypes.KeyMode.hint or self._context is None:
|
||||
# We have one HintManager per tab, so when this gets called,
|
||||
|
|
@ -999,12 +1021,12 @@ class WordHinter:
|
|||
derived from the hinted element.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self) -> None:
|
||||
# will be initialized on first use.
|
||||
self.words = set()
|
||||
self.words = set() # type: typing.Set[str]
|
||||
self.dictionary = None
|
||||
|
||||
def ensure_initialized(self):
|
||||
def ensure_initialized(self) -> None:
|
||||
"""Generate the used words if yet uninitialized."""
|
||||
dictionary = config.val.hints.dictionary
|
||||
if not self.words or self.dictionary != dictionary:
|
||||
|
|
@ -1031,8 +1053,11 @@ class WordHinter:
|
|||
error = "Word hints requires reading the file at {}: {}"
|
||||
raise HintingError(error.format(dictionary, str(e)))
|
||||
|
||||
def extract_tag_words(self, elem):
|
||||
def extract_tag_words(
|
||||
self, elem: webelem.AbstractWebElement
|
||||
) -> typing.Iterator[str]:
|
||||
"""Extract tag words form the given element."""
|
||||
_extractor_type = typing.Callable[[webelem.AbstractWebElement], str]
|
||||
attr_extractors = {
|
||||
"alt": lambda elem: elem["alt"],
|
||||
"name": lambda elem: elem["name"],
|
||||
|
|
@ -1041,7 +1066,7 @@ class WordHinter:
|
|||
"src": lambda elem: elem["src"].split('/')[-1],
|
||||
"href": lambda elem: elem["href"].split('/')[-1],
|
||||
"text": str,
|
||||
}
|
||||
} # type: typing.Mapping[str, _extractor_type]
|
||||
|
||||
extractable_attrs = collections.defaultdict(list, {
|
||||
"img": ["alt", "title", "src"],
|
||||
|
|
@ -1055,7 +1080,10 @@ class WordHinter:
|
|||
for attr in extractable_attrs[elem.tag_name()]
|
||||
if attr in elem or attr == "text")
|
||||
|
||||
def tag_words_to_hints(self, words):
|
||||
def tag_words_to_hints(
|
||||
self,
|
||||
words: typing.Iterable[str]
|
||||
) -> typing.Iterator[str]:
|
||||
"""Take words and transform them to proper hints if possible."""
|
||||
for candidate in words:
|
||||
if not candidate:
|
||||
|
|
@ -1066,13 +1094,20 @@ class WordHinter:
|
|||
if 4 < match.end() - match.start() < 8:
|
||||
yield candidate[match.start():match.end()].lower()
|
||||
|
||||
def any_prefix(self, hint, existing):
|
||||
def any_prefix(self, hint: str, existing: typing.Iterable[str]) -> bool:
|
||||
return any(hint.startswith(e) or e.startswith(hint) for e in existing)
|
||||
|
||||
def filter_prefixes(self, hints, existing):
|
||||
def filter_prefixes(
|
||||
self,
|
||||
hints: typing.Iterable[str],
|
||||
existing: typing.Iterable[str]
|
||||
) -> typing.Iterator[str]:
|
||||
"""Filter hints which don't start with the given prefix."""
|
||||
return (h for h in hints if not self.any_prefix(h, existing))
|
||||
|
||||
def new_hint_for(self, elem, existing, fallback):
|
||||
def new_hint_for(self, elem: webelem.AbstractWebElement,
|
||||
existing: typing.Iterable[str],
|
||||
fallback: typing.Iterable[str]) -> typing.Optional[str]:
|
||||
"""Return a hint for elem, not conflicting with the existing."""
|
||||
new = self.tag_words_to_hints(self.extract_tag_words(elem))
|
||||
new_no_prefixes = self.filter_prefixes(new, existing)
|
||||
|
|
@ -1081,7 +1116,7 @@ class WordHinter:
|
|||
return (next(new_no_prefixes, None) or
|
||||
next(fallback_no_prefixes, None))
|
||||
|
||||
def hint(self, elems):
|
||||
def hint(self, elems: _ElemsType) -> _HintStringsType:
|
||||
"""Produce hint labels based on the html tags.
|
||||
|
||||
Produce hint words based on the link text and random words
|
||||
|
|
@ -1096,7 +1131,7 @@ class WordHinter:
|
|||
"""
|
||||
self.ensure_initialized()
|
||||
hints = []
|
||||
used_hints = set()
|
||||
used_hints = set() # type: typing.Set[str]
|
||||
words = iter(self.words)
|
||||
for elem in elems:
|
||||
hint = self.new_hint_for(elem, used_hints, words)
|
||||
|
|
|
|||
Loading…
Reference in New Issue