Merge branch 'config-write-py'

This commit is contained in:
Florian Bruhin 2017-10-05 11:30:50 +02:00
commit 618586f8b0
13 changed files with 421 additions and 354 deletions

View File

@ -67,6 +67,7 @@ Added
- `:config-clear` to remove all configured options - `:config-clear` to remove all configured options
- `:config-source` to (re-)read a `config.py` file - `:config-source` to (re-)read a `config.py` file
- `:config-edit` to open the `config.py` file in an editor - `:config-edit` to open the `config.py` file in an editor
- `:config-write-py` to write a `config.py` template file
- New `:version` command which opens `qute://version`. - New `:version` command which opens `qute://version`.
Changed Changed

View File

@ -36,6 +36,7 @@ It is possible to run or bind multiple commands by separating them with `;;`.
|<<config-edit,config-edit>>|Open the config.py file in the editor. |<<config-edit,config-edit>>|Open the config.py file in the editor.
|<<config-source,config-source>>|Read a config.py file. |<<config-source,config-source>>|Read a config.py file.
|<<config-unset,config-unset>>|Unset an option. |<<config-unset,config-unset>>|Unset an option.
|<<config-write-py,config-write-py>>|Write the current configuration to a config.py file.
|<<download,download>>|Download a given URL, or current page if no URL given. |<<download,download>>|Download a given URL, or current page if no URL given.
|<<download-cancel,download-cancel>>|Cancel the last/[count]th download. |<<download-cancel,download-cancel>>|Cancel the last/[count]th download.
|<<download-clear,download-clear>>|Remove all finished downloads from the list. |<<download-clear,download-clear>>|Remove all finished downloads from the list.
@ -266,6 +267,19 @@ This sets an option back to its default and removes it from autoconfig.yml.
==== optional arguments ==== optional arguments
* +*-t*+, +*--temp*+: Don't touch autoconfig.yml. * +*-t*+, +*--temp*+: Don't touch autoconfig.yml.
[[config-write-py]]
=== config-write-py
Syntax: +:config-write-py [*--force*] [*--defaults*] ['filename']+
Write the current configuration to a config.py file.
==== positional arguments
* +'filename'+: The file to write to, or not given for the default config.py.
==== optional arguments
* +*-f*+, +*--force*+: Force overwriting existing files.
* +*-d*+, +*--defaults*+: Write the defaults instead of values configured via :set.
[[download]] [[download]]
=== download === download
Syntax: +:download [*--mhtml*] [*--dest* 'dest'] ['url'] ['dest-old']+ Syntax: +:download [*--mhtml*] [*--dest* 'dest'] ['url'] ['dest-old']+

View File

@ -68,6 +68,12 @@ will never be used in a default keybinding.
See the help pages linked above (or `:help :bind`, `:help :unbind`) for more See the help pages linked above (or `:help :bind`, `:help :unbind`) for more
information. information.
Other useful commands for config manipulation are
link:commands.html#config-unset[`:config-unset`] to reset a value to its default,
link:commands.html#config-clear[`:config-clear`] to reset the entire configuration,
and link:commands.html#config-cycle[`:config-cycle`] to cycle a setting between
different values.
Configuring qutebrowser via config.py Configuring qutebrowser via config.py
------------------------------------- -------------------------------------
@ -77,6 +83,10 @@ configuration. Note that qutebrowser will never touch this file - this means
you'll be responsible for updating it when upgrading to a newer qutebrowser you'll be responsible for updating it when upgrading to a newer qutebrowser
version. version.
You can run `:config-edit` inside qutebrowser to open the file in your editor,
`:config-source` to reload the file (`:config-edit` does this automatically), or
`:config-write-py --defaults` to write a template file to work with.
The file should be located in the "config" location listed on The file should be located in the "config" location listed on
link:qute://version[], which is typically `~/.config/qutebrowser/config.py` on link:qute://version[], which is typically `~/.config/qutebrowser/config.py` on
Linux, `~/.qutebrowser/config.py` on macOS, and Linux, `~/.qutebrowser/config.py` on macOS, and

View File

