Merge branch 'NoctuaNivalis-issue-1393'
This commit is contained in:
commit
e9da3ef8bb
@ -146,8 +146,8 @@ Contributors, sorted by the number of commits in descending order:
|
|||||||
* Lamar Pavel
|
* Lamar Pavel
|
||||||
* Bruno Oliveira
|
* Bruno Oliveira
|
||||||
* Alexander Cogneau
|
* Alexander Cogneau
|
||||||
* Martin Tournoij
|
|
||||||
* Felix Van der Jeugt
|
* Felix Van der Jeugt
|
||||||
|
* Martin Tournoij
|
||||||
* Raphael Pierzina
|
* Raphael Pierzina
|
||||||
* Joel Torstensson
|
* Joel Torstensson
|
||||||
* Ryan Roden-Corrent
|
* Ryan Roden-Corrent
|
||||||
|
@ -1004,11 +1004,14 @@ class WordHinter:
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
# will be initialized on first use.
|
# will be initialized on first use.
|
||||||
self.words = set()
|
self.words = set()
|
||||||
|
self.dictionary = None
|
||||||
|
|
||||||
def ensure_initialized(self):
|
def ensure_initialized(self):
|
||||||
"""Generate the used words if yet uninialized."""
|
"""Generate the used words if yet uninialized."""
|
||||||
if not self.words:
|
dictionary = config.get("hints", "dictionary")
|
||||||
dictionary = config.get("hints", "dictionary")
|
if not self.words or self.dictionary != dictionary:
|
||||||
|
self.words.clear()
|
||||||
|
self.dictionary = dictionary
|
||||||
try:
|
try:
|
||||||
with open(dictionary, encoding="UTF-8") as wordfile:
|
with open(dictionary, encoding="UTF-8") as wordfile:
|
||||||
alphabet = set(string.ascii_lowercase)
|
alphabet = set(string.ascii_lowercase)
|
||||||
@ -1065,12 +1068,17 @@ class WordHinter:
|
|||||||
def any_prefix(self, hint, existing):
|
def any_prefix(self, hint, existing):
|
||||||
return any(hint.startswith(e) or e.startswith(hint) for e in existing)
|
return any(hint.startswith(e) or e.startswith(hint) for e in existing)
|
||||||
|
|
||||||
def new_hint_for(self, elem, existing):
|
def filter_prefixes(self, hints, existing):
|
||||||
|
return (h for h in hints if not self.any_prefix(h, existing))
|
||||||
|
|
||||||
|
def new_hint_for(self, elem, existing, fallback):
|
||||||
"""Return a hint for elem, not conflicting with the existing."""
|
"""Return a hint for elem, not conflicting with the existing."""
|
||||||
new = self.tag_words_to_hints(self.extract_tag_words(elem))
|
new = self.tag_words_to_hints(self.extract_tag_words(elem))
|
||||||
no_prefixes = (h for h in new if not self.any_prefix(h, existing))
|
new_no_prefixes = self.filter_prefixes(new, existing)
|
||||||
|
fallback_no_prefixes = self.filter_prefixes(fallback, existing)
|
||||||
# either the first good, or None
|
# either the first good, or None
|
||||||
return next(no_prefixes, None)
|
return (next(new_no_prefixes, None) or
|
||||||
|
next(fallback_no_prefixes, None))
|
||||||
|
|
||||||
def hint(self, elems):
|
def hint(self, elems):
|
||||||
"""Produce hint labels based on the html tags.
|
"""Produce hint labels based on the html tags.
|
||||||
@ -1090,7 +1098,9 @@ class WordHinter:
|
|||||||
used_hints = set()
|
used_hints = set()
|
||||||
words = iter(self.words)
|
words = iter(self.words)
|
||||||
for elem in elems:
|
for elem in elems:
|
||||||
hint = self.new_hint_for(elem, used_hints) or next(words)
|
hint = self.new_hint_for(elem, used_hints, words)
|
||||||
|
if not hint:
|
||||||
|
raise WordHintingError("Not enough words in the dictionary.")
|
||||||
used_hints.add(hint)
|
used_hints.add(hint)
|
||||||
hints.append(hint)
|
hints.append(hint)
|
||||||
return hints
|
return hints
|
||||||
|
1
tests/integration/data/hinting.txt
Normal file
1
tests/integration/data/hinting.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
hinting
|
32
tests/integration/data/hints/issue1393.html
Normal file
32
tests/integration/data/hints/issue1393.html
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Let's Hint some words</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Word hints</h1>
|
||||||
|
|
||||||
|
<h2>Smart hints</h2>
|
||||||
|
<p>In qutebrowser, urls can not only be hinted with letters and
|
||||||
|
numbers, but also with <a href="../words.txt">words</a>. When there is
|
||||||
|
a sensible url text available, qutebrowser will even use that
|
||||||
|
text to create a <a href="../smart.txt">smart</a> hint.</p>
|
||||||
|
|
||||||
|
<h2>Filled hints</h2>
|
||||||
|
<p>When no smart hints are available, because the hint text is
|
||||||
|
<a href="../l33t.txt">too</a> short or <a href="../l33t.txt">l33t</a> to
|
||||||
|
use, words from a dictionary will be used.</p>
|
||||||
|
|
||||||
|
<h2>Hint conflicts</h2>
|
||||||
|
<p>Of course, hints have to be unique. For instance, all hints
|
||||||
|
below should get a different hint, whether they're smart or
|
||||||
|
not:</p>
|
||||||
|
<ul>
|
||||||
|
<li><a href="../hinting.txt">hinting</a> should be a smart hint</li>
|
||||||
|
<li><a href="../l33t.txt">word</a> is a prefix of words</li>
|
||||||
|
<li><a href="../l33t.txt">3</a> is too 1337</li>
|
||||||
|
<li><a href="../l33t.txt">4</a> is too 1337</li>
|
||||||
|
</ul>
|
||||||
|
</body>
|
||||||
|
</html>
|
1
tests/integration/data/l33t.txt
Normal file
1
tests/integration/data/l33t.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
l33t
|
1
tests/integration/data/smart.txt
Normal file
1
tests/integration/data/smart.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
smart
|
1
tests/integration/data/words.txt
Normal file
1
tests/integration/data/words.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
words
|
@ -25,6 +25,7 @@ import os.path
|
|||||||
import yaml
|
import yaml
|
||||||
import pytest
|
import pytest
|
||||||
import bs4
|
import bs4
|
||||||
|
import textwrap
|
||||||
|
|
||||||
|
|
||||||
def collect_tests():
|
def collect_tests():
|
||||||
@ -54,3 +55,35 @@ def test_hints(test_name, quteproc):
|
|||||||
quteproc.wait_for(message='hints: a', category='hints')
|
quteproc.wait_for(message='hints: a', category='hints')
|
||||||
quteproc.send_cmd(':follow-hint a')
|
quteproc.send_cmd(':follow-hint a')
|
||||||
quteproc.wait_for_load_finished('data/' + parsed['target'])
|
quteproc.wait_for_load_finished('data/' + parsed['target'])
|
||||||
|
|
||||||
|
|
||||||
|
def test_word_hints_issue1393(quteproc, tmpdir):
|
||||||
|
dict_file = tmpdir / 'dict'
|
||||||
|
dict_file.write(textwrap.dedent("""
|
||||||
|
alph
|
||||||
|
beta
|
||||||
|
gamm
|
||||||
|
delt
|
||||||
|
epsi
|
||||||
|
"""))
|
||||||
|
targets = [
|
||||||
|
('words', 'words.txt'),
|
||||||
|
('smart', 'smart.txt'),
|
||||||
|
('hinting', 'hinting.txt'),
|
||||||
|
('alph', 'l33t.txt'),
|
||||||
|
('beta', 'l33t.txt'),
|
||||||
|
('gamm', 'l33t.txt'),
|
||||||
|
('delt', 'l33t.txt'),
|
||||||
|
('epsi', 'l33t.txt'),
|
||||||
|
]
|
||||||
|
|
||||||
|
quteproc.set_setting('hints', 'mode', 'word')
|
||||||
|
quteproc.set_setting('hints', 'dictionary', str(dict_file))
|
||||||
|
|
||||||
|
for hint, target in targets:
|
||||||
|
quteproc.open_path('data/hints/issue1393.html')
|
||||||
|
quteproc.wait_for_load_finished('data/hints/issue1393.html')
|
||||||
|
quteproc.send_cmd(':hint')
|
||||||
|
quteproc.wait_for(message='hints: *', category='hints')
|
||||||
|
quteproc.send_cmd(':follow-hint {}'.format(hint))
|
||||||
|
quteproc.wait_for_load_finished('data/{}'.format(target))
|
||||||
|
Loading…
Reference in New Issue
Block a user