diff --git a/qutebrowser/config/config.py b/qutebrowser/config/config.py index fe3beb545..a723d2f10 100644 --- a/qutebrowser/config/config.py +++ b/qutebrowser/config/config.py @@ -73,10 +73,11 @@ class change_filter: # pylint: disable=invalid-name optname: The option to be filtered. function: Whether a function rather than a method is decorated. """ - if sectname not in configdata.DATA: - raise configexc.NoSectionError(sectname) - if optname is not None and optname not in configdata.DATA[sectname]: - raise configexc.NoOptionError(optname, sectname) + # FIXME:conf + # if sectname not in configdata.DATA: + # raise configexc.NoSectionError(sectname) + # if optname is not None and optname not in configdata.DATA[sectname]: + # raise configexc.NoOptionError(optname, sectname) self._sectname = sectname self._optname = optname self._function = function @@ -256,6 +257,7 @@ def init(parent=None): parent: The parent to pass to QObjects which get initialized. """ # _init_main_config(parent) + configdata.init() _init_new_config(parent) _init_key_config(parent) _init_misc() diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index 711fca495..db1a9ce34 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -33,11 +33,12 @@ import sys import re import collections -from qutebrowser.config import configtypes as typ -from qutebrowser.config import sections as sect -from qutebrowser.config.value import SettingValue -from qutebrowser.utils.qtutils import MAXVALS -from qutebrowser.utils import usertypes, qtutils +import yaml + +from qutebrowser.config import configtypes, sections +from qutebrowser.utils import usertypes, qtutils, utils + +DATA = None FIRST_COMMENT = r""" @@ -131,1343 +132,6 @@ MONOSPACE = (' xos4 Terminus, Terminus, Monospace, ' 'monospace, Fixed, Consolas, Terminal') - -def data(readonly=False): - """Get the default config data. - - Return: - A {name: section} OrderedDict. - """ - return collections.OrderedDict([ - ('general', sect.KeyValue( - ('ignore-case', - SettingValue(typ.IgnoreCase(), 'smart'), - "Whether to find text on a page case-insensitively."), - - ('startpage', - SettingValue(typ.List(typ.String()), - 'https://start.duckduckgo.com'), - "The default page(s) to open at the start, separated by commas."), - - ('yank-ignored-url-parameters', - SettingValue(typ.List(typ.String()), - 'ref,utm_source,utm_medium,utm_campaign,utm_term,' - 'utm_content'), - "The URL parameters to strip with :yank url, separated by " - "commas."), - - ('default-open-dispatcher', - SettingValue(typ.String(none_ok=True), ''), - "The default program used to open downloads. Set to an empty " - "string to use the default internal handler.\n\n" - "Any {} in the string will be expanded to the filename, else " - "the filename will be appended."), - - ('default-page', - SettingValue(typ.FuzzyUrl(), 'https://start.duckduckgo.com/'), - "The page to open if :open -t/-b/-w is used without URL. Use " - "`about:blank` for a blank page."), - - ('auto-search', - SettingValue(typ.AutoSearch(), 'naive'), - "Whether to start a search when something else than a URL is " - "entered."), - - ('auto-save-config', - SettingValue(typ.Bool(), 'true'), - "Whether to save the config automatically on quit."), - - ('auto-save-interval', - SettingValue(typ.Int(minval=0, maxval=MAXVALS['int']), '15000'), - "How often (in milliseconds) to auto-save config/cookies/etc."), - - ('editor', - SettingValue(typ.ShellCommand(placeholder=True), 'gvim -f "{}"'), - "The editor (and arguments) to use for the `open-editor` " - "command.\n\n" - "The arguments get split like in a shell, so you can use `\"` or " - "`'` to quote them.\n" - "`{}` gets replaced by the filename of the file to be edited."), - - ('editor-encoding', - SettingValue(typ.Encoding(), 'utf-8'), - "Encoding to use for editor."), - - ('private-browsing', - SettingValue(typ.Bool(), 'false'), - "Open new windows in private browsing mode which does not record " - "visited pages."), - - ('developer-extras', - SettingValue(typ.Bool(), 'false', - backends=[usertypes.Backend.QtWebKit]), - "Enable extra tools for Web developers.\n\n" - "This needs to be enabled for `:inspector` to work and also adds " - "an _Inspect_ entry to the context menu. For QtWebEngine, see " - "'qutebrowser --help' instead."), - - ('print-element-backgrounds', - SettingValue(typ.Bool(), 'true', - backends=( - None if qtutils.version_check('5.8', strict=True) - else [usertypes.Backend.QtWebKit])), - "Whether the background color and images are also drawn when the " - "page is printed.\n" - "This setting only works with Qt 5.8 or newer when using the " - "QtWebEngine backend."), - - ('xss-auditing', - SettingValue(typ.Bool(), 'false'), - "Whether load requests should be monitored for cross-site " - "scripting attempts.\n\n" - "Suspicious scripts will be blocked and reported in the " - "inspector's JavaScript console. Enabling this feature might " - "have an impact on performance."), - - ('default-encoding', - SettingValue(typ.String(), 'iso-8859-1'), - "Default encoding to use for websites.\n\n" - "The encoding must be a string describing an encoding such as " - "_utf-8_, _iso-8859-1_, etc."), - - ('new-instance-open-target', - 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."), - - ('new-instance-open-target.window', - SettingValue(typ.String( - valid_values=typ.ValidValues( - ('first-opened', "Open new tabs in the first (oldest) " - "opened window."), - ('last-opened', "Open new tabs in the last (newest) " - "opened window."), - ('last-focused', "Open new tabs in the most recently " - "focused window."), - ('last-visible', "Open new tabs in the most recently " - "visible window.") - )), 'last-focused'), - "Which window to choose when opening links as new tabs."), - - ('log-javascript-console', - SettingValue(typ.String( - valid_values=typ.ValidValues( - ('none', "Don't log messages."), - ('debug', "Log messages with debug level."), - ('info', "Log messages with info level.") - )), 'debug'), - "How to log javascript console messages."), - - ('save-session', - SettingValue(typ.Bool(), 'false'), - "Whether to always save the open pages."), - - ('session-default-name', - SettingValue(typ.SessionName(none_ok=True), ''), - "The name of the session to save by default, or empty for the " - "last loaded session."), - - ('url-incdec-segments', - 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."), - - readonly=readonly - )), - - ('ui', sect.KeyValue( - ('history-session-interval', - SettingValue(typ.Int(), '30'), - "The maximum time in minutes between two history items for them " - "to be considered being from the same session. Use -1 to " - "disable separation."), - - ('zoom-levels', - SettingValue(typ.List(typ.Perc(minval=0)), - '25%,33%,50%,67%,75%,90%,100%,110%,125%,150%,175%,' - '200%,250%,300%,400%,500%'), - "The available zoom levels, separated by commas."), - - ('default-zoom', - SettingValue(typ.Perc(), '100%'), - "The default zoom level."), - - ('downloads-position', - SettingValue(typ.VerticalPosition(), 'top'), - "Where to show the downloaded files."), - - ('status-position', - SettingValue(typ.VerticalPosition(), 'bottom'), - "The position of the status bar."), - - ('message-timeout', - SettingValue(typ.Int(minval=0), '2000'), - "Time (in ms) to show messages in the statusbar for.\n" - "Set to 0 to never clear messages."), - - ('message-unfocused', - SettingValue(typ.Bool(), 'false'), - "Whether to show messages in unfocused windows."), - - ('confirm-quit', - SettingValue(typ.ConfirmQuit(), 'never'), - "Whether to confirm quitting the application."), - - ('zoom-text-only', - SettingValue(typ.Bool(), 'false', - backends=[usertypes.Backend.QtWebKit]), - "Whether the zoom factor on a frame applies only to the text or " - "to all content."), - - ('frame-flattening', - SettingValue(typ.Bool(), 'false', - backends=[usertypes.Backend.QtWebKit]), - "Whether to expand each subframe to its contents.\n\n" - "This will flatten all the frames to become one scrollable " - "page."), - - ('user-stylesheet', - SettingValue(typ.File(none_ok=True), ''), - "User stylesheet to use (absolute filename or filename relative " - "to the config directory). Will expand environment variables."), - - ('hide-scrollbar', - SettingValue(typ.Bool(), 'true'), - "Hide the main scrollbar."), - - ('smooth-scrolling', - SettingValue(typ.Bool(), 'false'), - "Whether to enable smooth scrolling for web pages. Note smooth " - "scrolling does not work with the :scroll-px command."), - - ('remove-finished-downloads', - SettingValue(typ.Int(minval=-1), '-1'), - "Number of milliseconds to wait before removing finished " - "downloads. Will not be removed if value is -1."), - - ('hide-statusbar', - SettingValue(typ.Bool(), 'false'), - "Whether to hide the statusbar unless a message is shown."), - - ('statusbar-padding', - SettingValue(typ.Padding(), '1,1,0,0'), - "Padding for statusbar (top, bottom, left, right)."), - - ('window-title-format', - SettingValue(typ.FormatString(fields=['perc', 'perc_raw', 'title', - 'title_sep', 'id', - 'scroll_pos', 'host', - 'backend', 'private']), - '{perc}{title}{title_sep}qutebrowser'), - "The format to use for the window title. The following " - "placeholders are defined:\n\n" - "* `{perc}`: The percentage as a string like `[10%]`.\n" - "* `{perc_raw}`: The raw percentage, e.g. `10`\n" - "* `{title}`: The title of the current web page\n" - "* `{title_sep}`: The string ` - ` if a title is set, empty " - "otherwise.\n" - "* `{id}`: The internal window ID of this window.\n" - "* `{scroll_pos}`: The page scroll position.\n" - "* `{host}`: The host of the current web page.\n" - "* `{backend}`: Either 'webkit' or 'webengine'\n" - "* `{private}` : Indicates when private mode is enabled.\n"), - - ('modal-js-dialog', - SettingValue(typ.Bool(), 'false'), - "Use standard JavaScript modal dialog for alert() and confirm()"), - - ('hide-wayland-decoration', - SettingValue(typ.Bool(), 'false'), - "Hide the window decoration when using wayland " - "(requires restart)"), - - ('keyhint-blacklist', - SettingValue(typ.List(typ.String(), none_ok=True), ''), - "Keychains that shouldn't be shown in the keyhint dialog\n\n" - "Globs are supported, so ';*' will blacklist all keychains" - "starting with ';'. Use '*' to disable keyhints"), - - ('keyhint-delay', - SettingValue(typ.Int(minval=0), '500'), - "Time from pressing a key to seeing the keyhint dialog (ms)"), - - ('prompt-radius', - SettingValue(typ.Int(minval=0), '8'), - "The rounding radius for the edges of prompts."), - - ('prompt-filebrowser', - SettingValue(typ.Bool(), 'true'), - "Show a filebrowser in upload/download prompts."), - - readonly=readonly - )), - - ('network', sect.KeyValue( - ('do-not-track', - SettingValue(typ.Bool(), 'true'), - "Value to send in the `DNT` header."), - - ('accept-language', - SettingValue(typ.String(none_ok=True), 'en-US,en'), - "Value to send in the `accept-language` header."), - - ('referer-header', - 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', backends=[usertypes.Backend.QtWebKit]), - "Send the Referer header"), - - ('user-agent', - SettingValue(typ.UserAgent(none_ok=True), ''), - "User agent to send. Empty to send the default."), - - ('proxy', - SettingValue(typ.Proxy(), 'system', - backends=(None if qtutils.version_check('5.8') - else [usertypes.Backend.QtWebKit])), - "The proxy to use.\n\n" - "In addition to the listed values, you can use a `socks://...` " - "or `http://...` URL.\n\n" - "This setting only works with Qt 5.8 or newer when using the " - "QtWebEngine backend."), - - ('proxy-dns-requests', - SettingValue(typ.Bool(), 'true', - backends=[usertypes.Backend.QtWebKit]), - "Whether to send DNS requests over the configured proxy."), - - ('ssl-strict', - SettingValue(typ.BoolAsk(), 'ask'), - "Whether to validate SSL handshakes."), - - ('dns-prefetch', - SettingValue(typ.Bool(), 'true', - backends=[usertypes.Backend.QtWebKit]), - "Whether to try to pre-fetch DNS entries to speed up browsing."), - - ('custom-headers', - SettingValue(typ.HeaderDict(none_ok=True), ''), - "Set custom headers for qutebrowser HTTP requests."), - - ('netrc-file', - SettingValue(typ.File(none_ok=True), ''), - "Set location of a netrc-file for HTTP authentication. If empty, " - "~/.netrc is used."), - - readonly=readonly - )), - - ('completion', sect.KeyValue( - ('show', - SettingValue(typ.String( - valid_values=typ.ValidValues( - ('always', "Whenever a completion is available."), - ('auto', "Whenever a completion is requested."), - ('never', "Never.") - )), 'always'), - "When to show the autocompletion window."), - - ('download-path-suggestion', - 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', - SettingValue(typ.TimestampTemplate(none_ok=True), '%Y-%m-%d'), - "How to format timestamps (e.g. for history)"), - - ('height', - SettingValue(typ.PercOrInt(minperc=0, maxperc=100, minint=1), - '50%'), - "The height of the completion, in px or as percentage of the " - "window."), - - ('cmd-history-max-items', - SettingValue(typ.Int(minval=-1), '100'), - "How many commands to save in the command history.\n\n" - "0: no history / -1: unlimited"), - - ('web-history-max-items', - SettingValue(typ.Int(minval=-1), '1000'), - "How many URLs to show in the web history.\n\n" - "0: no history / -1: unlimited"), - - ('quick-complete', - SettingValue(typ.Bool(), 'true'), - "Whether to move on to the next part when there's only one " - "possible completion left."), - - ('shrink', - SettingValue(typ.Bool(), 'false'), - "Whether to shrink the completion to be smaller than the " - "configured size if there are no scrollbars."), - - ('scrollbar-width', - SettingValue(typ.Int(minval=0), '12'), - "Width of the scrollbar in the completion window (in px)."), - - ('scrollbar-padding', - SettingValue(typ.Int(minval=0), '2'), - "Padding of scrollbar handle in completion window (in px)."), - - readonly=readonly - )), - - ('input', sect.KeyValue( - ('timeout', - SettingValue(typ.Int(minval=0, maxval=MAXVALS['int']), '500'), - "Timeout (in milliseconds) for ambiguous key bindings.\n\n" - "If the current input forms both a complete match and a partial " - "match, the complete match will be executed after this time."), - - ('partial-timeout', - SettingValue(typ.Int(minval=0, maxval=MAXVALS['int']), '5000'), - "Timeout (in milliseconds) for partially typed key bindings.\n\n" - "If the current input forms only partial matches, the keystring " - "will be cleared after this time."), - - ('insert-mode-on-plugins', - SettingValue(typ.Bool(), 'false'), - "Whether to switch to insert mode when clicking flash and other " - "plugins."), - - ('auto-leave-insert-mode', - SettingValue(typ.Bool(), 'true'), - "Whether to leave insert mode if a non-editable element is " - "clicked."), - - ('auto-insert-mode', - SettingValue(typ.Bool(), 'false'), - "Whether to automatically enter insert mode if an editable " - "element is focused after page load."), - - ('forward-unbound-keys', - 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', - SettingValue(typ.Bool(), 'false'), - "Enables or disables the Spatial Navigation feature.\n\n" - "Spatial navigation consists in the ability to navigate between " - "focusable elements in a Web page, such as hyperlinks and form " - "controls, by using Left, Right, Up and Down arrow keys. For " - "example, if a user presses the Right key, heuristics determine " - "whether there is an element he might be trying to reach towards " - "the right and which element he probably wants."), - - ('links-included-in-focus-chain', - SettingValue(typ.Bool(), 'true'), - "Whether hyperlinks should be included in the keyboard focus " - "chain."), - - ('rocker-gestures', - SettingValue(typ.Bool(), 'false'), - "Whether to enable Opera-like mouse rocker gestures. This " - "disables the context menu."), - - ('mouse-zoom-divider', - SettingValue(typ.Int(minval=0), '512'), - "How much to divide the mouse wheel movements to translate them " - "into zoom increments."), - - readonly=readonly - )), - - ('tabs', sect.KeyValue( - ('background-tabs', - SettingValue(typ.Bool(), 'false'), - "Whether to open new tabs (middleclick/ctrl+click) in " - "background."), - - ('select-on-remove', - SettingValue(typ.SelectOnRemove(), 'next'), - "Which tab to select when the focused tab is removed."), - - ('new-tab-position', - SettingValue(typ.NewTabPosition(), 'next'), - "How new tabs are positioned."), - - ('new-tab-position-explicit', - SettingValue(typ.NewTabPosition(), 'last'), - "How new tabs opened explicitly are positioned."), - - ('last-close', - 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.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', - SettingValue(typ.Int(), '800'), - "Time to show the tab bar before hiding it when tabs->show is " - "set to 'switching'."), - - ('wrap', - SettingValue(typ.Bool(), 'true'), - "Whether to wrap when changing tabs."), - - ('movable', - SettingValue(typ.Bool(), 'true'), - "Whether tabs should be movable."), - - ('close-mouse-button', - 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', - SettingValue(typ.Position(), 'top'), - "The position of the tab bar."), - - ('show-favicons', - SettingValue(typ.Bool(), 'true'), - "Whether to show favicons in the tab bar."), - - ('favicon-scale', - SettingValue(typ.Float(minval=0.0), '1.0'), - "Scale for favicons in the tab bar. The tab size is unchanged, " - "so big favicons also require extra `tabs->padding`."), - - ('width', - SettingValue(typ.PercOrInt(minperc=0, maxperc=100, minint=1), - '20%'), - "The width of the tab bar if it's vertical, in px or as " - "percentage of the window."), - - ('pinned-width', - SettingValue(typ.Int(minval=10), - '43'), - "The width for pinned tabs with a horizontal tabbar, in px."), - - ('indicator-width', - SettingValue(typ.Int(minval=0), '3'), - "Width of the progress indicator (0 to disable)."), - - ('tabs-are-windows', - SettingValue(typ.Bool(), 'false'), - "Whether to open windows instead of tabs."), - - ('title-format', - SettingValue(typ.FormatString( - fields=['perc', 'perc_raw', 'title', 'title_sep', 'index', - 'id', 'scroll_pos', 'host', 'private'], none_ok=True), - '{index}: {title}'), - "The format to use for the tab title. The following placeholders " - "are defined:\n\n" - "* `{perc}`: The percentage as a string like `[10%]`.\n" - "* `{perc_raw}`: The raw percentage, e.g. `10`\n" - "* `{title}`: The title of the current web page\n" - "* `{title_sep}`: The string ` - ` if a title is set, empty " - "otherwise.\n" - "* `{index}`: The index of this tab.\n" - "* `{id}`: The internal tab ID of this tab.\n" - "* `{scroll_pos}`: The page scroll position.\n" - "* `{host}`: The host of the current web page.\n" - "* `{backend}`: Either 'webkit' or 'webengine'\n" - "* `{private}` : Indicates when private mode is enabled.\n"), - - ('title-format-pinned', - SettingValue(typ.FormatString( - fields=['perc', 'perc_raw', 'title', 'title_sep', 'index', - 'id', 'scroll_pos', 'host', 'private'], none_ok=True), - '{index}'), - "The format to use for the tab title for pinned tabs. " - "The same placeholders like for title-format are defined."), - - ('title-alignment', - SettingValue(typ.TextAlignment(), 'left'), - "Alignment of the text inside of tabs"), - - ('mousewheel-tab-switching', - SettingValue(typ.Bool(), 'true'), - "Switch between tabs using the mouse wheel."), - - ('padding', - SettingValue(typ.Padding(), '0,0,5,5'), - "Padding for tabs (top, bottom, left, right)."), - - ('indicator-padding', - SettingValue(typ.Padding(), '2,2,0,4'), - "Padding for indicators (top, bottom, left, right)."), - - readonly=readonly - )), - - ('storage', sect.KeyValue( - ('download-directory', - SettingValue(typ.Directory(none_ok=True), ''), - "The directory to save downloads to. An empty value selects a " - "sensible os-specific default. Will expand environment " - "variables."), - - ('prompt-download-directory', - SettingValue(typ.Bool(), 'true'), - "Whether to prompt the user for the download location.\n" - "If set to false, 'download-directory' will be used."), - - ('remember-download-directory', - SettingValue(typ.Bool(), 'true'), - "Whether to remember the last used download directory."), - - # Defaults from QWebSettings::QWebSettings() in - # qtwebkit/Source/WebKit/qt/Api/qwebsettings.cpp - - ('maximum-pages-in-cache', - SettingValue(typ.Int(minval=0, maxval=MAXVALS['int']), '0', - backends=[usertypes.Backend.QtWebKit]), - "The maximum number of pages to hold in the global memory page " - "cache.\n\n" - "The Page Cache allows for a nicer user experience when " - "navigating forth or back to pages in the forward/back history, " - "by pausing and resuming up to _n_ pages.\n\n" - "For more information about the feature, please refer to: " - "http://webkit.org/blog/427/webkit-page-cache-i-the-basics/"), - - ('offline-web-application-cache', - SettingValue(typ.Bool(), 'true', - backends=[usertypes.Backend.QtWebKit]), - "Whether support for the HTML 5 web application cache feature is " - "enabled.\n\n" - "An application cache acts like an HTTP cache in some sense. For " - "documents that use the application cache via JavaScript, the " - "loader engine will first ask the application cache for the " - "contents, before hitting the network.\n\n" - "The feature is described in details at: " - "http://dev.w3.org/html5/spec/Overview.html#appcache"), - - ('local-storage', - SettingValue(typ.Bool(), 'true'), - "Whether support for HTML 5 local storage and Web SQL is " - "enabled."), - - ('cache-size', - SettingValue(typ.Int(none_ok=True, minval=0, - maxval=MAXVALS['int64']), ''), - "Size of the HTTP network cache. Empty to use the default " - "value."), - - readonly=readonly - )), - - ('content', sect.KeyValue( - ('allow-images', - SettingValue(typ.Bool(), 'true'), - "Whether images are automatically loaded in web pages."), - - ('allow-javascript', - SettingValue(typ.Bool(), 'true'), - "Enables or disables the running of JavaScript programs."), - - ('allow-plugins', - SettingValue(typ.Bool(), 'false'), - "Enables or disables plugins in Web pages.\n\n" - 'Qt plugins with a mimetype such as "application/x-qt-plugin" ' - "are not affected by this setting."), - - ('webgl', - SettingValue(typ.Bool(), 'true'), - "Enables or disables WebGL."), - - ('hyperlink-auditing', - SettingValue(typ.Bool(), 'false'), - "Enable or disable hyperlink auditing ()."), - - ('geolocation', - SettingValue(typ.BoolAsk(), 'ask'), - "Allow websites to request geolocations."), - - ('notifications', - SettingValue(typ.BoolAsk(), 'ask'), - "Allow websites to show notifications."), - - ('media-capture', - SettingValue(typ.BoolAsk(), 'ask', - backends=[usertypes.Backend.QtWebEngine]), - "Allow websites to record audio/video."), - - ('javascript-can-open-windows-automatically', - SettingValue(typ.Bool(), 'false'), - "Whether JavaScript programs can open new windows without user " - "interaction."), - - ('javascript-can-close-windows', - SettingValue(typ.Bool(), 'false', - backends=[usertypes.Backend.QtWebKit]), - "Whether JavaScript programs can close windows."), - - ('javascript-can-access-clipboard', - SettingValue(typ.Bool(), 'false'), - "Whether JavaScript programs can read or write to the " - "clipboard.\nWith QtWebEngine, writing the clipboard as response " - "to a user interaction is always allowed."), - - ('ignore-javascript-prompt', - SettingValue(typ.Bool(), 'false'), - "Whether all javascript prompts should be ignored."), - - ('ignore-javascript-alert', - SettingValue(typ.Bool(), 'false'), - "Whether all javascript alerts should be ignored."), - - ('local-content-can-access-remote-urls', - SettingValue(typ.Bool(), 'false'), - "Whether locally loaded documents are allowed to access remote " - "urls."), - - ('local-content-can-access-file-urls', - SettingValue(typ.Bool(), 'true'), - "Whether locally loaded documents are allowed to access other " - "local urls."), - - ('cookies-accept', - 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', backends=[usertypes.Backend.QtWebKit]), - "Control which cookies to accept."), - - ('cookies-store', - SettingValue(typ.Bool(), 'true'), - "Whether to store cookies. Note this option needs a restart with " - "QtWebEngine on Qt < 5.9."), - - ('host-block-lists', - SettingValue( - typ.List(typ.Url(), none_ok=True), - 'https://www.malwaredomainlist.com/hostslist/hosts.txt,' - 'http://someonewhocares.org/hosts/hosts,' - 'http://winhelp2002.mvps.org/hosts.zip,' - 'http://malwaredomains.lehigh.edu/files/justdomains.zip,' - 'https://pgl.yoyo.org/adservers/serverlist.php?' - 'hostformat=hosts&mimetype=plaintext'), - "List of URLs of lists which contain hosts to block.\n\n" - "The file can be in one of the following formats:\n\n" - "- An '/etc/hosts'-like file\n" - "- One host per line\n" - "- A zip-file of any of the above, with either only one file, or " - "a file named 'hosts' (with any extension)."), - - ('host-blocking-enabled', - SettingValue(typ.Bool(), 'true'), - "Whether host blocking is enabled."), - - ('host-blocking-whitelist', - SettingValue(typ.List(typ.String(), none_ok=True), 'piwik.org'), - "List of domains that should always be loaded, despite being " - "ad-blocked.\n\n" - "Domains may contain * and ? wildcards and are otherwise " - "required to exactly match the requested domain.\n\n" - "Local domains are always exempt from hostblocking."), - - ('enable-pdfjs', SettingValue(typ.Bool(), 'false'), - "Enable pdf.js to view PDF files in the browser.\n\n" - "Note that the files can still be downloaded by clicking" - " the download button in the pdf.js viewer."), - - readonly=readonly - )), - - ('hints', sect.KeyValue( - ('border', - SettingValue(typ.String(), '1px solid #E3BE23'), - "CSS border value for hints."), - - ('mode', - SettingValue(typ.String( - valid_values=typ.ValidValues( - ('number', "Use numeric hints. (In this mode you can " - "also type letters form the hinted element to filter " - "and reduce the number of elements that are hinted.)"), - ('letter', "Use the chars in the hints -> " - "chars setting."), - ('word', "Use hints words based on the html " - "elements and the extra words."), - )), 'letter'), - "Mode to use for hints."), - - ('chars', - SettingValue(typ.UniqueCharString(minlen=2, completions=[ - ('asdfghjkl', "Home row"), - ('aoeuidnths', "Home row (Dvorak)"), - ('abcdefghijklmnopqrstuvwxyz', "All letters"), - ]), 'asdfghjkl'), - "Chars used for hint strings."), - - ('min-chars', - SettingValue(typ.Int(minval=1), '1'), - "Minimum number of chars used for hint strings."), - - ('scatter', - SettingValue(typ.Bool(), 'true'), - "Whether to scatter hint key chains (like Vimium) or not (like " - "dwb). Ignored for number hints."), - - ('uppercase', - SettingValue(typ.Bool(), 'false'), - "Make chars in hint strings uppercase."), - - ('dictionary', - SettingValue(typ.File(required=False), '/usr/share/dict/words'), - "The dictionary file to be used by the word hints."), - - ('auto-follow', - SettingValue(typ.String( - valid_values=typ.ValidValues( - ('always', "Auto-follow whenever there is only a single " - "hint on a page."), - ('unique-match', "Auto-follow whenever there is a unique " - "non-empty match in either the hint string (word mode) " - "or filter (number mode)."), - ('full-match', "Follow the hint when the user typed the " - "whole hint (letter, word or number mode) or the " - "element's text (only in number mode)."), - ('never', "The user will always need to press Enter to " - "follow a hint."), - )), 'unique-match'), - "Controls when a hint can be automatically followed without the " - "user pressing Enter."), - - ('auto-follow-timeout', - SettingValue(typ.Int(), '0'), - "A timeout (in milliseconds) to inhibit normal-mode key bindings " - "after a successful auto-follow."), - - ('next-regexes', - SettingValue(typ.List(typ.Regex(flags=re.IGNORECASE)), - r'\bnext\b,\bmore\b,\bnewer\b,\b[>→≫]\b,\b(>>|»)\b,' - r'\bcontinue\b'), - "A comma-separated list of regexes to use for 'next' links."), - - ('prev-regexes', - SettingValue(typ.List(typ.Regex(flags=re.IGNORECASE)), - r'\bprev(ious)?\b,\bback\b,\bolder\b,\b[<←≪]\b,' - r'\b(<<|«)\b'), - "A comma-separated list of regexes to use for 'prev' links."), - - ('find-implementation', - SettingValue(typ.String( - valid_values=typ.ValidValues( - ('javascript', "Better but slower"), - ('python', "Slightly worse but faster"), - )), 'python'), - "Which implementation to use to find elements to hint."), - - ('hide-unmatched-rapid-hints', - SettingValue(typ.Bool(), 'true'), - "Controls hiding unmatched hints in rapid mode."), - - readonly=readonly - )), - - ('searchengines', sect.ValueList( - typ.SearchEngineName(), typ.SearchEngineUrl(), - ('DEFAULT', 'https://duckduckgo.com/?q={}'), - - readonly=readonly - )), - - ('aliases', sect.ValueList( - typ.String(forbidden=' '), typ.Command(), - - readonly=readonly - )), - - ('colors', sect.KeyValue( - ('completion.fg', - SettingValue(typ.QtColor(), 'white'), - "Text color of the completion widget."), - - ('completion.bg', - SettingValue(typ.QssColor(), '#333333'), - "Background color of the completion widget."), - - ('completion.alternate-bg', - SettingValue(typ.QssColor(), '#444444'), - "Alternating background color of the completion widget."), - - ('completion.category.fg', - SettingValue(typ.QtColor(), 'white'), - "Foreground color of completion widget category headers."), - - ('completion.category.bg', - SettingValue(typ.QssColor(), 'qlineargradient(x1:0, y1:0, x2:0, ' - 'y2:1, stop:0 #888888, stop:1 #505050)'), - "Background color of the completion widget category headers."), - - ('completion.category.border.top', - SettingValue(typ.QssColor(), 'black'), - "Top border color of the completion widget category headers."), - - ('completion.category.border.bottom', - SettingValue(typ.QssColor(), 'black'), - "Bottom border color of the completion widget category headers."), - - ('completion.item.selected.fg', - SettingValue(typ.QtColor(), 'black'), - "Foreground color of the selected completion item."), - - ('completion.item.selected.bg', - SettingValue(typ.QssColor(), '#e8c000'), - "Background color of the selected completion item."), - - ('completion.item.selected.border.top', - SettingValue(typ.QssColor(), '#bbbb00'), - "Top border color of the completion widget category headers."), - - ('completion.item.selected.border.bottom', - SettingValue( - typ.QssColor(), '#bbbb00'), - "Bottom border color of the selected completion item."), - - ('completion.match.fg', - SettingValue(typ.QssColor(), '#ff4444'), - "Foreground color of the matched text in the completion."), - - ('completion.scrollbar.fg', - SettingValue(typ.QssColor(), 'white'), - "Color of the scrollbar handle in completion view."), - - ('completion.scrollbar.bg', - SettingValue(typ.QssColor(), '#333333'), - "Color of the scrollbar in completion view"), - - ('statusbar.fg', - SettingValue(typ.QssColor(), 'white'), - "Foreground color of the statusbar."), - - ('statusbar.bg', - SettingValue(typ.QssColor(), 'black'), - "Background color of the statusbar."), - - ('statusbar.fg.private', - SettingValue(typ.QssColor(), 'white'), - "Foreground color of the statusbar in private browsing mode."), - - ('statusbar.bg.private', - SettingValue(typ.QssColor(), '#666666'), - "Background color of the statusbar in private browsing mode."), - - ('statusbar.fg.insert', - SettingValue(typ.QssColor(), 'white'), - "Foreground color of the statusbar in insert mode."), - - ('statusbar.bg.insert', - SettingValue(typ.QssColor(), 'darkgreen'), - "Background color of the statusbar in insert mode."), - - ('statusbar.fg.command', - SettingValue(typ.QssColor(), 'white'), - "Foreground color of the statusbar in command mode."), - - ('statusbar.bg.command', - SettingValue(typ.QssColor(), 'black'), - "Background color of the statusbar in command mode."), - - ('statusbar.fg.command.private', - SettingValue(typ.QssColor(), 'white'), - "Foreground color of the statusbar in private browsing + command " - "mode."), - - ('statusbar.bg.command.private', - SettingValue(typ.QssColor(), 'grey'), - "Background color of the statusbar in private browsing + command " - "mode."), - - ('statusbar.fg.caret', - SettingValue(typ.QssColor(), 'white'), - "Foreground color of the statusbar in caret mode."), - - ('statusbar.bg.caret', - SettingValue(typ.QssColor(), 'purple'), - "Background color of the statusbar in caret mode."), - - ('statusbar.fg.caret-selection', - SettingValue(typ.QssColor(), 'white'), - "Foreground color of the statusbar in caret mode with a " - "selection"), - - ('statusbar.bg.caret-selection', - SettingValue(typ.QssColor(), '#a12dff'), - "Background color of the statusbar in caret mode with a " - "selection"), - - ('statusbar.progress.bg', - SettingValue(typ.QssColor(), 'white'), - "Background color of the progress bar."), - - ('statusbar.url.fg', - SettingValue(typ.QssColor(), 'white'), - "Default foreground color of the URL in the statusbar."), - - ('statusbar.url.fg.success', - SettingValue(typ.QssColor(), 'white'), - "Foreground color of the URL in the statusbar on successful " - "load (http)."), - - ('statusbar.url.fg.success.https', - SettingValue(typ.QssColor(), 'lime'), - "Foreground color of the URL in the statusbar on successful " - "load (https)."), - - ('statusbar.url.fg.error', - SettingValue(typ.QssColor(), 'orange'), - "Foreground color of the URL in the statusbar on error."), - - ('statusbar.url.fg.warn', - SettingValue(typ.QssColor(), 'yellow'), - "Foreground color of the URL in the statusbar when there's a " - "warning."), - - ('statusbar.url.fg.hover', - SettingValue(typ.QssColor(), 'aqua'), - "Foreground color of the URL in the statusbar for hovered " - "links."), - - ('tabs.fg.odd', - SettingValue(typ.QtColor(), 'white'), - "Foreground color of unselected odd tabs."), - - ('tabs.bg.odd', - SettingValue(typ.QtColor(), 'grey'), - "Background color of unselected odd tabs."), - - ('tabs.fg.even', - SettingValue(typ.QtColor(), 'white'), - "Foreground color of unselected even tabs."), - - ('tabs.bg.even', - SettingValue(typ.QtColor(), 'darkgrey'), - "Background color of unselected even tabs."), - - ('tabs.fg.selected.odd', - SettingValue(typ.QtColor(), 'white'), - "Foreground color of selected odd tabs."), - - ('tabs.bg.selected.odd', - SettingValue(typ.QtColor(), 'black'), - "Background color of selected odd tabs."), - - ('tabs.fg.selected.even', - SettingValue(typ.QtColor(), 'white'), - "Foreground color of selected even tabs."), - - ('tabs.bg.selected.even', - SettingValue(typ.QtColor(), 'black'), - "Background color of selected even tabs."), - - ('tabs.bg.bar', - SettingValue(typ.QtColor(), '#555555'), - "Background color of the tab bar."), - - ('tabs.indicator.start', - SettingValue(typ.QtColor(), '#0000aa'), - "Color gradient start for the tab indicator."), - - ('tabs.indicator.stop', - SettingValue(typ.QtColor(), '#00aa00'), - "Color gradient end for the tab indicator."), - - ('tabs.indicator.error', - SettingValue(typ.QtColor(), '#ff0000'), - "Color for the tab indicator on errors.."), - - ('tabs.indicator.system', - SettingValue(typ.ColorSystem(), 'rgb'), - "Color gradient interpolation system for the tab indicator."), - - ('hints.fg', - SettingValue(typ.QssColor(), 'black'), - "Font color for hints."), - - ('hints.bg', - SettingValue(typ.QssColor(), 'qlineargradient(x1:0, y1:0, x2:0, ' - 'y2:1, stop:0 rgba(255, 247, 133, 0.8), ' - 'stop:1 rgba(255, 197, 66, 0.8))'), - "Background color for hints. Note that you can use a `rgba(...)` " - "value for transparency."), - - ('hints.fg.match', - SettingValue(typ.QssColor(), 'green'), - "Font color for the matched part of hints."), - - ('downloads.bg.bar', - SettingValue(typ.QssColor(), 'black'), - "Background color for the download bar."), - - ('downloads.fg.start', - SettingValue(typ.QtColor(), 'white'), - "Color gradient start for download text."), - - ('downloads.bg.start', - SettingValue(typ.QtColor(), '#0000aa'), - "Color gradient start for download backgrounds."), - - ('downloads.fg.stop', - SettingValue(typ.QtColor(), '#0000aa'), - "Color gradient end for download text."), - - ('downloads.bg.stop', - SettingValue(typ.QtColor(), '#00aa00'), - "Color gradient stop for download backgrounds."), - - ('downloads.fg.system', - SettingValue(typ.ColorSystem(), 'rgb'), - "Color gradient interpolation system for download text."), - - ('downloads.bg.system', - SettingValue(typ.ColorSystem(), 'rgb'), - "Color gradient interpolation system for download backgrounds."), - - ('downloads.fg.error', - SettingValue(typ.QtColor(), 'white'), - "Foreground color for downloads with errors."), - - ('downloads.bg.error', - SettingValue(typ.QtColor(), 'red'), - "Background color for downloads with errors."), - - ('webpage.bg', - SettingValue(typ.QtColor(none_ok=True), 'white'), - "Background color for webpages if unset (or empty to use the " - "theme's color)"), - - ('keyhint.fg', - SettingValue(typ.QssColor(), '#FFFFFF'), - "Text color for the keyhint widget."), - - ('keyhint.fg.suffix', - SettingValue(typ.CssColor(), '#FFFF00'), - "Highlight color for keys to complete the current keychain"), - - ('keyhint.bg', - SettingValue(typ.QssColor(), 'rgba(0, 0, 0, 80%)'), - "Background color of the keyhint widget."), - - ('messages.fg.error', - SettingValue(typ.QssColor(), 'white'), - "Foreground color of an error message."), - - ('messages.bg.error', - SettingValue(typ.QssColor(), 'red'), - "Background color of an error message."), - - ('messages.border.error', - SettingValue(typ.QssColor(), '#bb0000'), - "Border color of an error message."), - - ('messages.fg.warning', - SettingValue(typ.QssColor(), 'white'), - "Foreground color a warning message."), - - ('messages.bg.warning', - SettingValue(typ.QssColor(), 'darkorange'), - "Background color of a warning message."), - - ('messages.border.warning', - SettingValue(typ.QssColor(), '#d47300'), - "Border color of an error message."), - - ('messages.fg.info', - SettingValue(typ.QssColor(), 'white'), - "Foreground color an info message."), - - ('messages.bg.info', - SettingValue(typ.QssColor(), 'black'), - "Background color of an info message."), - - ('messages.border.info', - SettingValue(typ.QssColor(), '#333333'), - "Border color of an info message."), - - ('prompts.fg', - SettingValue(typ.QssColor(), 'white'), - "Foreground color for prompts."), - - ('prompts.bg', - SettingValue(typ.QssColor(), 'darkblue'), - "Background color for prompts."), - - ('prompts.selected.bg', - SettingValue(typ.QssColor(), '#308cc6'), - "Background color for the selected item in filename prompts."), - - readonly=readonly - )), - - ('fonts', sect.KeyValue( - ('_monospace', - SettingValue(typ.Font(), 'xos4 Terminus, Terminus, Monospace, ' - '"DejaVu Sans Mono", Monaco, ' - '"Bitstream Vera Sans Mono", "Andale Mono", ' - '"Courier New", Courier, "Liberation Mono", ' - 'monospace, Fixed, Consolas, Terminal'), - "Default monospace fonts."), - - ('completion', - SettingValue(typ.Font(), DEFAULT_FONT_SIZE + MONOSPACE), - "Font used in the completion widget."), - - ('completion.category', - SettingValue(typ.Font(), 'bold ' + DEFAULT_FONT_SIZE + MONOSPACE), - "Font used in the completion categories."), - - ('tabbar', - SettingValue(typ.QtFont(), DEFAULT_FONT_SIZE + MONOSPACE), - "Font used in the tab bar."), - - ('statusbar', - SettingValue(typ.Font(), DEFAULT_FONT_SIZE + MONOSPACE), - "Font used in the statusbar."), - - ('downloads', - SettingValue(typ.Font(), DEFAULT_FONT_SIZE + MONOSPACE), - "Font used for the downloadbar."), - - ('hints', - SettingValue(typ.Font(), 'bold 13px ${_monospace}'), - "Font used for the hints."), - - ('debug-console', - SettingValue(typ.QtFont(), DEFAULT_FONT_SIZE + MONOSPACE), - "Font used for the debugging console."), - - ('web-family-standard', - SettingValue(typ.FontFamily(none_ok=True), ''), - "Font family for standard fonts."), - - ('web-family-fixed', - SettingValue(typ.FontFamily(none_ok=True), ''), - "Font family for fixed fonts."), - - ('web-family-serif', - SettingValue(typ.FontFamily(none_ok=True), ''), - "Font family for serif fonts."), - - ('web-family-sans-serif', - SettingValue(typ.FontFamily(none_ok=True), ''), - "Font family for sans-serif fonts."), - - ('web-family-cursive', - SettingValue(typ.FontFamily(none_ok=True), ''), - "Font family for cursive fonts."), - - ('web-family-fantasy', - SettingValue(typ.FontFamily(none_ok=True), ''), - "Font family for fantasy fonts."), - - # Defaults for web-size-* from WebEngineSettings::initDefaults in - # qtwebengine/src/core/web_engine_settings.cpp and - # QWebSettings::QWebSettings() in - # qtwebkit/Source/WebKit/qt/Api/qwebsettings.cpp - - ('web-size-minimum', - SettingValue(typ.Int(minval=0, maxval=MAXVALS['int']), '0'), - "The hard minimum font size."), - - # This is 0 as default on QtWebKit, and 6 on QtWebEngine - so let's - # just go for 6 here. - ('web-size-minimum-logical', - SettingValue(typ.Int(minval=0, maxval=MAXVALS['int']), '6'), - "The minimum logical font size that is applied when zooming " - "out."), - - ('web-size-default', - SettingValue(typ.Int(minval=1, maxval=MAXVALS['int']), '16'), - "The default font size for regular text."), - - ('web-size-default-fixed', - SettingValue(typ.Int(minval=1, maxval=MAXVALS['int']), '13'), - "The default font size for fixed-pitch text."), - - ('keyhint', - SettingValue(typ.Font(), DEFAULT_FONT_SIZE + MONOSPACE), - "Font used in the keyhint widget."), - - ('messages.error', - SettingValue(typ.Font(), DEFAULT_FONT_SIZE + MONOSPACE), - "Font used for error messages."), - - ('messages.warning', - SettingValue(typ.Font(), DEFAULT_FONT_SIZE + MONOSPACE), - "Font used for warning messages."), - - ('messages.info', - SettingValue(typ.Font(), DEFAULT_FONT_SIZE + MONOSPACE), - "Font used for info messages."), - - ('prompts', - SettingValue(typ.Font(), DEFAULT_FONT_SIZE + ' sans-serif'), - "Font used for prompts."), - - readonly=readonly - )), - ]) - - -DATA = data(readonly=True) - - KEY_FIRST_COMMENT = """ # vim: ft=conf # @@ -1858,3 +522,151 @@ CHANGED_KEY_COMMANDS = [ (re.compile(r'^tab-only -r$'), r'tab-only --next'), (re.compile(r'^tab-only --right$'), r'tab-only --next'), ] + + +Option = collections.namedtuple('Option', ['typ', 'default', 'backends', + 'description']) + + +def _raise_invalid_node(name, what, node): + """Raise an exception for an invalid configdata YAML node. + + Args: + name: The name of the setting being parsed. + what: The name of the thing being parsed. + node: The invalid node. + """ + raise ValueError("Invalid node for {} while reading {}: {!r}".format( + name, what, node)) + + +def _parse_yaml_type(name, node): + if isinstance(node, str): + # e.g: + # type: Bool + # -> create the type object without any arguments + type_name = node + kwargs = {} + elif isinstance(node, dict): + # e.g: + # type: + # name: String + # none_ok: true + # -> create the type object and pass arguments + type_name = node.pop('name') + kwargs = node + else: + _raise_invalid_node(name, 'type', node) + + try: + typ = getattr(configtypes, type_name) + except AttributeError as e: + raise AttributeError("Did not find type {} for {}".format( + type_name, name)) + + # Parse sub-types + try: + if typ is configtypes.Dict: + kwargs['keytype'] = _parse_yaml_type(name, kwargs['keytype']) + kwargs['valtype'] = _parse_yaml_type(name, kwargs['valtype']) + elif typ is configtypes.List: + kwargs['valtype'] = _parse_yaml_type(name, kwargs['valtype']) + except KeyError as e: + _raise_invalid_node(name, str(e), node) + + try: + return typ(**kwargs) + except TypeError as e: + raise TypeError("Error while creating {} with {}: {}".format( + type_name, node, e)) + + +def _parse_yaml_backends_dict(name, node): + """Parse a dict definition for backends. + + Example: + + backends: + QtWebKit: true + QtWebEngine: Qt 5.9 + """ + str_to_backend = { + 'QtWebKit': usertypes.Backend.QtWebKit, + 'QtWebEngine': usertypes.Backend.QtWebEngine, + } + + if node.keys() != str_to_backend.keys(): + _raise_invalid_node(name, 'backends', node) + + backends = [] + + # The value associated to the key, and whether we should add that backend or + # not. + conditionals = { + True: True, + False: False, + 'Qt 5.8': qtutils.version_check('5.8'), + 'Qt 5.9': qtutils.version_check('5.9'), + } + for key in node.keys(): + if conditionals[node[key]]: + backends.append(str_to_backend[key]) + + return backends + + +def _parse_yaml_backends(name, node): + """Parse a backend node in the yaml. + + It can have one of those four forms: + - Not present -> setting applies to both backends. + - backend: QtWebKit -> setting only available with QtWebKit + - backend: QtWebEngine -> setting only available with QtWebEngine + - backend: + QtWebKit: true + QtWebEngine: Qt 5.9 + -> setting available based on the given conditionals. + """ + if node is None: + return [usertypes.Backend.QtWebKit, usertypes.Backend.QtWebEngine] + elif node == 'QtWebKit': + return [usertypes.Backend.QtWebKit] + elif node == 'QtWebEngine': + return [usertypes.Backend.QtWebEngine] + elif isinstance(node, dict): + return _parse_yaml_backends_dict(name, node) + _raise_invalid_node(name, 'backends', node) + + +def _read_yaml(yaml_data): + """Read config data from a YAML file. + + Args: + yaml_data: The YAML string to parse. + + Return: + A dict mapping option names to Option elements. + """ + parsed = {} + data = yaml.load(yaml_data) + + keys = {'type', 'default', 'desc', 'backend'} + + for name, option in data.items(): + if not set(option.keys()).issubset(keys): + raise ValueError("Invalid keys {} for {}".format( + option.keys(), name)) + + parsed[name] = Option( + typ=_parse_yaml_type(name, option['type']), + default=option['default'], + backends=_parse_yaml_backends(name, option.get('backend', None)), + description=option['desc']) + + return parsed + + +def init(): + """Initialize configdata from the YAML file.""" + global DATA + DATA = _read_yaml(utils.read_file('config/configdata.yml')) diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml index 5245095f6..a66875dba 100644 --- a/qutebrowser/config/configdata.yml +++ b/qutebrowser/config/configdata.yml @@ -8,14 +8,14 @@ ignore_case: start_page: type: name: List - elemtype: String + valtype: String default: ["https://start.duckduckgo.com"] desc: The default page(s) to open at the start. yank_ignored_url_parameters: type: name: List - elemtype: String + valtype: String default: - ref - utm_source @@ -78,7 +78,7 @@ editor.command: editor.encoding: type: Encoding default: utf-8 - desc: Encoding to use for the editor. + desc : Encoding to use for the editor. content.private_browsing: type: Bool @@ -198,7 +198,7 @@ history_session_interval: zoom.levels: type: name: List - elemtype: + valtype: name: Perc minval: 0 default: @@ -354,7 +354,7 @@ window.hide_wayland_decoration: keyhint.blacklist: type: name: List - elemtype: + valtype: name: String none_ok: true default: "" @@ -417,11 +417,7 @@ content.user_agent: content.proxy: default: system - type: - name: Proxy - valid_values: - - system: "Use the system wide proxy." - - none: "Don't use any proxy" + type: Proxy backend: QtWebKit: true QtWebEngine: Qt 5.8 @@ -1015,7 +1011,7 @@ content.host_blocking.lists: - "https://pgl.yoyo.org/adservers/serverlist.php?hostformat=hosts&mimetype=plaintext" type: name: List - elemtype: Url + valtype: Url none_ok: true desc: | List of URLs of lists which contain hosts to block. @@ -1037,7 +1033,7 @@ content.host_blocking.whitelist: - piwik.org type: name: List - valtype: string + valtype: String none_ok: true desc: >- List of domains that should always be loaded, despite being ad-blocked. diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py index 3d6e71426..6f11ab1ef 100644 --- a/qutebrowser/config/configtypes.py +++ b/qutebrowser/config/configtypes.py @@ -35,7 +35,7 @@ from PyQt5.QtWidgets import QTabWidget, QTabBar from qutebrowser.commands import cmdutils from qutebrowser.config import configexc -from qutebrowser.utils import standarddir, utils +from qutebrowser.utils import standarddir, utils, qtutils SYSTEM_PROXY = object() # Return value for Proxy type @@ -313,27 +313,27 @@ class List(BaseType): """Base class for a (string-)list setting.""" - _show_inner_type = True + _show_valtype = True - def __init__(self, inner_type, none_ok=False, length=None): + def __init__(self, valtype, none_ok=False, length=None): super().__init__(none_ok) - self.inner_type = inner_type + self.valtype = valtype self.length = length def get_name(self): name = super().get_name() - if self._show_inner_type: - name += " of " + self.inner_type.get_name() + if self._show_valtype: + name += " of " + self.valtype.get_name() return name def get_valid_values(self): - return self.inner_type.get_valid_values() + return self.valtype.get_valid_values() def transform(self, value): if not value: return None else: - return [self.inner_type.transform(v.strip()) + return [self.valtype.transform(v.strip()) for v in value.split(',')] def validate(self, value): @@ -345,7 +345,7 @@ class List(BaseType): raise configexc.ValidationError(value, "Exactly {} values need to " "be set!".format(self.length)) for val in vals: - self.inner_type.validate(val.strip()) + self.valtype.validate(val.strip()) class FlagList(List): @@ -358,14 +358,14 @@ class FlagList(List): combinable_values = None - _show_inner_type = False + _show_valtype = False def __init__(self, none_ok=False, valid_values=None): super().__init__(BaseType(), none_ok) - self.inner_type.valid_values = valid_values + self.valtype.valid_values = valid_values def validate(self, value): - if self.inner_type.valid_values is not None: + if self.valtype.valid_values is not None: super().validate(value) else: self._basic_validation(value) @@ -379,7 +379,7 @@ class FlagList(List): value, "List contains duplicate values!") def complete(self): - valid_values = self.inner_type.valid_values + valid_values = self.valtype.valid_values if valid_values is None: return None @@ -453,11 +453,22 @@ class Int(BaseType): def __init__(self, minval=None, maxval=None, none_ok=False): super().__init__(none_ok) - if maxval is not None and minval is not None and maxval < minval: - raise ValueError("minval ({}) needs to be <= maxval ({})!".format( - minval, maxval)) - self.minval = minval - self.maxval = maxval + self.minval = self._parse_limit(minval) + self.maxval = self._parse_limit(maxval) + if self.maxval is not None and self.minval is not None: + if self.maxval < self.minval: + raise ValueError("minval ({}) needs to be <= maxval ({})!" + .format(self.minval, self.maxval)) + + def _parse_limit(self, value): + if value == 'maxint': + return qtutils.MAXVALS['int'] + elif value == 'maxint64': + return qtutils.MAXVALS['int64'] + else: + if value is not None: + assert isinstance(value, int), value + return value def transform(self, value): if not value: @@ -1056,12 +1067,12 @@ class Padding(List): """Setting for paddings around elements.""" - _show_inner_type = False + _show_valtype = False def __init__(self, none_ok=False, valid_values=None): super().__init__(Int(minval=0, none_ok=none_ok), none_ok=none_ok, length=4) - self.inner_type.valid_values = valid_values + self.valtype.valid_values = valid_values def transform(self, value): elems = super().transform(value) @@ -1179,10 +1190,17 @@ class Url(BaseType): "{}".format(val.errorString())) -class HeaderDict(BaseType): +class Dict(BaseType): """A JSON-like dictionary for custom HTTP headers.""" + # FIXME:conf validate correctly + + def __init__(self, keytype, valtype, none_ok=False): + super().__init__(none_ok) + self.keytype = keytype + self.valtype = valtype + def _validate_str(self, value, what): """Check if the given thing is an ascii-only string. @@ -1274,8 +1292,8 @@ class ConfirmQuit(FlagList): def __init__(self, none_ok=False): super().__init__(none_ok) - self.inner_type.none_ok = none_ok - self.inner_type.valid_values = ValidValues( + self.valtype.none_ok = none_ok + self.valtype.valid_values = ValidValues( ('always', "Always show a confirmation."), ('multiple-tabs', "Show a confirmation if " "multiple tabs are opened."), diff --git a/qutebrowser/config/newconfig.py b/qutebrowser/config/newconfig.py index f4c350d66..807ad0bfe 100644 --- a/qutebrowser/config/newconfig.py +++ b/qutebrowser/config/newconfig.py @@ -48,14 +48,16 @@ class NewConfigManager(QObject): super().__init__(parent) self._values = {} - def _key(self, sect, opt): - return sect + ' -> ' + opt + def _key(self, sect, opt=None): + if opt is None: + # New usage + return sect + return sect + '.' + opt def read_defaults(self): - for name, section in configdata.data().items(): - for key, value in section.items(): - self._values[self._key(name, key)] = value + for name, option in configdata.DATA.items(): + self._values[name] = option def get(self, section, option): val = self._values[self._key(section, option)] - return val.typ.transform(val.value()) + return val.typ.transform(val.default) diff --git a/scripts/dev/pylint_checkers/qute_pylint/config.py b/scripts/dev/pylint_checkers/qute_pylint/config.py index be8ac8da8..f95979bbd 100644 --- a/scripts/dev/pylint_checkers/qute_pylint/config.py +++ b/scripts/dev/pylint_checkers/qute_pylint/config.py @@ -23,16 +23,11 @@ import sys import os import os.path +import yaml import astroid from pylint import interfaces, checkers from pylint.checkers import utils -sys.path.insert( - 0, os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, - os.pardir)) - -from qutebrowser.config import configdata - class ConfigChecker(checkers.BaseChecker): diff --git a/tests/unit/config/test_configdata.py b/tests/unit/config/test_configdata.py index fdb7f00e4..e300318d4 100644 --- a/tests/unit/config/test_configdata.py +++ b/tests/unit/config/test_configdata.py @@ -18,27 +18,198 @@ """Tests for qutebrowser.config.configdata.""" +import textwrap + +import yaml import pytest -from qutebrowser.config import configdata +from qutebrowser.config import configdata, configtypes +from qutebrowser.utils import usertypes -@pytest.mark.parametrize('sect', configdata.DATA.keys()) -def test_section_desc(sect): - """Make sure every section has a description.""" - desc = configdata.SECTION_DESC[sect] - assert isinstance(desc, str) +def test_init(): + """Test reading the default yaml file.""" + configdata.init() + assert isinstance(configdata.DATA, dict) + assert 'ignore_case' in configdata.DATA -def test_data(): - """Some simple sanity tests on data().""" - data = configdata.data() - assert 'general' in data - assert 'ignore-case' in data['general'] +class TestReadYaml: -def test_readonly_data(): - """Make sure DATA is readonly.""" - with pytest.raises(ValueError, match="Trying to modify a read-only " - "config!"): - configdata.DATA['general'].setv('temp', 'ignore-case', 'true', 'true') + def test_valid(self): + data = textwrap.dedent(""" + test1: + type: Bool + default: true + desc: Hello World + + test2: + type: String + default: foo + backend: QtWebKit + desc: Hello World 2 + """) + data = configdata._read_yaml(data) + assert data.keys() == {'test1', 'test2'} + assert data['test1'].description == "Hello World" + assert data['test2'].default == "foo" + assert data['test2'].backends == [usertypes.Backend.QtWebKit] + assert isinstance(data['test1'].typ, configtypes.Bool) + + def test_invalid_keys(self): + """Test reading with unknown keys.""" + data = textwrap.dedent(""" + test: + type: Bool + default: true + desc: Hello World + hello: world + """,) + with pytest.raises(ValueError, match='Invalid keys'): + configdata._read_yaml(data) + + +class TestParseYamlType: + + def _yaml(self, s): + """Get the type from parsed YAML data.""" + return yaml.load(textwrap.dedent(s))['type'] + + def test_simple(self): + """Test type which is only a name.""" + data = self._yaml("type: Bool") + typ = configdata._parse_yaml_type('test', data) + assert isinstance(typ, configtypes.Bool) + assert not typ.none_ok + + def test_complex(self): + """Test type parsing with arguments.""" + data = self._yaml(""" + type: + name: String + minlen: 2 + """) + typ = configdata._parse_yaml_type('test', data) + assert isinstance(typ, configtypes.String) + assert not typ.none_ok + assert typ.minlen == 2 + + def test_list(self): + """Test type parsing with a list and subtypes.""" + data = self._yaml(""" + type: + name: List + valtype: String + """) + typ = configdata._parse_yaml_type('test', data) + assert isinstance(typ, configtypes.List) + assert isinstance(typ.valtype, configtypes.String) + assert not typ.none_ok + assert not typ.valtype.none_ok + + def test_dict(self): + """Test type parsing with a dict and subtypes.""" + data = self._yaml(""" + type: + name: Dict + keytype: String + valtype: + name: Int + minval: 10 + """) + typ = configdata._parse_yaml_type('test', data) + assert isinstance(typ, configtypes.Dict) + assert isinstance(typ.keytype, configtypes.String) + assert isinstance(typ.valtype, configtypes.Int) + assert not typ.none_ok + assert typ.valtype.minval == 10 + + def test_invalid_node(self): + """Test type parsing with invalid node type.""" + data = self._yaml("type: 42") + with pytest.raises(ValueError, match="Invalid node for test while " + "reading type: 42"): + configdata._parse_yaml_type('test', data) + + def test_unknown_type(self): + """Test type parsing with type which doesn't exist.""" + data = self._yaml("type: Foobar") + with pytest.raises(AttributeError, + match="Did not find type Foobar for test"): + configdata._parse_yaml_type('test', data) + + def test_unknown_dict(self): + """Test type parsing with a dict without keytype.""" + data = self._yaml("type: Dict") + with pytest.raises(ValueError, match="Invalid node for test while " + "reading 'keytype': 'Dict'"): + configdata._parse_yaml_type('test', data) + + def test_unknown_args(self): + """Test type parsing with unknown type arguments.""" + data = self._yaml(""" + type: + name: Int + answer: 42 + """) + with pytest.raises(TypeError, match="Error while creating Int"): + configdata._parse_yaml_type('test', data) + + +class TestParseYamlBackend: + + def _yaml(self, s): + """Get the type from parsed YAML data.""" + return yaml.load(textwrap.dedent(s))['backend'] + + @pytest.mark.parametrize('backend, expected', [ + ('QtWebKit', [usertypes.Backend.QtWebKit]), + ('QtWebEngine', [usertypes.Backend.QtWebEngine]), + # This is also what _parse_yaml_backends gets when backend: is not given + # at all + ('null', [usertypes.Backend.QtWebKit, usertypes.Backend.QtWebEngine]), + ]) + def test_simple(self, backend, expected): + """Check a simple "backend: QtWebKit".""" + data = self._yaml("backend: {}".format(backend)) + backends = configdata._parse_yaml_backends('test', data) + assert backends == expected + + @pytest.mark.parametrize('webkit, has_new_version, expected', [ + (True, True, [usertypes.Backend.QtWebKit, + usertypes.Backend.QtWebEngine]), + (False, True, [usertypes.Backend.QtWebEngine]), + (True, False, [usertypes.Backend.QtWebKit]), + ]) + def test_dict(self, monkeypatch, webkit, has_new_version, expected): + data = self._yaml(""" + backend: + QtWebKit: {} + QtWebEngine: Qt 5.8 + """.format('true' if webkit else 'false')) + monkeypatch.setattr(configdata.qtutils, 'version_check', + lambda v: has_new_version) + backends = configdata._parse_yaml_backends('test', data) + assert backends == expected + + @pytest.mark.parametrize('yaml_data', [ + # Wrong type + "backend: 42", + # Unknown key + """ + backend: + QtWebKit: true + QtWebEngine: true + foo: bar + """, + # Missing key + """ + backend: + QtWebKit: true + """, + ]) + def test_invalid_backend(self, yaml_data): + with pytest.raises(ValueError, match="Invalid node for test while " + "reading backends:"): + configdata._parse_yaml_backends('test', self._yaml(yaml_data))