@ -280,11 +280,6 @@ Always restore open sites when qutebrowser is reopened.
Type: <<types,Bool>> Type: <<types,Bool>>
Valid values:
* +true+
* +false+
Default: +pass:[false]+ Default: +pass:[false]+
[[backend]] [[backend]]
@ -1304,11 +1299,6 @@ Move on to the next part when there's only one possible completion left.
Type: <<types,Bool>> Type: <<types,Bool>>
Valid values:
* +true+
* +false+
Default: +pass:[true]+ Default: +pass:[true]+
[[completion.scrollbar.padding]] [[completion.scrollbar.padding]]
@ -1347,11 +1337,6 @@ Shrink the completion to be smaller than the configured size if there are no scr
Type: <<types,Bool>> Type: <<types,Bool>>
Valid values:
* +true+
* +false+
Default: +pass:[false]+ Default: +pass:[false]+
[[completion.timestamp_format]] [[completion.timestamp_format]]
@ -1395,11 +1380,6 @@ An application cache acts like an HTTP cache in some sense. For documents that u
Type: <<types,Bool>> Type: <<types,Bool>>
Valid values:
* +true+
* +false+
Default: +pass:[true]+ Default: +pass:[true]+
This setting is only available with the QtWebKit backend. This setting is only available with the QtWebKit backend.
@ -1448,11 +1428,6 @@ Note this option needs a restart with QtWebEngine on Qt < 5.9.
Type: <<types,Bool>> Type: <<types,Bool>>
Valid values:
* +true+
* +false+
Default: +pass:[true]+ Default: +pass:[true]+
[[content.default_encoding]] [[content.default_encoding]]
@ -1471,11 +1446,6 @@ This needs to be enabled for `:inspector` to work and also adds an _Inspect_ ent
Type: <<types,Bool>> Type: <<types,Bool>>
Valid values:
* +true+
* +false+
Default: +pass:[false]+ Default: +pass:[false]+
This setting is only available with the QtWebKit backend. This setting is only available with the QtWebKit backend.
@ -1486,11 +1456,6 @@ Try to pre-fetch DNS entries to speed up browsing.
Type: <<types,Bool>> Type: <<types,Bool>>
Valid values:
* +true+
* +false+
Default: +pass:[true]+ Default: +pass:[true]+
This setting is only available with the QtWebKit backend. This setting is only available with the QtWebKit backend.
@ -1502,11 +1467,6 @@ This will flatten all the frames to become one scrollable page.
Type: <<types,Bool>> Type: <<types,Bool>>
Valid values:
* +true+
* +false+
Default: +pass:[false]+ Default: +pass:[false]+
This setting is only available with the QtWebKit backend. This setting is only available with the QtWebKit backend.
@ -1548,11 +1508,6 @@ When this is set to true, qutebrowser asks websites to not track your identity.
Type: <<types,Bool>> Type: <<types,Bool>>
Valid values:
* +true+
* +false+
Default: +pass:[true]+ Default: +pass:[true]+
[[content.headers.referer]] [[content.headers.referer]]
@ -1586,11 +1541,6 @@ Whether host blocking is enabled.
Type: <<types,Bool>> Type: <<types,Bool>>
Valid values:
* +true+
* +false+
Default: +pass:[true]+ Default: +pass:[true]+
[[content.host_blocking.lists]] [[content.host_blocking.lists]]
@ -1633,11 +1583,6 @@ Enable or disable hyperlink auditing (`<a ping>`).
Type: <<types,Bool>> Type: <<types,Bool>>
Valid values:
* +true+
* +false+
Default: +pass:[false]+ Default: +pass:[false]+
[[content.images]] [[content.images]]
@ -1646,11 +1591,6 @@ Whether images are automatically loaded in web pages.
Type: <<types,Bool>> Type: <<types,Bool>>
Valid values:
* +true+
* +false+
Default: +pass:[true]+ Default: +pass:[true]+
[[content.javascript.alert]] [[content.javascript.alert]]
@ -1659,11 +1599,6 @@ Show javascript alerts.
Type: <<types,Bool>> Type: <<types,Bool>>
Valid values:
* +true+
* +false+
Default: +pass:[true]+ Default: +pass:[true]+
[[content.javascript.can_access_clipboard]] [[content.javascript.can_access_clipboard]]
@ -1673,11 +1608,6 @@ With QtWebEngine, writing the clipboard as response to a user interaction is alw
Type: <<types,Bool>> Type: <<types,Bool>>
Valid values:
* +true+
* +false+
Default: +pass:[false]+ Default: +pass:[false]+
[[content.javascript.can_close_tabs]] [[content.javascript.can_close_tabs]]
@ -1686,11 +1616,6 @@ Whether JavaScript can close tabs.
Type: <<types,Bool>> Type: <<types,Bool>>
Valid values:
* +true+
* +false+
Default: +pass:[false]+ Default: +pass:[false]+
This setting is only available with the QtWebKit backend. This setting is only available with the QtWebKit backend.
@ -1701,11 +1626,6 @@ Whether JavaScript can open new tabs without user interaction.
Type: <<types,Bool>> Type: <<types,Bool>>
Valid values:
* +true+
* +false+
Default: +pass:[false]+ Default: +pass:[false]+
[[content.javascript.enabled]] [[content.javascript.enabled]]
@ -1714,11 +1634,6 @@ Enables or disables JavaScript.
Type: <<types,Bool>> Type: <<types,Bool>>
Valid values:
* +true+
* +false+
Default: +pass:[true]+ Default: +pass:[true]+
[[content.javascript.log]] [[content.javascript.log]]
@ -1742,11 +1657,6 @@ Use the standard JavaScript modal dialog for `alert()` and `confirm()`
Type: <<types,Bool>> Type: <<types,Bool>>
Valid values:
* +true+
* +false+
Default: +pass:[false]+ Default: +pass:[false]+
[[content.javascript.prompt]] [[content.javascript.prompt]]
@ -1755,11 +1665,6 @@ Show javascript prompts.
Type: <<types,Bool>> Type: <<types,Bool>>
Valid values:
* +true+
* +false+
Default: +pass:[true]+ Default: +pass:[true]+
[[content.local_content_can_access_file_urls]] [[content.local_content_can_access_file_urls]]
@ -1768,11 +1673,6 @@ Whether locally loaded documents are allowed to access other local urls.
Type: <<types,Bool>> Type: <<types,Bool>>
Valid values:
* +true+
* +false+
Default: +pass:[true]+ Default: +pass:[true]+
[[content.local_content_can_access_remote_urls]] [[content.local_content_can_access_remote_urls]]
@ -1781,11 +1681,6 @@ Whether locally loaded documents are allowed to access remote urls.
Type: <<types,Bool>> Type: <<types,Bool>>
Valid values:
* +true+
* +false+
Default: +pass:[false]+ Default: +pass:[false]+
[[content.local_storage]] [[content.local_storage]]
@ -1794,11 +1689,6 @@ Whether support for HTML 5 local storage and Web SQL is enabled.
Type: <<types,Bool>> Type: <<types,Bool>>
Valid values:
* +true+
* +false+
Default: +pass:[true]+ Default: +pass:[true]+
[[content.media_capture]] [[content.media_capture]]
@ -1847,11 +1737,6 @@ Note that the files can still be downloaded by clicking the download button in t
Type: <<types,Bool>> Type: <<types,Bool>>
Valid values:
* +true+
* +false+
Default: +pass:[false]+ Default: +pass:[false]+
This setting is only available with the QtWebKit backend. This setting is only available with the QtWebKit backend.
@ -1862,11 +1747,6 @@ Enables or disables plugins in Web pages.
Type: <<types,Bool>> Type: <<types,Bool>>
Valid values:
* +true+
* +false+
Default: +pass:[false]+ Default: +pass:[false]+
[[content.print_element_backgrounds]] [[content.print_element_backgrounds]]
@ -1875,11 +1755,6 @@ Whether the background color and images are also drawn when the page is printed.
Type: <<types,Bool>> Type: <<types,Bool>>
Valid values:
* +true+
* +false+
Default: +pass:[true]+ Default: +pass:[true]+
On QtWebEngine, this setting requires Qt 5.8 or newer. On QtWebEngine, this setting requires Qt 5.8 or newer.
@ -1890,11 +1765,6 @@ Open new windows in private browsing mode which does not record visited pages.
Type: <<types,Bool>> Type: <<types,Bool>>
Valid values:
* +true+
* +false+
Default: +pass:[false]+ Default: +pass:[false]+
[[content.proxy]] [[content.proxy]]
@ -1917,11 +1787,6 @@ Send DNS requests over the configured proxy.
Type: <<types,Bool>> Type: <<types,Bool>>
Valid values:
* +true+
* +false+
Default: +pass:[true]+ Default: +pass:[true]+
This setting is only available with the QtWebKit backend. This setting is only available with the QtWebKit backend.
@ -1954,11 +1819,6 @@ Enables or disables WebGL.
Type: <<types,Bool>> Type: <<types,Bool>>
Valid values:
* +true+
* +false+
Default: +pass:[true]+ Default: +pass:[true]+
[[content.xss_auditing]] [[content.xss_auditing]]
@ -1968,11 +1828,6 @@ Suspicious scripts will be blocked and reported in the inspector's JavaScript co
Type: <<types,Bool>> Type: <<types,Bool>>
Valid values:
* +true+
* +false+
Default: +pass:[false]+ Default: +pass:[false]+
[[downloads.location.directory]] [[downloads.location.directory]]
@ -1991,11 +1846,6 @@ If set to false, `downloads.location.directory` will be used.
Type: <<types,Bool>> Type: <<types,Bool>>
Valid values:
* +true+
* +false+
Default: +pass:[true]+ Default: +pass:[true]+
[[downloads.location.remember]] [[downloads.location.remember]]
@ -2004,11 +1854,6 @@ Remember the last used download directory.
Type: <<types,Bool>> Type: <<types,Bool>>
Valid values:
* +true+
* +false+
Default: +pass:[true]+ Default: +pass:[true]+
[[downloads.location.suggestion]] [[downloads.location.suggestion]]
@ -2270,11 +2115,6 @@ This is needed for QtWebEngine to work with Nouveau drivers. This setting requir
Type: <<types,Bool>> Type: <<types,Bool>>
Valid values:
* +true+
* +false+
Default: +pass:[false]+ Default: +pass:[false]+
This setting is only available with the QtWebEngine backend. This setting is only available with the QtWebEngine backend.
@ -2347,11 +2187,6 @@ Hide unmatched hints in rapid mode.
Type: <<types,Bool>> Type: <<types,Bool>>
Valid values:
* +true+
* +false+
Default: +pass:[true]+ Default: +pass:[true]+
[[hints.min_chars]] [[hints.min_chars]]
@ -2412,11 +2247,6 @@ Ignored for number hints.
Type: <<types,Bool>> Type: <<types,Bool>>
Valid values:
* +true+
* +false+
Default: +pass:[true]+ Default: +pass:[true]+
[[hints.uppercase]] [[hints.uppercase]]
@ -2425,11 +2255,6 @@ Make chars in hint strings uppercase.
Type: <<types,Bool>> Type: <<types,Bool>>
Valid values:
* +true+
* +false+
Default: +pass:[false]+ Default: +pass:[false]+
[[history_gap_interval]] [[history_gap_interval]]
@ -2475,11 +2300,6 @@ Leave insert mode if a non-editable element is clicked.
Type: <<types,Bool>> Type: <<types,Bool>>
Valid values:
* +true+
* +false+
Default: +pass:[true]+ Default: +pass:[true]+
[[input.insert_mode.auto_load]] [[input.insert_mode.auto_load]]
@ -2488,11 +2308,6 @@ Automatically enter insert mode if an editable element is focused after loading
Type: <<types,Bool>> Type: <<types,Bool>>
Valid values:
* +true+
* +false+
Default: +pass:[false]+ Default: +pass:[false]+
[[input.insert_mode.plugins]] [[input.insert_mode.plugins]]
@ -2501,11 +2316,6 @@ Switch to insert mode when clicking flash and other plugins.
Type: <<types,Bool>> Type: <<types,Bool>>
Valid values:
* +true+
* +false+
Default: +pass:[false]+ Default: +pass:[false]+
[[input.links_included_in_focus_chain]] [[input.links_included_in_focus_chain]]
@ -2514,11 +2324,6 @@ Include hyperlinks in the keyboard focus chain when tabbing.
Type: <<types,Bool>> Type: <<types,Bool>>
Valid values:
* +true+
* +false+
Default: +pass:[true]+ Default: +pass:[true]+
[[input.partial_timeout]] [[input.partial_timeout]]
@ -2537,11 +2342,6 @@ This disables the context menu.
Type: <<types,Bool>> Type: <<types,Bool>>
Valid values:
* +true+
* +false+
Default: +pass:[false]+ Default: +pass:[false]+
[[input.spatial_navigation]] [[input.spatial_navigation]]
@ -2551,11 +2351,6 @@ Spatial navigation consists in the ability to navigate between focusable element
Type: <<types,Bool>> Type: <<types,Bool>>
Valid values:
* +true+
* +false+
Default: +pass:[false]+ Default: +pass:[false]+
[[keyhint.blacklist]] [[keyhint.blacklist]]
@ -2590,11 +2385,6 @@ Show messages in unfocused windows.
Type: <<types,Bool>> Type: <<types,Bool>>
Valid values:
* +true+
* +false+
Default: +pass:[false]+ Default: +pass:[false]+
[[new_instance_open_target]] [[new_instance_open_target]]
@ -2637,11 +2427,6 @@ Show a filebrowser in upload/download prompts.
Type: <<types,Bool>> Type: <<types,Bool>>
Valid values:
* +true+
* +false+
Default: +pass:[true]+ Default: +pass:[true]+
[[prompt.radius]] [[prompt.radius]]
@ -2668,11 +2453,6 @@ Show a scrollbar.
Type: <<types,Bool>> Type: <<types,Bool>>
Valid values:
* +true+
* +false+
Default: +pass:[false]+ Default: +pass:[false]+
[[scrolling.smooth]] [[scrolling.smooth]]
@ -2682,11 +2462,6 @@ Note smooth scrolling does not work with the `:scroll-px` command.
Type: <<types,Bool>> Type: <<types,Bool>>
Valid values:
* +true+
* +false+
Default: +pass:[false]+ Default: +pass:[false]+
[[session_default_name]] [[session_default_name]]
@ -2704,11 +2479,6 @@ Hide the statusbar unless a message is shown.
Type: <<types,Bool>> Type: <<types,Bool>>
Valid values:
* +true+
* +false+
Default: +pass:[false]+ Default: +pass:[false]+
[[statusbar.padding]] [[statusbar.padding]]
@ -2743,11 +2513,6 @@ Open new tabs (middleclick/ctrl+click) in the background.
Type: <<types,Bool>> Type: <<types,Bool>>
Valid values:
* +true+
* +false+
Default: +pass:[false]+ Default: +pass:[false]+
[[tabs.close_mouse_button]] [[tabs.close_mouse_button]]
@ -2779,11 +2544,6 @@ Show favicons in the tab bar.
Type: <<types,Bool>> Type: <<types,Bool>>
Valid values:
* +true+
* +false+
Default: +pass:[true]+ Default: +pass:[true]+
[[tabs.indicator_padding]] [[tabs.indicator_padding]]
@ -2821,11 +2581,6 @@ Switch between tabs using the mouse wheel.
Type: <<types,Bool>> Type: <<types,Bool>>
Valid values:
* +true+
* +false+
Default: +pass:[true]+ Default: +pass:[true]+
[[tabs.new_position.related]] [[tabs.new_position.related]]
@ -2929,11 +2684,6 @@ Open a new window for every tab.
Type: <<types,Bool>> Type: <<types,Bool>>
Valid values:
* +true+
* +false+
Default: +pass:[false]+ Default: +pass:[false]+
[[tabs.title.alignment]] [[tabs.title.alignment]]
@ -3001,11 +2751,6 @@ Whether to wrap when changing tabs.
Type: <<types,Bool>> Type: <<types,Bool>>
Valid values:
* +true+
* +false+
Default: +pass:[true]+ Default: +pass:[true]+
[[url.auto_search]] [[url.auto_search]]
@ -3090,11 +2835,6 @@ Hide the window decoration when using wayland (requires restart)
Type: <<types,Bool>> Type: <<types,Bool>>
Valid values:
* +true+
* +false+
Default: +pass:[false]+ Default: +pass:[false]+
[[window.title_format]] [[window.title_format]]
@ -3164,11 +2904,6 @@ Whether the zoom factor on a frame applies only to the text or to all content.
Type: <<types,Bool>> Type: <<types,Bool>>
Valid values:
* +true+
* +false+
Default: +pass:[false]+ Default: +pass:[false]+
This setting is only available with the QtWebKit backend. This setting is only available with the QtWebKit backend.

