diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index 7a89b042a..387d4225b 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -209,7 +209,22 @@ def data(readonly=False): "be used."), ('new-instance-open-target', - SettingValue(typ.NewInstanceOpenTarget(), 'tab'), + SettingValue(typ.String( + valid_values=typ.ValidValues( + ('tab', "Open a new tab in the existing " + "window and activate the window."), + ('tab-bg', "Open a new background tab in the " + "existing window and activate the " + "window."), + ('tab-silent', "Open a new tab in the existing " + "window without activating " + "the window."), + ('tab-bg-silent', "Open a new background tab " + "in the existing window " + "without activating the " + "window."), + ('window', "Open in a new window.") + )), 'tab'), "How to open links in an existing instance if a new one is " "launched."), @@ -227,7 +242,10 @@ def data(readonly=False): "last loaded session."), ('url-incdec-segments', - SettingValue(typ.URLSegmentList(), 'path,query'), + SettingValue( + typ.FlagList(valid_values=typ.ValidValues( + 'host', 'path', 'query', 'anchor')), + 'path,query'), "The URL segments where `:navigate increment/decrement` will " "search for a number."), @@ -345,7 +363,15 @@ def data(readonly=False): "Value to send in the `accept-language` header."), ('referer-header', - SettingValue(typ.Referer(), 'same-domain'), + SettingValue(typ.String( + valid_values=typ.ValidValues( + ('always', "Always send."), + ('never', "Never send; this is not recommended," + " as some sites may break."), + ('same-domain', "Only send for the same domain." + " This will still protect your privacy, but" + " shouldn't break any sites.") + )), 'same-domain'), "Send the Referer header"), ('user-agent', @@ -379,7 +405,12 @@ def data(readonly=False): "Automatically open completion when typing."), ('download-path-suggestion', - SettingValue(typ.DownloadPathSuggestion(), 'path'), + SettingValue( + typ.String(valid_values=typ.ValidValues( + ('path', "Show only the download path."), + ('filename', "Show only download filename."), + ('both', "Show download path and filename."))), + 'path'), "What to display in the download filename input."), ('timestamp-format', @@ -452,7 +483,13 @@ def data(readonly=False): "element is focused after page load."), ('forward-unbound-keys', - SettingValue(typ.ForwardUnboundKeys(), 'auto'), + SettingValue(typ.String( + valid_values=typ.ValidValues( + ('all', "Forward all unbound keys."), + ('auto', "Forward unbound non-alphanumeric " + "keys."), + ('none', "Don't forward any keys.") + )), 'auto'), "Whether to forward unbound keys to the webview in normal mode."), ('spatial-navigation', @@ -502,11 +539,26 @@ def data(readonly=False): "How new tabs opened explicitly are positioned."), ('last-close', - SettingValue(typ.LastClose(), 'ignore'), + SettingValue(typ.String( + valid_values=typ.ValidValues( + ('ignore', "Don't do anything."), + ('blank', "Load a blank page."), + ('startpage', "Load the start page."), + ('default-page', "Load the default page."), + ('close', "Close the window.") + )), 'ignore'), "Behavior when the last tab is closed."), ('show', - SettingValue(typ.TabBarShow(), 'always'), + SettingValue( + typ.String(valid_values=typ.ValidValues( + ('always', "Always show the tab bar."), + ('never', "Always hide the tab bar."), + ('multiple', "Hide the tab bar if only one tab " + "is open."), + ('switching', "Show the tab bar when switching " + "tabs.") + )), 'always'), "When to show the tab bar"), ('show-switching-delay', @@ -523,7 +575,12 @@ def data(readonly=False): "Whether tabs should be movable."), ('close-mouse-button', - SettingValue(typ.CloseButton(), 'middle'), + SettingValue(typ.String( + valid_values=typ.ValidValues( + ('right', "Close tabs on right-click."), + ('middle', "Close tabs on middle-click."), + ('none', "Don't close tabs using the mouse.") + )), 'middle'), "On which mouse button to close tabs."), ('position', @@ -731,7 +788,16 @@ def data(readonly=False): "local urls."), ('cookies-accept', - SettingValue(typ.AcceptCookies(), 'no-3rdparty'), + SettingValue(typ.String( + valid_values=typ.ValidValues( + ('all', "Accept all cookies."), + ('no-3rdparty', "Accept cookies from the same" + " origin only."), + ('no-unknown-3rdparty', "Accept cookies from " + "the same origin only, unless a cookie is " + "already set for the domain."), + ('never', "Don't accept cookies at all.") + )), 'no-3rdparty'), "Control which cookies to accept."), ('cookies-store', @@ -779,7 +845,12 @@ def data(readonly=False): "Opacity for hints."), ('mode', - SettingValue(typ.HintMode(), 'letter'), + SettingValue(typ.String( + valid_values=typ.ValidValues( + ('number', "Use numeric hints."), + ('letter', "Use the chars in the hints -> " + "chars setting.") + )), 'letter'), "Mode to use for hints."), ('chars', diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py index 4c6a3b4c8..d4d498358 100644 --- a/qutebrowser/config/configtypes.py +++ b/qutebrowser/config/configtypes.py @@ -121,11 +121,11 @@ class BaseType: mentioned in the config file. """ - valid_values = None special = False def __init__(self, none_ok=False): self.none_ok = none_ok + self.valid_values = None def _basic_validation(self, value): """Do some basic validation for the value (empty, non-printable chars). @@ -216,11 +216,10 @@ class MappingType(BaseType): MAPPING = {} - def __init__(self, none_ok=False): + def __init__(self, none_ok=False, + valid_values=None): super().__init__(none_ok) - if list(sorted(self.MAPPING)) != list(sorted(self.valid_values)): - raise ValueError("Mapping {!r} doesn't match valid values " - "{!r}".format(self.MAPPING, self.valid_values)) + self.valid_values = valid_values def validate(self, value): super().validate(value.lower()) @@ -243,8 +242,10 @@ class String(BaseType): """ def __init__(self, minlen=None, maxlen=None, forbidden=None, - none_ok=False, completions=None): + none_ok=False, completions=None, valid_values=None): super().__init__(none_ok) + self.valid_values = valid_values + if minlen is not None and minlen < 1: raise ValueError("minlen ({}) needs to be >= 1!".format(minlen)) elif maxlen is not None and maxlen < 1: @@ -280,6 +281,10 @@ class List(BaseType): """Base class for a (string-)list setting.""" + def __init__(self, none_ok=False, valid_values=None): + super().__init__(none_ok) + self.valid_values = valid_values + def transform(self, value): if not value: return None @@ -351,7 +356,9 @@ class Bool(BaseType): """Base class for a boolean setting.""" - valid_values = ValidValues('true', 'false') + def __init__(self, none_ok=False): + super().__init__(none_ok) + self.valid_values = ValidValues('true', 'false') def transform(self, value): if not value: @@ -371,7 +378,9 @@ class BoolAsk(Bool): """A yes/no/ask question.""" - valid_values = ValidValues('true', 'false', 'ask') + def __init__(self, none_ok=False): + super().__init__(none_ok) + self.valid_values = ValidValues('true', 'false', 'ask') def transform(self, value): if value.lower() == 'ask': @@ -650,10 +659,15 @@ class ColorSystem(MappingType): """Color systems for interpolation.""" special = True - valid_values = ValidValues(('rgb', "Interpolate in the RGB color system."), - ('hsv', "Interpolate in the HSV color system."), - ('hsl', "Interpolate in the HSL color system."), - ('none', "Don't show a gradient.")) + + def __init__(self, none_ok=False): + super().__init__( + none_ok, + valid_values=ValidValues( + ('rgb', "Interpolate in the RGB color system."), + ('hsv', "Interpolate in the HSV color system."), + ('hsl', "Interpolate in the HSL color system."), + ('none', "Don't show a gradient."))) MAPPING = { 'rgb': QColor.Rgb, @@ -1087,23 +1101,11 @@ class ShellCommand(BaseType): return shlex.split(value) -class HintMode(BaseType): - - """Base class for the hints -> mode setting.""" - - special = True - valid_values = ValidValues(('number', "Use numeric hints."), - ('letter', "Use the chars in the hints -> " - "chars setting.")) - - class Proxy(BaseType): """A proxy URL or special value.""" special = True - valid_values = ValidValues(('system', "Use the system wide proxy."), - ('none', "Don't use any proxy")) PROXY_TYPES = { 'http': QNetworkProxy.HttpProxy, @@ -1111,6 +1113,12 @@ class Proxy(BaseType): 'socks5': QNetworkProxy.Socks5Proxy, } + def __init__(self, none_ok=False): + super().__init__(none_ok) + self.valid_values = ValidValues( + ('system', "Use the system wide proxy."), + ('none', "Don't use any proxy")) + def validate(self, value): self._basic_validation(value) if not value: @@ -1297,13 +1305,14 @@ class AutoSearch(BaseType): """Whether to start a search when something else than a URL is entered.""" special = True - valid_values = ValidValues(('naive', "Use simple/naive check."), - ('dns', "Use DNS requests (might be slow!)."), - ('false', "Never search automatically.")) def __init__(self, none_ok=False): super().__init__(none_ok) self.booltype = Bool(none_ok=none_ok) + self.valid_values = ValidValues( + ('naive', "Use simple/naive check."), + ('dns', "Use DNS requests (might be slow!)."), + ('false', "Never search automatically.")) def validate(self, value): self._basic_validation(value) @@ -1330,8 +1339,6 @@ class Position(MappingType): """The position of the tab bar.""" - valid_values = ValidValues('top', 'bottom', 'left', 'right') - MAPPING = { 'top': QTabWidget.North, 'bottom': QTabWidget.South, @@ -1339,12 +1346,19 @@ class Position(MappingType): 'right': QTabWidget.East, } + def __init__(self, none_ok=False): + super().__init__( + none_ok, + valid_values=ValidValues('top', 'bottom', 'left', 'right')) + class VerticalPosition(BaseType): """The position of the download bar.""" - valid_values = ValidValues('top', 'bottom') + def __init__(self, none_ok=False): + super().__init__(none_ok) + self.valid_values = ValidValues('top', 'bottom') class UrlList(List): @@ -1389,10 +1403,6 @@ class SelectOnRemove(MappingType): """Which tab to select when the focused tab is removed.""" special = True - valid_values = ValidValues( - ('left', "Select the tab on the left."), - ('right', "Select the tab on the right."), - ('previous', "Select the previously selected tab.")) MAPPING = { 'left': QTabBar.SelectLeftTab, @@ -1400,31 +1410,13 @@ class SelectOnRemove(MappingType): 'previous': QTabBar.SelectPreviousTab, } - -class LastClose(BaseType): - - """Behavior when the last tab is closed.""" - - special = True - valid_values = ValidValues(('ignore', "Don't do anything."), - ('blank', "Load a blank page."), - ('startpage', "Load the start page."), - ('default-page', "Load the default page."), - ('close', "Close the window.")) - - -class AcceptCookies(BaseType): - - """Control which cookies to accept.""" - - special = True - valid_values = ValidValues(('all', "Accept all cookies."), - ('no-3rdparty', "Accept cookies from the same" - " origin only."), - ('no-unknown-3rdparty', "Accept cookies from " - "the same origin only, unless a cookie is " - "already set for the domain."), - ('never', "Don't accept cookies at all.")) + def __init__(self, none_ok=False): + super().__init__( + none_ok, + valid_values=ValidValues( + ('left', "Select the tab on the left."), + ('right', "Select the tab on the right."), + ('previous', "Select the previously selected tab."))) class ConfirmQuit(FlagList): @@ -1432,15 +1424,20 @@ class ConfirmQuit(FlagList): """Whether to display a confirmation when the window is closed.""" special = True - valid_values = ValidValues(('always', "Always show a confirmation."), - ('multiple-tabs', "Show a confirmation if " - "multiple tabs are opened."), - ('downloads', "Show a confirmation if " - "downloads are running"), - ('never', "Never show a confirmation.")) + # Values that can be combined with commas combinable_values = ('multiple-tabs', 'downloads') + def __init__(self, none_ok=False): + super().__init__(none_ok) + self.valid_values = ValidValues( + ('always', "Always show a confirmation."), + ('multiple-tabs', "Show a confirmation if " + "multiple tabs are opened."), + ('downloads', "Show a confirmation if " + "downloads are running"), + ('never', "Never show a confirmation.")) + def validate(self, value): super().validate(value) if not value: @@ -1457,36 +1454,19 @@ class ConfirmQuit(FlagList): value, "List cannot contain always!") -class ForwardUnboundKeys(BaseType): - - """Whether to forward unbound keys.""" - - special = True - valid_values = ValidValues(('all', "Forward all unbound keys."), - ('auto', "Forward unbound non-alphanumeric " - "keys."), - ('none', "Don't forward any keys.")) - - -class CloseButton(BaseType): - - """Mouse button used to close tabs.""" - - special = True - valid_values = ValidValues(('right', "Close tabs on right-click."), - ('middle', "Close tabs on middle-click."), - ('none', "Don't close tabs using the mouse.")) - - class NewTabPosition(BaseType): """How new tabs are positioned.""" special = True - valid_values = ValidValues(('left', "On the left of the current tab."), - ('right', "On the right of the current tab."), - ('first', "At the left end."), - ('last', "At the right end.")) + + def __init__(self, none_ok=False): + super().__init__(none_ok) + self.valid_values = ValidValues( + ('left', "On the left of the current tab."), + ('right', "On the right of the current tab."), + ('first', "At the left end."), + ('last', "At the right end.")) class IgnoreCase(Bool): @@ -1494,10 +1474,14 @@ class IgnoreCase(Bool): """Whether to ignore case when searching.""" special = True - valid_values = ValidValues(('true', "Search case-insensitively"), - ('false', "Search case-sensitively"), - ('smart', "Search case-sensitively if there " - "are capital chars")) + + def __init__(self, none_ok=False): + super().__init__(none_ok) + self.valid_values = ValidValues( + ('true', "Search case-insensitively"), + ('false', "Search case-sensitively"), + ('smart', "Search case-sensitively if there " + "are capital chars")) def transform(self, value): if value.lower() == 'smart': @@ -1515,57 +1499,12 @@ class IgnoreCase(Bool): super().validate(value) -class NewInstanceOpenTarget(BaseType): - - """How to open links in an existing instance if a new one is launched.""" - - special = True - valid_values = ValidValues(('tab', "Open a new tab in the existing " - "window and activate the window."), - ('tab-bg', "Open a new background tab in the " - "existing window and activate the " - "window."), - ('tab-silent', "Open a new tab in the existing " - "window without activating " - "the window."), - ('tab-bg-silent', "Open a new background tab " - "in the existing window " - "without activating the " - "window."), - ('window', "Open in a new window.")) - - -class DownloadPathSuggestion(BaseType): - - """How to format the question when downloading.""" - - special = True - valid_values = ValidValues(('path', "Show only the download path."), - ('filename', "Show only download filename."), - ('both', "Show download path and filename.")) - - -class Referer(BaseType): - - """Send the Referer header.""" - - valid_values = ValidValues(('always', "Always send."), - ('never', "Never send; this is not recommended," - " as some sites may break."), - ('same-domain', "Only send for the same domain." - " This will still protect your privacy, but" - " shouldn't break any sites.")) - - class UserAgent(BaseType): """The user agent to use.""" special = True - def __init__(self, none_ok=False): - super().__init__(none_ok) - def validate(self, value): self._basic_validation(value) @@ -1620,25 +1559,6 @@ class UserAgent(BaseType): return out -class TabBarShow(BaseType): - - """When to show the tab bar.""" - - valid_values = ValidValues(('always', "Always show the tab bar."), - ('never', "Always hide the tab bar."), - ('multiple', "Hide the tab bar if only one tab " - "is open."), - ('switching', "Show the tab bar when switching " - "tabs.")) - - -class URLSegmentList(FlagList): - - """A list of URL segments.""" - - valid_values = ValidValues('host', 'path', 'query', 'anchor') - - class TimestampTemplate(BaseType): """A strftime-like template for timestamps. diff --git a/tests/unit/config/test_configtypes.py b/tests/unit/config/test_configtypes.py index 7c8bf8d56..43190c3cf 100644 --- a/tests/unit/config/test_configtypes.py +++ b/tests/unit/config/test_configtypes.py @@ -225,27 +225,18 @@ class TestBaseType: assert basetype.complete() == completions -class GoodMappingSubclass(configtypes.MappingType): +class MappingSubclass(configtypes.MappingType): """A MappingType we use in TestMappingType which is valid/good.""" - valid_values = configtypes.ValidValues('one', 'two') - MAPPING = { 'one': 1, 'two': 2, } - -class BadMappingSubclass(configtypes.MappingType): - - """A MappingType which is missing a value in MAPPING.""" - - valid_values = configtypes.ValidValues('one', 'two') - - MAPPING = { - 'one': 1, - } + def __init__(self, none_ok=False): + super().__init__(none_ok) + self.valid_values = configtypes.ValidValues('one', 'two') class TestMappingType: @@ -261,7 +252,7 @@ class TestMappingType: @pytest.fixture def klass(self): - return GoodMappingSubclass + return MappingSubclass @pytest.mark.parametrize('val', TESTS.keys()) def test_validate_valid(self, klass, val): @@ -276,9 +267,11 @@ class TestMappingType: def test_transform(self, klass, val, expected): assert klass().transform(val) == expected - def test_bad_subclass_init(self): - with pytest.raises(ValueError): - BadMappingSubclass() + @pytest.mark.parametrize('typ', [configtypes.ColorSystem(), + configtypes.Position(), + configtypes.SelectOnRemove()]) + def test_mapping_type_matches_valid_values(self, typ): + assert list(sorted(typ.MAPPING)) == list(sorted(typ.valid_values)) class TestString: @@ -383,9 +376,12 @@ class FlagListSubclass(configtypes.FlagList): Valid values are 'foo', 'bar' and 'baz'. """ - valid_values = configtypes.ValidValues('foo', 'bar', 'baz') combinable_values = ['foo', 'bar'] + def __init__(self, none_ok=False): + super().__init__(none_ok) + self.valid_values = configtypes.ValidValues('foo', 'bar', 'baz') + class TestFlagList: