Add a config option for navigate_incdec
Also known as Ctrl-A/Ctrl-X. You can now specify which parts of the URL should be searched for numbers. The setting is general->url-incdec-segments and it's a set with valid values of 'host', 'path', 'query' and 'anchor'.
This commit is contained in:
parent
eb662a2468
commit
1bdb012b2c
@ -471,8 +471,9 @@ class CommandDispatcher:
|
||||
background: Open the link in a new background tab.
|
||||
window: Open the link in a new window.
|
||||
"""
|
||||
segments = config.get('general', 'url-incdec-segments')
|
||||
try:
|
||||
new_url = urlutils.incdec_number(url, incdec)
|
||||
new_url = urlutils.incdec_number(url, incdec, segments=segments)
|
||||
except urlutils.IncDecError as error:
|
||||
raise cmdexc.CommandError(error.msg)
|
||||
self._open(new_url, tab, background, window)
|
||||
|
@ -225,6 +225,11 @@ def data(readonly=False):
|
||||
"The name of the session to save by default, or empty for the "
|
||||
"last loaded session."),
|
||||
|
||||
('url-incdec-segments',
|
||||
SettingValue(typ.URLSegmentList(none_ok=True), 'path,query'),
|
||||
"The URL segments where `:navigate increment/decrement` will "
|
||||
"search for a number."),
|
||||
|
||||
readonly=readonly
|
||||
)),
|
||||
|
||||
|
@ -1578,3 +1578,37 @@ class TabBarShow(BaseType):
|
||||
"is open."),
|
||||
('switching', "Show the tab bar when switching "
|
||||
"tabs."))
|
||||
|
||||
|
||||
class URLSegmentList(List):
|
||||
|
||||
"""A list of URL segments."""
|
||||
|
||||
valid_values = ValidValues('host', 'path', 'query', 'anchor')
|
||||
|
||||
def transform(self, value):
|
||||
values = super().transform(value)
|
||||
if values is None:
|
||||
return set()
|
||||
return set(values)
|
||||
|
||||
def validate(self, value):
|
||||
self._basic_validation(value)
|
||||
segments = super().transform(value)
|
||||
if segments is None:
|
||||
# Basic validation already checked if none_ok is True
|
||||
return
|
||||
faulty_segments = set(segments) - set(self.valid_values.values)
|
||||
# faulty_segments is a set of options that are given but not valid
|
||||
if None in faulty_segments:
|
||||
raise configexc.ValidationError(value,
|
||||
"Empty list item is not allowed")
|
||||
if faulty_segments:
|
||||
error_str = ("List contains invalid segments: {}"
|
||||
.format(', '.join(faulty_segments)))
|
||||
raise configexc.ValidationError(value, error_str)
|
||||
|
||||
# Make sure there are no duplicates
|
||||
if len(set(segments)) != len(segments):
|
||||
raise configexc.ValidationError(value,
|
||||
"List contains duplicate segments")
|
||||
|
@ -456,12 +456,15 @@ class IncDecError(Exception):
|
||||
return '{}: {}'.format(self.msg, self.url.toString())
|
||||
|
||||
|
||||
def incdec_number(url, incdec):
|
||||
def incdec_number(url, incdec, segments=None):
|
||||
"""Find a number in the url and increment or decrement it.
|
||||
|
||||
Args:
|
||||
url: The current url
|
||||
incdec: Either 'increment' or 'decrement'
|
||||
segments: A set of URL segments to search. Valid segments are:
|
||||
'host', 'path', 'query', 'anchor'.
|
||||
Default: {'path', 'query'}
|
||||
|
||||
Return:
|
||||
The new url with the number incremented/decremented.
|
||||
@ -471,24 +474,46 @@ def incdec_number(url, incdec):
|
||||
if not url.isValid():
|
||||
raise InvalidUrlError(url)
|
||||
|
||||
path = url.path()
|
||||
# Get the last number in a string
|
||||
match = re.match(r'(.*\D|^)(\d+)(.*)', path)
|
||||
if not match:
|
||||
raise IncDecError("No number found in URL!", url)
|
||||
pre, number, post = match.groups()
|
||||
# This should always succeed because we match \d+
|
||||
val = int(number)
|
||||
if incdec == 'decrement':
|
||||
if val <= 0:
|
||||
raise IncDecError("Can't decrement {}!".format(val), url)
|
||||
val -= 1
|
||||
elif incdec == 'increment':
|
||||
val += 1
|
||||
else:
|
||||
raise ValueError("Invalid value {} for indec!".format(incdec))
|
||||
new_path = ''.join([pre, str(val), post])
|
||||
if segments is None:
|
||||
segments = {'path', 'query'}
|
||||
valid_segments = {'host', 'path', 'query', 'anchor'}
|
||||
if segments - valid_segments:
|
||||
extra_elements = segments - valid_segments
|
||||
raise IncDecError("Invalid segments: {}".format(
|
||||
', '.join(extra_elements)), url)
|
||||
|
||||
# Make a copy of the QUrl so we don't modify the original
|
||||
new_url = QUrl(url)
|
||||
new_url.setPath(new_path)
|
||||
return new_url
|
||||
url = QUrl(url)
|
||||
# Order as they appear in a URL
|
||||
segment_modifiers = [
|
||||
('host', url.host, url.setHost),
|
||||
('path', url.path, url.setPath),
|
||||
('query', url.query, url.setQuery),
|
||||
('anchor', url.fragment, url.setFragment),
|
||||
]
|
||||
# We're searching the last number so we walk the url segments backwards
|
||||
for segment, getter, setter in reversed(segment_modifiers):
|
||||
if segment not in segments:
|
||||
continue
|
||||
|
||||
# Get the last number in a string
|
||||
match = re.match(r'(.*\D|^)(\d+)(.*)', getter())
|
||||
if not match:
|
||||
continue
|
||||
|
||||
pre, number, post = match.groups()
|
||||
# This should always succeed because we match \d+
|
||||
val = int(number)
|
||||
if incdec == 'decrement':
|
||||
if val <= 0:
|
||||
raise IncDecError("Can't decrement {}!".format(val), url)
|
||||
val -= 1
|
||||
elif incdec == 'increment':
|
||||
val += 1
|
||||
else:
|
||||
raise ValueError("Invalid value {} for indec!".format(incdec))
|
||||
new_value = ''.join([pre, str(val), post])
|
||||
setter(new_value)
|
||||
return url
|
||||
|
||||
raise IncDecError("No number found in URL!", url)
|
||||
|
@ -1902,6 +1902,43 @@ class TestUserAgent:
|
||||
"""Simple smoke test for completion."""
|
||||
klass().complete()
|
||||
|
||||
class TestURLSegmentList:
|
||||
|
||||
"""Test URLSegmentList."""
|
||||
|
||||
@pytest.fixture
|
||||
def klass(self):
|
||||
return configtypes.URLSegmentList
|
||||
|
||||
@pytest.mark.parametrize('val', [
|
||||
'', 'host', 'path', 'query', 'anchor', 'host,path,anchor',
|
||||
])
|
||||
def test_validate_valid(self, klass, val):
|
||||
klass(none_ok=True).validate(val)
|
||||
|
||||
def test_validate_empty(self, klass):
|
||||
with pytest.raises(configexc.ValidationError):
|
||||
klass(none_ok=False).validate('')
|
||||
|
||||
@pytest.mark.parametrize('val', [
|
||||
'foo', 'bar', 'foo,bar', 'host,path,foo', 'host,host'
|
||||
])
|
||||
def test_validate_invalid(self, klass, val):
|
||||
with pytest.raises(configexc.ValidationError):
|
||||
klass(none_ok=True).validate(val)
|
||||
|
||||
@pytest.mark.parametrize('val, expected', [
|
||||
('', set()),
|
||||
('path', {'path'}),
|
||||
('path,query', {'path', 'query'}),
|
||||
])
|
||||
def test_transform(self, klass, val, expected):
|
||||
assert klass().transform(val) == expected
|
||||
|
||||
def test_complete(self, klass):
|
||||
"""Simple smoke test for completion."""
|
||||
klass().complete()
|
||||
|
||||
|
||||
@pytest.mark.parametrize('first, second, equal', [
|
||||
(re.compile('foo'), RegexEq('foo'), True),
|
||||
|
@ -531,22 +531,36 @@ class TestIncDecNumber:
|
||||
|
||||
"""Tests for urlutils.incdec_number()."""
|
||||
|
||||
@pytest.mark.parametrize('url, incdec, output', [
|
||||
("http://example.com/index1.html", "increment", "http://example.com/index2.html"),
|
||||
("http://foo.bar/folder_1/image_2", "increment", "http://foo.bar/folder_1/image_3"),
|
||||
("http://bbc.c0.uk:80/story_1", "increment", "http://bbc.c0.uk:80/story_2"),
|
||||
("http://mydomain.tld/1_%C3%A4", "increment", "http://mydomain.tld/2_%C3%A4"),
|
||||
("http://example.com/site/5#5", "increment", "http://example.com/site/6#5"),
|
||||
@pytest.mark.parametrize('url, incdec, output, segments', [
|
||||
("http://example.com/index1.html", "increment",
|
||||
"http://example.com/index2.html", {'path'}),
|
||||
("http://foo.bar/folder_1/image_2", "increment",
|
||||
"http://foo.bar/folder_1/image_3", {'path'}),
|
||||
("http://bbc.c0.uk:80/story_1", "increment",
|
||||
"http://bbc.c0.uk:80/story_2", {'path'}),
|
||||
("http://mydomain.tld/1_%C3%A4", "increment",
|
||||
"http://mydomain.tld/2_%C3%A4", {'path'}),
|
||||
("http://example.com/site/5#5", "increment",
|
||||
"http://example.com/site/6#5", {'path'}),
|
||||
("http://example.com/site/1#1", "increment",
|
||||
"http://example.com/site/1#2", {'path', 'anchor'}),
|
||||
("http://example.com/site/1?page=2#3", "increment",
|
||||
"http://example.com/site/1?page=3#3", {'path', 'query'}),
|
||||
|
||||
("http://example.com/index10.html", "decrement", "http://example.com/index9.html"),
|
||||
("http://foo.bar/folder_1/image_3", "decrement", "http://foo.bar/folder_1/image_2"),
|
||||
("http://bbc.c0.uk:80/story_1", "decrement", "http://bbc.c0.uk:80/story_0"),
|
||||
("http://mydomain.tld/2_%C3%A4", "decrement", "http://mydomain.tld/1_%C3%A4"),
|
||||
("http://example.com/site/5#5", "decrement", "http://example.com/site/4#5"),
|
||||
("http://example.com/index10.html", "decrement",
|
||||
"http://example.com/index9.html", {'path'}),
|
||||
("http://foo.bar/folder_1/image_3", "decrement",
|
||||
"http://foo.bar/folder_1/image_2", {'path'}),
|
||||
("http://bbc.c0.uk:80/story_1", "decrement",
|
||||
"http://bbc.c0.uk:80/story_0", {'path'}),
|
||||
("http://mydomain.tld/2_%C3%A4", "decrement",
|
||||
"http://mydomain.tld/1_%C3%A4", {'path'}),
|
||||
("http://example.com/site/5#5", "decrement",
|
||||
"http://example.com/site/4#5", {'path'}),
|
||||
])
|
||||
def test_incdec_number(self, url, incdec, output):
|
||||
def test_incdec_number(self, url, incdec, output, segments):
|
||||
"""Test incdec_number with valid URLs."""
|
||||
new_url = urlutils.incdec_number(QUrl(url), incdec)
|
||||
new_url = urlutils.incdec_number(QUrl(url), incdec, segments=segments)
|
||||
assert new_url == QUrl(output)
|
||||
|
||||
@pytest.mark.parametrize('url', [
|
||||
@ -582,6 +596,12 @@ class TestIncDecNumber:
|
||||
with pytest.raises(ValueError):
|
||||
urlutils.incdec_number(valid_url, "foobar")
|
||||
|
||||
def test_wrong_segment(self):
|
||||
"""Test if incdec_number rejects a wrong segment"""
|
||||
with pytest.raises(urlutils.IncDecError):
|
||||
urlutils.incdec_number(QUrl('http://example.com'),
|
||||
'increment', segments={'foobar'})
|
||||
|
||||
@pytest.mark.parametrize("url, msg, expected_str", [
|
||||
("http://example.com", "Invalid", "Invalid: http://example.com"),
|
||||
])
|
||||
|
Loading…
Reference in New Issue
Block a user