View File

@ -223,6 +223,11 @@ class Config(QObject):
self._mutables = {} self._mutables = {}
self._yaml = yaml_config self._yaml = yaml_config
def __iter__(self):
"""Iterate over Option, value tuples."""
for name, value in sorted(self._values.items()):
yield (self.get_opt(name), value)
def init_save_manager(self, save_manager): def init_save_manager(self, save_manager):
"""Make sure the config gets saved properly. """Make sure the config gets saved properly.
@ -365,10 +370,9 @@ class Config(QObject):
The changed config part as string. The changed config part as string.
""" """
lines = [] lines = []
for optname, value in sorted(self._values.items()): for opt, value in self:
opt = self.get_opt(optname)
str_value = opt.typ.to_str(value) str_value = opt.typ.to_str(value)
lines.append('{} = {}'.format(optname, str_value)) lines.append('{} = {}'.format(opt.name, str_value))
if not lines: if not lines:
lines = ['<Default configuration>'] lines = ['<Default configuration>']
return '\n'.join(lines) return '\n'.join(lines)

View File

@ -27,7 +27,7 @@ from PyQt5.QtCore import QUrl
from qutebrowser.commands import cmdexc, cmdutils from qutebrowser.commands import cmdexc, cmdutils
from qutebrowser.completion.models import configmodel from qutebrowser.completion.models import configmodel
from qutebrowser.utils import objreg, utils, message, standarddir from qutebrowser.utils import objreg, utils, message, standarddir
from qutebrowser.config import configtypes, configexc, configfiles from qutebrowser.config import configtypes, configexc, configfiles, configdata
from qutebrowser.misc import editor from qutebrowser.misc import editor
@ -246,3 +246,35 @@ class ConfigCommands:
filename = os.path.join(standarddir.config(), 'config.py') filename = os.path.join(standarddir.config(), 'config.py')
ed.edit_file(filename) ed.edit_file(filename)
@cmdutils.register(instance='config-commands')
def config_write_py(self, filename=None, force=False, defaults=False):
"""Write the current configuration to a config.py file.
Args:
filename: The file to write to, or None for the default config.py.
force: Force overwriting existing files.
defaults: Write the defaults instead of values configured via :set.
"""
if filename is None:
filename = os.path.join(standarddir.config(), 'config.py')
else:
filename = os.path.expanduser(filename)
if os.path.exists(filename) and not force:
raise cmdexc.CommandError("{} already exists - use --force to "
"overwrite!".format(filename))
if defaults:
options = [(opt, opt.default)
for _name, opt in sorted(configdata.DATA.items())]
bindings = dict(configdata.DATA['bindings.default'].default)
commented = True
else:
options = list(self._config)
bindings = dict(self._config.get_obj('bindings.commands'))
commented = False
writer = configfiles.ConfigPyWriter(options, bindings,
commented=commented)
writer.write(filename)

