Don't make default config a mutable global.

Before, configdata.DATA only existed once - that means when something
manipulated it, instantiating a new ConfigManager actually gave us the
*modified* rather than the default data.

There's still a (now readonly) configdata.DATA for performance reasons -
before, the settings completion model called data() many times, which caused
initializing of it taking a few (instead of nearly 0) seconds.

See https://github.com/hackebrot/qutebrowser/pull/16#discussion-diff-27770433
This commit is contained in:
Florian Bruhin 2015-04-04 21:20:26 +02:00
parent b2df5a5b47
commit 57158e7191
3 changed files with 898 additions and 833 deletions

View File

@ -281,7 +281,7 @@ class ConfigManager(QObject):
def __init__(self, configdir, fname, relaxed=False, parent=None):
super().__init__(parent)
self._initialized = False
self.sections = configdata.DATA
self.sections = configdata.data()
self._interpolation = configparser.ExtendedInterpolation()
self._proxies = {}
for sectname in self.sections.keys():

View File

@ -23,7 +23,8 @@ Module attributes:
FIRST_COMMENT: The initial comment header to place in the config.
SECTION_DESC: A dictionary with descriptions for sections.
DATA: The config defaults, an OrderedDict of sections.
DATA: A global read-only copy of the default config, an OrderedDict of
sections.
"""
import sys
@ -117,7 +118,13 @@ SECTION_DESC = {
DEFAULT_FONT_SIZE = '10pt' if sys.platform == 'darwin' else '8pt'
DATA = collections.OrderedDict([
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'),
@ -125,7 +132,8 @@ DATA = collections.OrderedDict([
('wrap-search',
SettingValue(typ.Bool(), 'true'),
"Whether to wrap finding text to the top when arriving at the end."),
"Whether to wrap finding text to the top when arriving at the "
"end."),
('startpage',
SettingValue(typ.List(), 'https://www.duckduckgo.com'),
@ -151,9 +159,10 @@ DATA = collections.OrderedDict([
('editor',
SettingValue(typ.ShellCommand(placeholder=True), 'gvim -f "{}"'),
"The editor (and arguments) to use for the `open-editor` command.\n\n"
"Use `{}` for the filename. The value gets split like in a shell, so "
"you can use `\"` or `'` to quote arguments."),
"The editor (and arguments) to use for the `open-editor` "
"command.\n\n"
"Use `{}` for the filename. The value gets split like in a "
"shell, so you can use `\"` or `'` to quote arguments."),
('editor-encoding',
SettingValue(typ.Encoding(), 'utf-8'),
@ -167,8 +176,8 @@ DATA = collections.OrderedDict([
('developer-extras',
SettingValue(typ.Bool(), 'false'),
"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."),
"This needs to be enabled for `:inspector` to work and also adds "
"an _Inspect_ entry to the context menu."),
('print-element-backgrounds',
SettingValue(typ.Bool(), 'true'),
@ -177,11 +186,11 @@ DATA = collections.OrderedDict([
('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."),
"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."),
('site-specific-quirks',
SettingValue(typ.Bool(), 'true'),
@ -191,8 +200,8 @@ DATA = collections.OrderedDict([
SettingValue(typ.String(none_ok=True), ''),
"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. If left empty a default value will be '
"used."),
"_utf-8_, _iso-8859-1_, etc. If left empty a default value will "
"be used."),
('new-instance-open-target',
SettingValue(typ.NewInstanceOpenTarget(), 'window'),
@ -206,13 +215,15 @@ DATA = collections.OrderedDict([
('save-session',
SettingValue(typ.Bool(), 'false'),
"Whether to always save the open pages."),
readonly=readonly
)),
('ui', sect.KeyValue(
('zoom-levels',
SettingValue(typ.PercList(minval=0),
'25%,33%,50%,67%,75%,90%,100%,110%,125%,150%,175%,200%,'
'250%,300%,400%,500%'),
'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',
@ -237,13 +248,14 @@ DATA = collections.OrderedDict([
('zoom-text-only',
SettingValue(typ.Bool(), 'false'),
"Whether the zoom factor on a frame applies only to the text or to "
"all content."),
"Whether the zoom factor on a frame applies only to the text or "
"to all content."),
('frame-flattening',
SettingValue(typ.Bool(), 'false'),
"Whether to expand each subframe to its contents.\n\n"
"This will flatten all the frames to become one scrollable page."),
"This will flatten all the frames to become one scrollable "
"page."),
('user-stylesheet',
SettingValue(typ.UserStyleSheet(),
@ -267,14 +279,16 @@ DATA = collections.OrderedDict([
SettingValue(typ.FormatString(fields=['perc', 'perc_raw', 'title',
'title_sep', 'id']),
'{perc}{title}{title_sep}qutebrowser'),
"The format to use for the window title. The following placeholders "
"are defined:\n\n"
"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."),
readonly=readonly
)),
('network', sect.KeyValue(
@ -293,8 +307,8 @@ DATA = collections.OrderedDict([
('proxy',
SettingValue(typ.Proxy(), 'system'),
"The proxy to use.\n\n"
"In addition to the listed values, you can use a `socks://...` or "
"`http://...` URL."),
"In addition to the listed values, you can use a `socks://...` "
"or `http://...` URL."),
('proxy-dns-requests',
SettingValue(typ.Bool(), 'true'),
@ -307,6 +321,8 @@ DATA = collections.OrderedDict([
('dns-prefetch',
SettingValue(typ.Bool(), 'true'),
"Whether to try to pre-fetch DNS entries to speed up browsing."),
readonly=readonly
)),
('completion', sect.KeyValue(
@ -323,7 +339,8 @@ DATA = collections.OrderedDict([
"Whether to show the autocompletion window."),
('height',
SettingValue(typ.PercOrInt(minperc=0, maxperc=100, minint=1), '50%'),
SettingValue(typ.PercOrInt(minperc=0, maxperc=100, minint=1),
'50%'),
"The height of the completion, in px or as percentage of the "
"window."),
@ -339,13 +356,15 @@ DATA = collections.OrderedDict([
('quick-complete',
SettingValue(typ.Bool(), 'true'),
"Whether to move on to the next part when there's only one possible "
"completion left."),
"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."),
"Whether to shrink the completion to be smaller than the "
"configured size if there are no scrollbars."),
readonly=readonly
)),
('input', sect.KeyValue(
@ -364,12 +383,13 @@ DATA = collections.OrderedDict([
('auto-leave-insert-mode',
SettingValue(typ.Bool(), 'true'),
"Whether to leave insert mode if a non-editable element is clicked."),
"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."),
"Whether to automatically enter insert mode if an editable "
"element is focused after page load."),
('forward-unbound-keys',
SettingValue(typ.ForwardUnboundKeys(), 'auto'),
@ -382,28 +402,32 @@ DATA = collections.OrderedDict([
"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."),
"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."),
"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."),
"Whether to enable Opera-like mouse rocker gestures. This "
"disables the context menu."),
('mouse-zoom-divider',
SettingValue(typ.Int(minval=1), '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."),
"Whether to open new tabs (middleclick/ctrl+click) in "
"background."),
('select-on-remove',
SettingValue(typ.SelectOnRemove(), 'right'),
@ -450,9 +474,10 @@ DATA = collections.OrderedDict([
"Whether to show favicons in the tab bar."),
('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."),
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."),
('indicator-width',
SettingValue(typ.Int(minval=0), '3'),
@ -467,9 +492,9 @@ DATA = collections.OrderedDict([
"Whether to open windows instead of tabs."),
('title-format',
SettingValue(typ.FormatString(fields=['perc', 'perc_raw', 'title',
'title_sep', 'index', 'id']),
'{index}: {title}'),
SettingValue(typ.FormatString(
fields=['perc', 'perc_raw', 'title', 'title_sep', 'index',
'id']), '{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"
@ -479,33 +504,36 @@ DATA = collections.OrderedDict([
"otherwise.\n"
"* `{index}`: The index of this tab.\n"
"* `{id}`: The internal tab ID of this tab."),
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."),
"sensible os-specific default. Will expand environment "
"variables."),
('maximum-pages-in-cache',
SettingValue(
typ.Int(none_ok=True, minval=0, maxval=MAXVALS['int']), ''),
"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"
"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/"),
('object-cache-capacities',
SettingValue(
typ.WebKitBytesList(length=3, maxsize=MAXVALS['int']), ''),
"The capacities for the global memory cache for dead objects such as "
"stylesheets or scripts. Syntax: cacheMinDeadCapacity, cacheMaxDead, "
"totalCapacity.\n\n"
"The _cacheMinDeadCapacity_ specifies the minimum number of bytes "
"that dead objects should consume when the cache is under "
"The capacities for the global memory cache for dead objects "
"such as stylesheets or scripts. Syntax: cacheMinDeadCapacity, "
"cacheMaxDead, totalCapacity.\n\n"
"The _cacheMinDeadCapacity_ specifies the minimum number of "
"bytes that dead objects should consume when the cache is under "
"pressure.\n\n"
"_cacheMaxDead_ is the maximum number of bytes that dead objects "
"should consume when the cache is *not* under pressure.\n\n"
@ -522,26 +550,31 @@ DATA = collections.OrderedDict([
('offline-storage-database',
SettingValue(typ.Bool(), 'true'),
"Whether support for the HTML 5 offline storage feature is enabled."),
"Whether support for the HTML 5 offline storage feature is "
"enabled."),
('offline-web-application-storage',
SettingValue(typ.Bool(), 'true'),
"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"
"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 the HTML 5 local storage feature is enabled."),
"Whether support for the HTML 5 local storage feature is "
"enabled."),
('cache-size',
SettingValue(typ.Int(minval=0, maxval=MAXVALS['int64']), '52428800'),
SettingValue(typ.Int(minval=0, maxval=MAXVALS['int64']),
'52428800'),
"Size of the HTTP network cache."),
readonly=readonly
)),
('content', sect.KeyValue(
@ -556,8 +589,8 @@ DATA = collections.OrderedDict([
('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."),
'Qt plugins with a mimetype such as "application/x-qt-plugin" '
"are not affected by this setting."),
('geolocation',
SettingValue(typ.NoAsk(), 'ask'),
@ -582,7 +615,8 @@ DATA = collections.OrderedDict([
('javascript-can-access-clipboard',
SettingValue(typ.Bool(), 'false'),
"Whether JavaScript programs can read or write to the clipboard."),
"Whether JavaScript programs can read or write to the "
"clipboard."),
('ignore-javascript-prompt',
SettingValue(typ.Bool(), 'false'),
@ -599,8 +633,8 @@ DATA = collections.OrderedDict([
('local-content-can-access-file-urls',
SettingValue(typ.Bool(), 'true'),
"Whether locally loaded documents are allowed to access other local "
"urls."),
"Whether locally loaded documents are allowed to access other "
"local urls."),
('cookies-accept',
SettingValue(typ.AcceptCookies(), 'default'),
@ -611,7 +645,8 @@ DATA = collections.OrderedDict([
"Whether to store cookies."),
('host-block-lists',
SettingValue(typ.UrlList(none_ok=True),
SettingValue(
typ.UrlList(none_ok=True),
'http://www.malwaredomainlist.com/hostslist/hosts.txt,'
'http://someonewhocares.org/hosts/hosts,'
'http://winhelp2002.mvps.org/hosts.zip,'
@ -622,12 +657,14 @@ DATA = collections.OrderedDict([
"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)."),
"- 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."),
readonly=readonly
)),
('hints', sect.KeyValue(
@ -674,15 +711,21 @@ DATA = collections.OrderedDict([
r'\bprev(ious)?\b,\bback\b,\bolder\b,\b[<←≪]\b,'
r'\b(<<|«)\b'),
"A comma-separated list of regexes to use for 'prev' links."),
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(
@ -728,8 +771,8 @@ DATA = collections.OrderedDict([
"Top border color of the completion widget category headers."),
('completion.item.selected.border.bottom',
SettingValue(typ.QssColor(), '${completion.item.selected.border.'
'top}'),
SettingValue(
typ.QssColor(), '${completion.item.selected.border.top}'),
"Bottom border color of the selected completion item."),
('completion.match.fg',
@ -784,7 +827,8 @@ DATA = collections.OrderedDict([
('statusbar.url.fg.hover',
SettingValue(typ.QssColor(), 'aqua'),
"Foreground color of the URL in the statusbar for hovered links."),
"Foreground color of the URL in the statusbar for hovered "
"links."),
('tabs.fg.odd',
SettingValue(typ.QtColor(), 'white'),
@ -839,7 +883,8 @@ DATA = collections.OrderedDict([
"Font color for the matched part of hints."),
('hints.bg',
SettingValue(typ.CssColor(), '-webkit-gradient(linear, left top, '
SettingValue(
typ.CssColor(), '-webkit-gradient(linear, left top, '
'left bottom, color-stop(0%,#FFF785), '
'color-stop(100%,#FFC542))'),
"Background color for hints."),
@ -867,14 +912,17 @@ DATA = collections.OrderedDict([
('downloads.bg.error',
SettingValue(typ.QtColor(), 'red'),
"Background color for downloads with errors."),
readonly=readonly
)),
('fonts', sect.KeyValue(
('_monospace',
SettingValue(typ.Font(), 'Terminus, Monospace, "DejaVu Sans Mono", '
'Monaco, "Bitstream Vera Sans Mono", "Andale Mono", '
'"Liberation Mono", "Courier New", Courier, monospace, '
'Fixed, Consolas, Terminal'),
SettingValue(typ.Font(), 'Terminus, Monospace, '
'"DejaVu Sans Mono", Monaco, '
'"Bitstream Vera Sans Mono", "Andale Mono", '
'"Liberation Mono", "Courier New", Courier, '
'monospace, Fixed, Consolas, Terminal'),
"Default monospace fonts."),
('completion',
@ -933,7 +981,8 @@ DATA = collections.OrderedDict([
('web-size-minimum-logical',
SettingValue(
typ.Int(none_ok=True, minval=1, maxval=MAXVALS['int']), ''),
"The minimum logical font size that is applied when zooming out."),
"The minimum logical font size that is applied when zooming "
"out."),
('web-size-default',
SettingValue(
@ -944,10 +993,15 @@ DATA = collections.OrderedDict([
SettingValue(
typ.Int(none_ok=True, minval=1, maxval=MAXVALS['int']), ''),
"The default font size for fixed-pitch text."),
readonly=readonly
)),
])
DATA = data(readonly=True)
KEY_FIRST_COMMENT = """
# vim: ft=conf
#

