Merge branch 'master' into new-config

This commit is contained in:
Florian Bruhin 2017-09-09 10:52:02 +02:00
commit a283a1bb65
16 changed files with 203 additions and 35 deletions

View File

@ -1 +1 @@
schedule: "every week" schedule: "every week on monday"

View File

@ -479,7 +479,8 @@ The following arguments are supported for `@cmdutils.argument`:
- `win_id=True`: Mark the argument as special window ID argument - `win_id=True`: Mark the argument as special window ID argument
- `count=True`: Mark the argument as special count argument - `count=True`: Mark the argument as special count argument
- `hide=True`: Hide the argument from the documentation - `hide=True`: Hide the argument from the documentation
- `completion`: A `usertypes.Completion` member to use as completion. - `completion`: A completion function (see `qutebrowser.completions.models.*`)
to use when completing arguments for the given command.
- `choices`: The allowed string choices for the argument. - `choices`: The allowed string choices for the argument.
The name of an argument will always be the parameter name, with any trailing The name of an argument will always be the parameter name, with any trailing

View File

@ -85,7 +85,7 @@ qutebrowser is available in the official repositories for Fedora 22 and newer.
# dnf install qutebrowser # dnf install qutebrowser
---- ----
It's also recommended to install `qt5-qtwebengine` and start with `--backend It's also recommended to install `python3-qt5-webengine` and start with `--backend
webengine` to use the new backend. webengine` to use the new backend.
On Archlinux On Archlinux
@ -175,6 +175,12 @@ If video or sound don't seem to work, try installing the gstreamer plugins:
# emerge -av gst-plugins-{base,good,bad,ugly,libav} # emerge -av gst-plugins-{base,good,bad,ugly,libav}
---- ----
To be able to play videos with proprietary codecs with QtWebEngine, you will
need to turn off the `bindist` flag for `dev-qt/qtwebengine`.
See the https://wiki.gentoo.org/wiki/Qutebrowser#USE_flags[Gentoo Wiki] for
more information.
On Void Linux On Void Linux
------------- -------------

View File

@ -5,5 +5,5 @@ chardet==3.0.4
codecov==2.0.9 codecov==2.0.9
coverage==4.4.1 coverage==4.4.1
idna==2.6 idna==2.6
requests==2.18.3 requests==2.18.4
urllib3==1.22 urllib3==1.22

View File

@ -10,7 +10,7 @@ lazy-object-proxy==1.3.1
mccabe==0.6.1 mccabe==0.6.1
-e git+https://github.com/PyCQA/pylint.git#egg=pylint -e git+https://github.com/PyCQA/pylint.git#egg=pylint
./scripts/dev/pylint_checkers ./scripts/dev/pylint_checkers
requests==2.18.3 requests==2.18.4
six==1.10.0 six==1.10.0
uritemplate==3.0.0 uritemplate==3.0.0
uritemplate.py==3.0.2 uritemplate.py==3.0.2

View File

@ -10,7 +10,7 @@ lazy-object-proxy==1.3.1
mccabe==0.6.1 mccabe==0.6.1
pylint==1.7.2 pylint==1.7.2
./scripts/dev/pylint_checkers ./scripts/dev/pylint_checkers
requests==2.18.3 requests==2.18.4
six==1.10.0 six==1.10.0
uritemplate==3.0.0 uritemplate==3.0.0
uritemplate.py==3.0.2 uritemplate.py==3.0.2

View File

@ -9,10 +9,10 @@ decorator==4.1.2
EasyProcess==0.2.3 EasyProcess==0.2.3
fields==5.0.0 fields==5.0.0
Flask==0.12.2 Flask==0.12.2
glob2==0.5 glob2==0.6
httpbin==0.5.0 httpbin==0.5.0
hunter==1.4.1 hunter==1.4.1
hypothesis==3.18.0 hypothesis==3.19.1
itsdangerous==0.24 itsdangerous==0.24
# Jinja2==2.9.6 # Jinja2==2.9.6
Mako==1.0.7 Mako==1.0.7
@ -30,10 +30,10 @@ pytest-instafail==0.3.0
pytest-mock==1.6.2 pytest-mock==1.6.2
pytest-qt==2.1.2 pytest-qt==2.1.2
pytest-repeat==0.4.1 pytest-repeat==0.4.1
pytest-rerunfailures==2.2 pytest-rerunfailures==3.0
pytest-travis-fold==1.2.0 pytest-travis-fold==1.2.0
pytest-xvfb==1.0.0 pytest-xvfb==1.0.0
PyVirtualDisplay==0.2.1 PyVirtualDisplay==0.2.1
six==1.10.0 six==1.10.0
vulture==0.24 vulture==0.25
Werkzeug==0.12.2 Werkzeug==0.12.2

View File

@ -1,3 +1,3 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py # This file is automatically generated by scripts/dev/recompile_requirements.py
vulture==0.24 vulture==0.25

47
misc/userscripts/format_json Executable file
View File

@ -0,0 +1,47 @@
#!/bin/sh
#
# Behavior:
# Userscript for qutebrowser which will take the raw JSON text of the current
# page, format it using `jq`, will add syntax highlighting using `pygments`,
# and open the syntax highlighted pretty printed html in a new tab. If the file
# is larger than 10MB then this script will only indent the json and will forego
# syntax highlighting using pygments.
#
# In order to use this script, just start it using `spawn --userscript` from
# qutebrowser. I recommend using an alias, e.g. put this in the
# [alias]-section of qutebrowser.conf:
#
# json = spawn --userscript /path/to/json_format
#
# Note that the color style defaults to monokai, but a different pygments style
# can be passed as the first parameter to the script. A full list of the pygments
# styles can be found at: https://help.farbox.com/pygments.html
#
# Bryan Gilbert, 2017
# default style to monokai if none is provided
STYLE=${1:-monokai}
# format json using jq
FORMATTED_JSON="$(cat "$QUTE_TEXT" | jq '.')"
# if jq command failed or formatted json is empty, assume failure and terminate
if [ $? -ne 0 ] || [ -z "$FORMATTED_JSON" ]; then
echo "Invalid json, aborting..."
exit 1
fi
# calculate the filesize of the json document
FILE_SIZE=$(ls -s --block-size=1048576 "$QUTE_TEXT" | cut -d' ' -f1)
# use pygments to pretty-up the json (syntax highlight) if file is less than 10MB
if [ "$FILE_SIZE" -lt "10" ]; then
FORMATTED_JSON="$(echo "$FORMATTED_JSON" | pygmentize -l json -f html -O full,style=$STYLE)"
fi
# create a temp file and write the formatted json to that file
TEMP_FILE="$(mktemp --suffix '.html')"
echo "$FORMATTED_JSON" > $TEMP_FILE
# send the command to qutebrowser to open the new file containing the formatted json
echo "open -t file://$TEMP_FILE" >> "$QUTE_FIFO"

View File

@ -327,6 +327,17 @@ open_entry "$file"
js() { js() {
cat <<EOF cat <<EOF
function isVisible(elem) {
var style = elem.ownerDocument.defaultView.getComputedStyle(elem, null);
if (style.getPropertyValue("visibility") !== "visible" ||
style.getPropertyValue("display") === "none" ||
style.getPropertyValue("opacity") === "0") {
return false;
}
return elem.offsetWidth > 0 && elem.offsetHeight > 0;
};
function hasPasswordField(form) { function hasPasswordField(form) {
var inputs = form.getElementsByTagName("input"); var inputs = form.getElementsByTagName("input");
for (var j = 0; j < inputs.length; j++) { for (var j = 0; j < inputs.length; j++) {
@ -341,7 +352,7 @@ cat <<EOF
var inputs = form.getElementsByTagName("input"); var inputs = form.getElementsByTagName("input");
for (var j = 0; j < inputs.length; j++) { for (var j = 0; j < inputs.length; j++) {
var input = inputs[j]; var input = inputs[j];
if (input.type == "text" || input.type == "email") { if (isVisible(input) && (input.type == "text" || input.type == "email")) {
input.focus(); input.focus();
input.value = "$(javascript_escape "${username}")"; input.value = "$(javascript_escape "${username}")";
input.blur(); input.blur();

View File

@ -30,6 +30,10 @@ from qutebrowser.utils import (utils, objreg, log, usertypes, message,
from qutebrowser.misc import objects, sql from qutebrowser.misc import objects, sql
# increment to indicate that HistoryCompletion must be regenerated
_USER_VERSION = 1
class CompletionHistory(sql.SqlTable): class CompletionHistory(sql.SqlTable):
"""History which only has the newest entry for each URL.""" """History which only has the newest entry for each URL."""
@ -48,6 +52,11 @@ class WebHistory(sql.SqlTable):
super().__init__("History", ['url', 'title', 'atime', 'redirect'], super().__init__("History", ['url', 'title', 'atime', 'redirect'],
parent=parent) parent=parent)
self.completion = CompletionHistory(parent=self) self.completion = CompletionHistory(parent=self)
if sql.Query('pragma user_version').run().value() < _USER_VERSION:
self.completion.delete_all()
if not self.completion:
# either the table is out-of-date or the user wiped it manually
self._rebuild_completion()
self.create_index('HistoryIndex', 'url') self.create_index('HistoryIndex', 'url')
self.create_index('HistoryAtimeIndex', 'atime') self.create_index('HistoryAtimeIndex', 'atime')
self._contains_query = self.contains_query('url') self._contains_query = self.contains_query('url')
@ -71,6 +80,18 @@ class WebHistory(sql.SqlTable):
def __contains__(self, url): def __contains__(self, url):
return self._contains_query.run(val=url).value() return self._contains_query.run(val=url).value()
def _rebuild_completion(self):
data = {'url': [], 'title': [], 'last_atime': []}
# select the latest entry for each url
q = sql.Query('SELECT url, title, max(atime) AS atime FROM History '
'WHERE NOT redirect GROUP BY url ORDER BY atime asc')
for entry in q.run():
data['url'].append(self._format_completion_url(QUrl(entry.url)))
data['title'].append(entry.title)
data['last_atime'].append(entry.atime)
self.completion.insert_batch(data, replace=True)
sql.Query('pragma user_version = {}'.format(_USER_VERSION)).run()
def get_recent(self): def get_recent(self):
"""Get the most recent history entries.""" """Get the most recent history entries."""
return self.select(sort_by='atime', sort_order='desc', limit=100) return self.select(sort_by='atime', sort_order='desc', limit=100)
@ -159,13 +180,12 @@ class WebHistory(sql.SqlTable):
return return
atime = int(atime) if (atime is not None) else int(time.time()) atime = int(atime) if (atime is not None) else int(time.time())
url_str = url.toString(QUrl.FullyEncoded | QUrl.RemovePassword) self.insert({'url': self._format_url(url),
self.insert({'url': url_str,
'title': title, 'title': title,
'atime': atime, 'atime': atime,
'redirect': redirect}) 'redirect': redirect})
if not redirect: if not redirect:
self.completion.insert({'url': url_str, self.completion.insert({'url': self._format_completion_url(url),
'title': title, 'title': title,
'last_atime': atime}, 'last_atime': atime},
replace=True) replace=True)
@ -249,12 +269,13 @@ class WebHistory(sql.SqlTable):
if parsed is None: if parsed is None:
continue continue
url, title, atime, redirect = parsed url, title, atime, redirect = parsed
data['url'].append(url) data['url'].append(self._format_url(url))
data['title'].append(title) data['title'].append(title)
data['atime'].append(atime) data['atime'].append(atime)
data['redirect'].append(redirect) data['redirect'].append(redirect)
if not redirect: if not redirect:
completion_data['url'].append(url) completion_data['url'].append(
self._format_completion_url(url))
completion_data['title'].append(title) completion_data['title'].append(title)
completion_data['last_atime'].append(atime) completion_data['last_atime'].append(atime)
except ValueError as ex: except ValueError as ex:
@ -263,6 +284,12 @@ class WebHistory(sql.SqlTable):
self.insert_batch(data) self.insert_batch(data)
self.completion.insert_batch(completion_data, replace=True) self.completion.insert_batch(completion_data, replace=True)
def _format_url(self, url):
return url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
def _format_completion_url(self, url):
return url.toString(QUrl.RemovePassword)
@cmdutils.register(instance='web-history', debug=True) @cmdutils.register(instance='web-history', debug=True)
def debug_dump_history(self, dest): def debug_dump_history(self, dest):
"""Dump the history to a file in the old pre-SQL format. """Dump the history to a file in the old pre-SQL format.

View File

@ -43,3 +43,4 @@ rules:
array-bracket-newline: "off" array-bracket-newline: "off"
array-element-newline: "off" array-element-newline: "off"
no-multi-spaces: ["error", {"ignoreEOLComments": true}] no-multi-spaces: ["error", {"ignoreEOLComments": true}]
function-paren-newline: "off"

View File

@ -480,7 +480,7 @@ class SessionManager(QObject):
try: try:
if only_active_window: if only_active_window:
name = self.save(name, only_window=win_id, name = self.save(name, only_window=win_id,
with_private=with_private) with_private=True)
else: else:
name = self.save(name, with_private=with_private) name = self.save(name, with_private=with_private)
except SessionError as e: except SessionError as e:

View File

@ -132,10 +132,11 @@ def report(items):
properties which get used for the items. properties which get used for the items.
""" """
output = [] output = []
for item in sorted(items, key=lambda e: (e.filename.lower(), e.lineno)): for item in sorted(items,
key=lambda e: (e.filename.lower(), e.first_lineno)):
relpath = os.path.relpath(item.filename) relpath = os.path.relpath(item.filename)
path = relpath if not relpath.startswith('..') else item.filename path = relpath if not relpath.startswith('..') else item.filename
output.append("{}:{}: Unused {} '{}'".format(path, item.lineno, output.append("{}:{}: Unused {} '{}'".format(path, item.first_lineno,
item.typ, item.name)) item.typ, item.name))
return output return output
@ -148,7 +149,7 @@ def run(files):
whitelist_file.close() whitelist_file.close()
vult = vulture.Vulture(exclude=[], verbose=False) vult = vulture.Vulture(verbose=False)
vult.scavenge(files + [whitelist_file.name]) vult.scavenge(files + [whitelist_file.name])
os.remove(whitelist_file.name) os.remove(whitelist_file.name)

View File

@ -153,3 +153,26 @@ Feature: Using private browsing
- history: - history:
- url: http://localhost:*/data/numbers/1.txt - url: http://localhost:*/data/numbers/1.txt
- url: http://localhost:*/data/numbers/2.txt - url: http://localhost:*/data/numbers/2.txt
Scenario: Saving a private session with only-active-window
When I open data/numbers/1.txt
And I open data/numbers/2.txt in a new tab
And I open data/numbers/3.txt in a private window
And I open data/numbers/4.txt in a new tab
And I open data/numbers/5.txt in a new tab
And I run :session-save --only-active-window window_session_name
And I run :window-only
And I run :tab-only
And I run :session-load -c window_session_name
And I wait until data/numbers/5.txt is loaded
Then the session should look like:
windows:
- tabs:
- history:
- url: http://localhost:*/data/numbers/3.txt
- history:
- url: http://localhost:*/data/numbers/4.txt
- history:
- active: true
url: http://localhost:*/data/numbers/5.txt

View File

@ -143,23 +143,27 @@ def test_delete_url(hist):
assert completion_diff == {('http://example.com/1', '', 0)} assert completion_diff == {('http://example.com/1', '', 0)}
@pytest.mark.parametrize('url, atime, title, redirect, expected_url', [ @pytest.mark.parametrize(
('http://www.example.com', 12346, 'the title', False, 'url, atime, title, redirect, history_url, completion_url', [
'http://www.example.com'),
('http://www.example.com', 12346, 'the title', True, ('http://www.example.com', 12346, 'the title', False,
'http://www.example.com'), 'http://www.example.com', 'http://www.example.com'),
('http://www.example.com/spa ce', 12346, 'the title', False, ('http://www.example.com', 12346, 'the title', True,
'http://www.example.com/spa%20ce'), 'http://www.example.com', None),
('https://user:pass@example.com', 12346, 'the title', False, ('http://www.example.com/sp ce', 12346, 'the title', False,
'https://user@example.com'), 'http://www.example.com/sp%20ce', 'http://www.example.com/sp ce'),
]) ('https://user:pass@example.com', 12346, 'the title', False,
def test_add_url(qtbot, hist, url, atime, title, redirect, expected_url): 'https://user@example.com', 'https://user@example.com'),
]
)
def test_add_url(qtbot, hist, url, atime, title, redirect, history_url,
completion_url):
hist.add_url(QUrl(url), atime=atime, title=title, redirect=redirect) hist.add_url(QUrl(url), atime=atime, title=title, redirect=redirect)
assert list(hist) == [(expected_url, title, atime, redirect)] assert list(hist) == [(history_url, title, atime, redirect)]
if redirect: if completion_url is None:
assert not len(hist.completion) assert not len(hist.completion)
else: else:
assert list(hist.completion) == [(expected_url, title, atime)] assert list(hist.completion) == [(completion_url, title, atime)]
def test_add_url_invalid(qtbot, hist, caplog): def test_add_url_invalid(qtbot, hist, caplog):
@ -349,3 +353,50 @@ def test_debug_dump_history_nonexistent(hist, tmpdir):
histfile = tmpdir / 'nonexistent' / 'history' histfile = tmpdir / 'nonexistent' / 'history'
with pytest.raises(cmdexc.CommandError): with pytest.raises(cmdexc.CommandError):
hist.debug_dump_history(str(histfile)) hist.debug_dump_history(str(histfile))
def test_rebuild_completion(hist):
hist.insert({'url': 'example.com/1', 'title': 'example1',
'redirect': False, 'atime': 1})
hist.insert({'url': 'example.com/1', 'title': 'example1',
'redirect': False, 'atime': 2})
hist.insert({'url': 'example.com/2%203', 'title': 'example2',
'redirect': False, 'atime': 3})
hist.insert({'url': 'example.com/3', 'title': 'example3',
'redirect': True, 'atime': 4})
hist.insert({'url': 'example.com/2 3', 'title': 'example2',
'redirect': False, 'atime': 5})
hist.completion.delete_all()
hist2 = history.WebHistory()
assert list(hist2.completion) == [
('example.com/1', 'example1', 2),
('example.com/2 3', 'example2', 5),
]
def test_no_rebuild_completion(hist):
"""Ensure that completion is not regenerated unless completely empty."""
hist.add_url(QUrl('example.com/1'), redirect=False, atime=1)
hist.add_url(QUrl('example.com/2'), redirect=False, atime=2)
hist.completion.delete('url', 'example.com/2')
hist2 = history.WebHistory()
assert list(hist2.completion) == [('example.com/1', '', 1)]
def test_user_version(hist, monkeypatch):
"""Ensure that completion is regenerated if user_version is incremented."""
hist.add_url(QUrl('example.com/1'), redirect=False, atime=1)
hist.add_url(QUrl('example.com/2'), redirect=False, atime=2)
hist.completion.delete('url', 'example.com/2')
hist2 = history.WebHistory()
assert list(hist2.completion) == [('example.com/1', '', 1)]
monkeypatch.setattr(history, '_USER_VERSION', history._USER_VERSION + 1)
hist3 = history.WebHistory()
assert list(hist3.completion) == [
('example.com/1', '', 1),
('example.com/2', '', 2),
]