View File

@ -245,6 +245,101 @@ class ConfigAPI:
self._keyconfig.unbind(key, mode=mode) self._keyconfig.unbind(key, mode=mode)
class ConfigPyWriter:
"""Writer for config.py files from given settings."""
def __init__(self, options, bindings, *, commented):
self._options = options
self._bindings = bindings
self._commented = commented
def write(self, filename):
"""Write the config to the given file."""
with open(filename, 'w', encoding='utf-8') as f:
f.write('\n'.join(self._gen_lines()))
def _line(self, line):
"""Get an (optionally commented) line."""
if self._commented:
if line.startswith('#'):
return '#' + line
else:
return '# ' + line
else:
return line
def _gen_lines(self):
"""Generate a config.py with the given settings/bindings.
Yields individual lines.
"""
yield from self._gen_header()
yield from self._gen_options()
yield from self._gen_bindings()
def _gen_header(self):
"""Generate the initial header of the config."""
yield self._line("# Autogenerated config.py")
yield self._line("# Documentation:")
yield self._line("# qute://help/configuring.html")
yield self._line("# qute://help/settings.html")
yield ''
if self._commented:
# When generated from an autoconfig.yml with commented=False,
# we don't want to load that autoconfig.yml anymore.
yield self._line("# This is here so configs done via the GUI are "
"still loaded.")
yield self._line("# Remove it to not load settings done via the "
"GUI.")
yield self._line("config.load_autoconfig()")
yield ''
else:
yield self._line("# Uncomment this to still load settings "
"configured via autoconfig.yml")
yield self._line("# config.load_autoconfig()")
yield ''
def _gen_options(self):
"""Generate the options part of the config."""
for opt, value in self._options:
if opt.name in ['bindings.commands', 'bindings.default']:
continue
for line in textwrap.wrap(opt.description):
yield self._line("# {}".format(line))
yield self._line("# Type: {}".format(opt.typ.get_name()))
valid_values = opt.typ.get_valid_values()
if valid_values is not None and valid_values.generate_docs:
yield self._line("# Valid values:")
for val in valid_values:
try:
desc = valid_values.descriptions[val]
yield self._line("# - {}: {}".format(val, desc))
except KeyError:
yield self._line("# - {}".format(val))
yield self._line('c.{} = {!r}'.format(opt.name, value))
yield ''
def _gen_bindings(self):
"""Generate the bindings part of the config."""
normal_bindings = self._bindings.pop('normal', {})
if normal_bindings:
yield self._line('# Bindings for normal mode')
for key, command in sorted(normal_bindings.items()):
yield self._line('config.bind({!r}, {!r})'.format(key, command))
for mode, mode_bindings in sorted(self._bindings.items()):
yield ''
yield self._line('# Bindings for {} mode'.format(mode))
for key, command in sorted(mode_bindings.items()):
yield self._line('config.bind({!r}, {!r}, mode={!r})'.format(
key, command, mode))
def read_config_py(filename, raising=False): def read_config_py(filename, raising=False):
"""Read a config.py file. """Read a config.py file.
@ -253,6 +348,9 @@ def read_config_py(filename, raising=False):
raising: Raise exceptions happening in config.py. raising: Raise exceptions happening in config.py.
This is needed during tests to use pytest's inspection. This is needed during tests to use pytest's inspection.
""" """
assert config.instance is not None
assert config.key_instance is not None
api = ConfigAPI(config.instance, config.key_instance) api = ConfigAPI(config.instance, config.key_instance)
container = config.ConfigContainer(config.instance, configapi=api) container = config.ConfigContainer(config.instance, configapi=api)
basename = os.path.basename(filename) basename = os.path.basename(filename)