View File

@ -29,6 +29,7 @@ class Section:
"""Base class for KeyValue/ValueList sections.
Attributes:
_readonly: Whether this section is read-only.
values: An OrderedDict with key as index and value as value.
key: string
value: SettingValue
@ -38,6 +39,7 @@ class Section:
def __init__(self):
self.values = None
self.descriptions = {}
self._readonly = False
def __getitem__(self, key):
"""Get the value for key.
@ -99,13 +101,15 @@ class KeyValue(Section):
set of keys.
"""
def __init__(self, *defaults):
def __init__(self, *defaults, readonly=False):
"""Constructor.
Args:
*defaults: A (key, value, description) list of defaults.
readonly: Whether this config is readonly.
"""
super().__init__()
self._readonly = readonly
if not defaults:
return
self.values = collections.OrderedDict()
@ -115,6 +119,8 @@ class KeyValue(Section):
self.descriptions[k] = desc
def setv(self, layer, key, value, interpolated):
if self._readonly:
raise ValueError("Trying to modify a read-only config!")
self.values[key].setv(layer, value, interpolated)
def dump_userconfig(self):
@ -143,17 +149,20 @@ class ValueList(Section):
keytype: The type to use for the key (only used for validating)
valtype: The type to use for the value.
_ordered_value_cache: A ChainMap-like OrderedDict of all values.
_readonly: Whether this section is read-only.
"""
def __init__(self, keytype, valtype, *defaults):
def __init__(self, keytype, valtype, *defaults, readonly=False):
"""Wrap types over default values. Take care when overriding this.
Args:
keytype: The type instance to be used for keys.
valtype: The type instance to be used for values.
*defaults: A (key, value) list of default values.
readonly: Whether this config is readonly.
"""
super().__init__()
self._readonly = readonly
self._ordered_value_cache = None
self.keytype = keytype
self.valtype = valtype
@ -182,6 +191,8 @@ class ValueList(Section):
return self._ordered_value_cache
def setv(self, layer, key, value, interpolated):
if self._readonly:
raise ValueError("Trying to modify a read-only config!")
self.keytype.validate(key)
if key in self.layers[layer]:
self.layers[layer][key].setv(layer, value, interpolated)