View File

@ -78,13 +78,15 @@ class ValidValues:
Attributes: Attributes:
values: A list with the allowed untransformed values. values: A list with the allowed untransformed values.
descriptions: A dict with value/desc mappings. descriptions: A dict with value/desc mappings.
generate_docs: Whether to show the values in the docs.
""" """
def __init__(self, *values): def __init__(self, *values, generate_docs=True):
if not values: if not values:
raise ValueError("ValidValues with no values makes no sense!") raise ValueError("ValidValues with no values makes no sense!")
self.descriptions = {} self.descriptions = {}
self.values = [] self.values = []
self.generate_docs = generate_docs
for value in values: for value in values:
if isinstance(value, str): if isinstance(value, str):
# Value without description # Value without description
@ -608,7 +610,7 @@ class Bool(BaseType):
def __init__(self, none_ok=False): def __init__(self, none_ok=False):
super().__init__(none_ok) super().__init__(none_ok)
self.valid_values = ValidValues('true', 'false') self.valid_values = ValidValues('true', 'false', generate_docs=False)
def to_py(self, value): def to_py(self, value):
self._basic_py_validation(value, bool) self._basic_py_validation(value, bool)

View File

@ -421,7 +421,7 @@ def _generate_setting_option(f, opt):
f.write("\n") f.write("\n")
valid_values = opt.typ.get_valid_values() valid_values = opt.typ.get_valid_values()
if valid_values is not None: if valid_values is not None and valid_values.generate_docs:
f.write("Valid values:\n") f.write("Valid values:\n")
f.write("\n") f.write("\n")
for val in valid_values: for val in valid_values:

View File

@ -1,29 +0,0 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# This file is part of qutebrowser.
#
# qutebrowser is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# qutebrowser is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
"""Fixtures needed in various config test files."""
import pytest
from qutebrowser.config import config
@pytest.fixture
def keyconf(config_stub):
config_stub.val.aliases = {}
return config.KeyConfig(config_stub)

View File

@ -93,11 +93,6 @@ class TestChangeFilter:
class TestKeyConfig: class TestKeyConfig:
@pytest.fixture
def keyconf(self, config_stub):
config_stub.val.aliases = {}
return config.KeyConfig(config_stub)
@pytest.fixture @pytest.fixture
def no_bindings(self): def no_bindings(self):
"""Get a dict with no bindings.""" """Get a dict with no bindings."""
@ -107,14 +102,14 @@ class TestKeyConfig:
('A', 'A'), ('A', 'A'),
('<Ctrl-X>', '<ctrl+x>'), ('<Ctrl-X>', '<ctrl+x>'),
]) ])
def test_prepare_valid(self, keyconf, key, expected): def test_prepare_valid(self, key_config_stub, key, expected):
"""Make sure prepare normalizes the key.""" """Make sure prepare normalizes the key."""
assert keyconf._prepare(key, 'normal') == expected assert key_config_stub._prepare(key, 'normal') == expected
def test_prepare_invalid(self, keyconf): def test_prepare_invalid(self, key_config_stub):
"""Make sure prepare checks the mode.""" """Make sure prepare checks the mode."""
with pytest.raises(configexc.KeybindingError): with pytest.raises(configexc.KeybindingError):
assert keyconf._prepare('x', 'abnormal') assert key_config_stub._prepare('x', 'abnormal')
@pytest.mark.parametrize('commands, expected', [ @pytest.mark.parametrize('commands, expected', [
# Unbinding default key # Unbinding default key
@ -126,7 +121,8 @@ class TestKeyConfig:
# Unbinding unknown key # Unbinding unknown key
({'x': None}, {'a': 'message-info foo', 'b': 'message-info bar'}), ({'x': None}, {'a': 'message-info foo', 'b': 'message-info bar'}),
]) ])
def test_get_bindings_for_and_get_command(self, keyconf, config_stub, def test_get_bindings_for_and_get_command(self, key_config_stub,
config_stub,
commands, expected): commands, expected):
orig_default_bindings = {'normal': {'a': 'message-info foo', orig_default_bindings = {'normal': {'a': 'message-info foo',
'b': 'message-info bar'}, 'b': 'message-info bar'},
@ -139,18 +135,19 @@ class TestKeyConfig:
'register': {}} 'register': {}}
config_stub.val.bindings.default = copy.deepcopy(orig_default_bindings) config_stub.val.bindings.default = copy.deepcopy(orig_default_bindings)
config_stub.val.bindings.commands = {'normal': commands} config_stub.val.bindings.commands = {'normal': commands}
bindings = keyconf.get_bindings_for('normal') bindings = key_config_stub.get_bindings_for('normal')
# Make sure the code creates a copy and doesn't modify the setting # Make sure the code creates a copy and doesn't modify the setting
assert config_stub.val.bindings.default == orig_default_bindings assert config_stub.val.bindings.default == orig_default_bindings
assert bindings == expected assert bindings == expected
for key, command in expected.items(): for key, command in expected.items():
assert keyconf.get_command(key, 'normal') == command assert key_config_stub.get_command(key, 'normal') == command
def test_get_command_unbound(self, keyconf, config_stub, no_bindings): def test_get_command_unbound(self, key_config_stub, config_stub,
no_bindings):
config_stub.val.bindings.default = no_bindings config_stub.val.bindings.default = no_bindings
config_stub.val.bindings.commands = no_bindings config_stub.val.bindings.commands = no_bindings
assert keyconf.get_command('foobar', 'normal') is None assert key_config_stub.get_command('foobar', 'normal') is None
@pytest.mark.parametrize('bindings, expected', [ @pytest.mark.parametrize('bindings, expected', [
# Simple # Simple
@ -166,46 +163,47 @@ class TestKeyConfig:
({'a': 'message-info foo ;; message-info bar'}, ({'a': 'message-info foo ;; message-info bar'},
{'message-info foo': ['a'], 'message-info bar': ['a']}), {'message-info foo': ['a'], 'message-info bar': ['a']}),
]) ])
def test_get_reverse_bindings_for(self, keyconf, config_stub, no_bindings, def test_get_reverse_bindings_for(self, key_config_stub, config_stub,
bindings, expected): no_bindings, bindings, expected):
config_stub.val.bindings.default = no_bindings config_stub.val.bindings.default = no_bindings
config_stub.val.bindings.commands = {'normal': bindings} config_stub.val.bindings.commands = {'normal': bindings}
assert keyconf.get_reverse_bindings_for('normal') == expected assert key_config_stub.get_reverse_bindings_for('normal') == expected
@pytest.mark.parametrize('key', ['a', '<Ctrl-X>', 'b']) @pytest.mark.parametrize('key', ['a', '<Ctrl-X>', 'b'])
def test_bind_duplicate(self, keyconf, config_stub, key): def test_bind_duplicate(self, key_config_stub, config_stub, key):
config_stub.val.bindings.default = {'normal': {'a': 'nop', config_stub.val.bindings.default = {'normal': {'a': 'nop',
'<Ctrl+x>': 'nop'}} '<Ctrl+x>': 'nop'}}
config_stub.val.bindings.commands = {'normal': {'b': 'nop'}} config_stub.val.bindings.commands = {'normal': {'b': 'nop'}}
keyconf.bind(key, 'message-info foo', mode='normal') key_config_stub.bind(key, 'message-info foo', mode='normal')
assert keyconf.get_command(key, 'normal') == 'message-info foo' assert key_config_stub.get_command(key, 'normal') == 'message-info foo'
@pytest.mark.parametrize('mode', ['normal', 'caret']) @pytest.mark.parametrize('mode', ['normal', 'caret'])
@pytest.mark.parametrize('command', [ @pytest.mark.parametrize('command', [
'message-info foo', 'message-info foo',
'nop ;; wq', # https://github.com/qutebrowser/qutebrowser/issues/3002 'nop ;; wq', # https://github.com/qutebrowser/qutebrowser/issues/3002
]) ])
def test_bind(self, keyconf, config_stub, qtbot, no_bindings, def test_bind(self, key_config_stub, config_stub, qtbot, no_bindings,
mode, command): mode, command):
config_stub.val.bindings.default = no_bindings config_stub.val.bindings.default = no_bindings
config_stub.val.bindings.commands = no_bindings config_stub.val.bindings.commands = no_bindings
with qtbot.wait_signal(config_stub.changed): with qtbot.wait_signal(config_stub.changed):
keyconf.bind('a', command, mode=mode) key_config_stub.bind('a', command, mode=mode)
assert config_stub.val.bindings.commands[mode]['a'] == command assert config_stub.val.bindings.commands[mode]['a'] == command
assert keyconf.get_bindings_for(mode)['a'] == command assert key_config_stub.get_bindings_for(mode)['a'] == command
assert keyconf.get_command('a', mode) == command assert key_config_stub.get_command('a', mode) == command
def test_bind_mode_changing(self, keyconf, config_stub, no_bindings): def test_bind_mode_changing(self, key_config_stub, config_stub,
no_bindings):
"""Make sure we can bind to a command which changes the mode. """Make sure we can bind to a command which changes the mode.
https://github.com/qutebrowser/qutebrowser/issues/2989 https://github.com/qutebrowser/qutebrowser/issues/2989
""" """
config_stub.val.bindings.default = no_bindings config_stub.val.bindings.default = no_bindings
config_stub.val.bindings.commands = no_bindings config_stub.val.bindings.commands = no_bindings
keyconf.bind('a', 'set-cmd-text :nop ;; rl-beginning-of-line', key_config_stub.bind('a', 'set-cmd-text :nop ;; rl-beginning-of-line',
mode='normal') mode='normal')
@pytest.mark.parametrize('key, normalized', [ @pytest.mark.parametrize('key, normalized', [
('a', 'a'), # default bindings ('a', 'a'), # default bindings
@ -213,7 +211,8 @@ class TestKeyConfig:
('<Ctrl-X>', '<ctrl+x>') ('<Ctrl-X>', '<ctrl+x>')
]) ])
@pytest.mark.parametrize('mode', ['normal', 'caret', 'prompt']) @pytest.mark.parametrize('mode', ['normal', 'caret', 'prompt'])
def test_unbind(self, keyconf, config_stub, qtbot, key, normalized, mode): def test_unbind(self, key_config_stub, config_stub, qtbot,
key, normalized, mode):
default_bindings = { default_bindings = {
'normal': {'a': 'nop', '<ctrl+x>': 'nop'}, 'normal': {'a': 'nop', '<ctrl+x>': 'nop'},
'caret': {'a': 'nop', '<ctrl+x>': 'nop'}, 'caret': {'a': 'nop', '<ctrl+x>': 'nop'},
@ -228,9 +227,9 @@ class TestKeyConfig:
} }
with qtbot.wait_signal(config_stub.changed): with qtbot.wait_signal(config_stub.changed):
keyconf.unbind(key, mode=mode) key_config_stub.unbind(key, mode=mode)
assert keyconf.get_command(key, mode) is None assert key_config_stub.get_command(key, mode) is None
mode_bindings = config_stub.val.bindings.commands[mode] mode_bindings = config_stub.val.bindings.commands[mode]
if key == 'b' and mode != 'prompt': if key == 'b' and mode != 'prompt':
@ -241,13 +240,13 @@ class TestKeyConfig:
assert default_bindings[mode] == old_default_bindings[mode] assert default_bindings[mode] == old_default_bindings[mode]
assert mode_bindings[normalized] is None assert mode_bindings[normalized] is None
def test_unbind_unbound(self, keyconf, config_stub, no_bindings): def test_unbind_unbound(self, key_config_stub, config_stub, no_bindings):
"""Try unbinding a key which is not bound.""" """Try unbinding a key which is not bound."""
config_stub.val.bindings.default = no_bindings config_stub.val.bindings.default = no_bindings
config_stub.val.bindings.commands = no_bindings config_stub.val.bindings.commands = no_bindings
with pytest.raises(configexc.KeybindingError, with pytest.raises(configexc.KeybindingError,
match="Can't find binding 'foobar' in normal mode"): match="Can't find binding 'foobar' in normal mode"):
keyconf.unbind('foobar', mode='normal') key_config_stub.unbind('foobar', mode='normal')
class TestConfig: class TestConfig:

View File

@ -31,8 +31,8 @@ from qutebrowser.misc import objects
@pytest.fixture @pytest.fixture
def commands(config_stub, keyconf): def commands(config_stub, key_config_stub):
return configcommands.ConfigCommands(config_stub, keyconf) return configcommands.ConfigCommands(config_stub, key_config_stub)
class TestSet: class TestSet:
@ -263,7 +263,8 @@ class TestSource:
"""Test :config-source.""" """Test :config-source."""
pytestmark = pytest.mark.usefixtures('config_tmpdir', 'data_tmpdir') pytestmark = pytest.mark.usefixtures('config_tmpdir', 'data_tmpdir',
'config_stub', 'key_config_stub')
@pytest.mark.parametrize('use_default_dir', [True, False]) @pytest.mark.parametrize('use_default_dir', [True, False])
@pytest.mark.parametrize('clear', [True, False]) @pytest.mark.parametrize('clear', [True, False])
@ -302,14 +303,17 @@ class TestEdit:
"""Tests for :config-edit.""" """Tests for :config-edit."""
def test_no_source(self, commands, mocker, config_tmpdir): pytestmark = pytest.mark.usefixtures('config_tmpdir', 'data_tmpdir',
'config_stub', 'key_config_stub')
def test_no_source(self, commands, mocker):
mock = mocker.patch('qutebrowser.config.configcommands.editor.' mock = mocker.patch('qutebrowser.config.configcommands.editor.'
'ExternalEditor._start_editor', autospec=True) 'ExternalEditor._start_editor', autospec=True)
commands.config_edit(no_source=True) commands.config_edit(no_source=True)
mock.assert_called_once_with(unittest.mock.ANY) mock.assert_called_once_with(unittest.mock.ANY)
@pytest.fixture @pytest.fixture
def patch_editor(self, mocker, config_tmpdir, data_tmpdir): def patch_editor(self, mocker):
"""Write a config.py file.""" """Write a config.py file."""
def do_patch(text): def do_patch(text):
def _write_file(editor_self): def _write_file(editor_self):
@ -345,6 +349,55 @@ class TestEdit:
assert msg.text == expected assert msg.text == expected
class TestWritePy:
"""Tests for :config-write-py."""
def test_custom(self, commands, config_stub, key_config_stub, tmpdir):
confpy = tmpdir / 'config.py'
config_stub.val.content.javascript.enabled = True
key_config_stub.bind(',x', 'message-info foo', mode='normal')
commands.config_write_py(str(confpy))
lines = confpy.read_text('utf-8').splitlines()
assert "c.content.javascript.enabled = True" in lines
assert "config.bind(',x', 'message-info foo')" in lines
def test_defaults(self, commands, tmpdir):
confpy = tmpdir / 'config.py'
commands.config_write_py(str(confpy), defaults=True)
lines = confpy.read_text('utf-8').splitlines()
assert "# c.content.javascript.enabled = True" in lines
assert "# config.bind('H', 'back')" in lines
def test_default_location(self, commands, config_tmpdir):
confpy = config_tmpdir / 'config.py'
commands.config_write_py()
lines = confpy.read_text('utf-8').splitlines()
assert '# Autogenerated config.py' in lines
def test_existing_file(self, commands, tmpdir):
confpy = tmpdir / 'config.py'
confpy.ensure()
with pytest.raises(cmdexc.CommandError) as excinfo:
commands.config_write_py(str(confpy))
expected = " already exists - use --force to overwrite!"
assert str(excinfo.value).endswith(expected)
def test_existing_file_force(self, commands, tmpdir):
confpy = tmpdir / 'config.py'
confpy.ensure()
commands.config_write_py(str(confpy), force=True)
lines = confpy.read_text('utf-8').splitlines()
assert '# Autogenerated config.py' in lines
class TestBind: class TestBind:
"""Tests for :bind and :unbind.""" """Tests for :bind and :unbind."""
@ -355,14 +408,15 @@ class TestBind:
return {'normal': {}} return {'normal': {}}
@pytest.mark.parametrize('command', ['nop', 'nope']) @pytest.mark.parametrize('command', ['nop', 'nope'])
def test_bind(self, commands, config_stub, no_bindings, keyconf, command): def test_bind(self, commands, config_stub, no_bindings, key_config_stub,
command):
"""Simple :bind test (and aliases).""" """Simple :bind test (and aliases)."""
config_stub.val.aliases = {'nope': 'nop'} config_stub.val.aliases = {'nope': 'nop'}
config_stub.val.bindings.default = no_bindings config_stub.val.bindings.default = no_bindings
config_stub.val.bindings.commands = no_bindings config_stub.val.bindings.commands = no_bindings
commands.bind('a', command) commands.bind('a', command)
assert keyconf.get_command('a', 'normal') == command assert key_config_stub.get_command('a', 'normal') == command
yaml_bindings = config_stub._yaml['bindings.commands']['normal'] yaml_bindings = config_stub._yaml['bindings.commands']['normal']
assert yaml_bindings['a'] == command assert yaml_bindings['a'] == command
@ -413,7 +467,7 @@ class TestBind:
commands.bind('a', 'nop', mode='wrongmode') commands.bind('a', 'nop', mode='wrongmode')
@pytest.mark.parametrize('key', ['a', 'b', '<Ctrl-X>']) @pytest.mark.parametrize('key', ['a', 'b', '<Ctrl-X>'])
def test_bind_duplicate(self, commands, config_stub, keyconf, key): def test_bind_duplicate(self, commands, config_stub, key_config_stub, key):
"""Run ':bind' with a key which already has been bound.'. """Run ':bind' with a key which already has been bound.'.
Also tests for https://github.com/qutebrowser/qutebrowser/issues/1544 Also tests for https://github.com/qutebrowser/qutebrowser/issues/1544
@ -426,7 +480,7 @@ class TestBind:
} }
commands.bind(key, 'message-info foo', mode='normal') commands.bind(key, 'message-info foo', mode='normal')
assert keyconf.get_command(key, 'normal') == 'message-info foo' assert key_config_stub.get_command(key, 'normal') == 'message-info foo'
def test_bind_none(self, commands, config_stub): def test_bind_none(self, commands, config_stub):
config_stub.val.bindings.commands = None config_stub.val.bindings.commands = None
@ -442,7 +496,8 @@ class TestBind:
('c', 'c'), # :bind then :unbind ('c', 'c'), # :bind then :unbind
('<Ctrl-X>', '<ctrl+x>') # normalized special binding ('<Ctrl-X>', '<ctrl+x>') # normalized special binding
]) ])
def test_unbind(self, commands, keyconf, config_stub, key, normalized): def test_unbind(self, commands, key_config_stub, config_stub,
key, normalized):
config_stub.val.bindings.default = { config_stub.val.bindings.default = {
'normal': {'a': 'nop', '<ctrl+x>': 'nop'}, 'normal': {'a': 'nop', '<ctrl+x>': 'nop'},
'caret': {'a': 'nop', '<ctrl+x>': 'nop'}, 'caret': {'a': 'nop', '<ctrl+x>': 'nop'},
@ -456,7 +511,7 @@ class TestBind:
commands.bind(key, 'nop') commands.bind(key, 'nop')
commands.unbind(key) commands.unbind(key)
assert keyconf.get_command(key, 'normal') is None assert key_config_stub.get_command(key, 'normal') is None
yaml_bindings = config_stub._yaml['bindings.commands']['normal'] yaml_bindings = config_stub._yaml['bindings.commands']['normal']
if key in 'bc': if key in 'bc':

View File

@ -21,11 +21,13 @@
import os import os
import sys import sys
import unittest.mock import unittest.mock
import textwrap
import pytest import pytest
from qutebrowser.config import config, configfiles, configexc, configdata from qutebrowser.config import (config, configfiles, configexc, configdata,
from qutebrowser.utils import utils configtypes)
from qutebrowser.utils import utils, usertypes
from PyQt5.QtCore import QSettings from PyQt5.QtCore import QSettings
@ -271,14 +273,15 @@ class ConfPy:
'qbmodule.run(config)') 'qbmodule.run(config)')
@pytest.fixture
def confpy(tmpdir, config_tmpdir, data_tmpdir, config_stub, key_config_stub):
return ConfPy(tmpdir)
class TestConfigPyModules: class TestConfigPyModules:
pytestmark = pytest.mark.usefixtures('config_stub', 'key_config_stub') pytestmark = pytest.mark.usefixtures('config_stub', 'key_config_stub')
@pytest.fixture
def confpy(self, tmpdir, config_tmpdir, data_tmpdir):
return ConfPy(tmpdir)
@pytest.fixture @pytest.fixture
def qbmodulepy(self, tmpdir): def qbmodulepy(self, tmpdir):
return ConfPy(tmpdir, filename="qbmodule.py") return ConfPy(tmpdir, filename="qbmodule.py")
@ -340,10 +343,6 @@ class TestConfigPy:
pytestmark = pytest.mark.usefixtures('config_stub', 'key_config_stub') pytestmark = pytest.mark.usefixtures('config_stub', 'key_config_stub')
@pytest.fixture
def confpy(self, tmpdir, config_tmpdir, data_tmpdir):
return ConfPy(tmpdir)
def test_assertions(self, confpy): def test_assertions(self, confpy):
"""Make sure assertions in config.py work for these tests.""" """Make sure assertions in config.py work for these tests."""
confpy.write('assert False') confpy.write('assert False')
@ -527,6 +526,153 @@ class TestConfigPy:
assert error.traceback is not None assert error.traceback is not None
class TestConfigPyWriter:
def test_output(self):
desc = ("This is an option description.\n\n"
"Nullam eu ante vel est convallis dignissim. Fusce suscipit, "
"wisi nec facilisis facilisis, est dui fermentum leo, quis "
"tempor ligula erat quis odio.")
opt = configdata.Option(
name='opt', typ=configtypes.Int(), default='def',
backends=[usertypes.Backend.QtWebEngine], raw_backends=None,
description=desc)
options = [(opt, 'val')]
bindings = {'normal': {',x': 'message-info normal'},
'caret': {',y': 'message-info caret'}}
writer = configfiles.ConfigPyWriter(options, bindings, commented=False)
text = '\n'.join(writer._gen_lines())
assert text == textwrap.dedent("""
# Autogenerated config.py
# Documentation:
# qute://help/configuring.html
# qute://help/settings.html
# Uncomment this to still load settings configured via autoconfig.yml
# config.load_autoconfig()
# This is an option description. Nullam eu ante vel est convallis
# dignissim. Fusce suscipit, wisi nec facilisis facilisis, est dui
# fermentum leo, quis tempor ligula erat quis odio.
# Type: Int
c.opt = 'val'
# Bindings for normal mode
config.bind(',x', 'message-info normal')
# Bindings for caret mode
config.bind(',y', 'message-info caret', mode='caret')
""").strip()
def test_binding_options_hidden(self):
opt1 = configdata.DATA['bindings.default']
opt2 = configdata.DATA['bindings.commands']
options = [(opt1, {'normal': {'x': 'message-info x'}}),
(opt2, {})]
writer = configfiles.ConfigPyWriter(options, bindings={},
commented=False)
text = '\n'.join(writer._gen_lines())
assert 'bindings.default' not in text
assert 'bindings.commands' not in text
def test_commented(self):
opt = configdata.Option(
name='opt', typ=configtypes.Int(), default='def',
backends=[usertypes.Backend.QtWebEngine], raw_backends=None,
description='Hello World')
options = [(opt, 'val')]
bindings = {'normal': {',x': 'message-info normal'},
'caret': {',y': 'message-info caret'}}
writer = configfiles.ConfigPyWriter(options, bindings, commented=True)
lines = list(writer._gen_lines())
assert "## Autogenerated config.py" in lines
assert "# config.load_autoconfig()" in lines
assert "# c.opt = 'val'" in lines
assert "## Bindings for normal mode" in lines
assert "# config.bind(',x', 'message-info normal')" in lines
caret_bind = ("# config.bind(',y', 'message-info caret', "
"mode='caret')")
assert caret_bind in lines
def test_valid_values(self):
opt1 = configdata.Option(
name='opt1', typ=configtypes.BoolAsk(), default='ask',
backends=[usertypes.Backend.QtWebEngine], raw_backends=None,
description='Hello World')
opt2 = configdata.Option(
name='opt2', typ=configtypes.ColorSystem(), default='rgb',
backends=[usertypes.Backend.QtWebEngine], raw_backends=None,
description='All colors are beautiful!')
options = [(opt1, 'ask'), (opt2, 'rgb')]
writer = configfiles.ConfigPyWriter(options, bindings={},
commented=False)
text = '\n'.join(writer._gen_lines())
expected = textwrap.dedent("""
# Hello World
# Type: BoolAsk
# Valid values:
# - true
# - false
# - ask
c.opt1 = 'ask'
# All colors are beautiful!
# Type: ColorSystem
# Valid values:
# - 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.
c.opt2 = 'rgb'
""")
assert expected in text
def test_empty(self):
writer = configfiles.ConfigPyWriter(options=[], bindings={},
commented=False)
text = '\n'.join(writer._gen_lines())
expected = textwrap.dedent("""
# Autogenerated config.py
# Documentation:
# qute://help/configuring.html
# qute://help/settings.html
# Uncomment this to still load settings configured via autoconfig.yml
# config.load_autoconfig()
""").lstrip()
assert text == expected
def test_write(self, tmpdir):
pyfile = tmpdir / 'config.py'
writer = configfiles.ConfigPyWriter(options=[], bindings={},
commented=False)
writer.write(str(pyfile))
lines = pyfile.read_text('utf-8').splitlines()
assert '# Autogenerated config.py' in lines
def test_defaults_work(self, confpy):
"""Get a config.py with default values and run it."""
options = [(opt, opt.default)
for _name, opt in sorted(configdata.DATA.items())]
bindings = dict(configdata.DATA['bindings.default'].default)
writer = configfiles.ConfigPyWriter(options, bindings, commented=False)
writer.write(confpy.filename)
try:
configfiles.read_config_py(confpy.filename)
except configexc.ConfigFileErrors as exc:
# Make sure no other errors happened
for error in exc.errors:
assert isinstance(error.exception, configexc.BackendError)
@pytest.fixture @pytest.fixture
def init_patch(qapp, fake_save_manager, config_tmpdir, data_tmpdir, def init_patch(qapp, fake_save_manager, config_tmpdir, data_tmpdir,
config_stub, monkeypatch): config_stub, monkeypatch):