Merge branch 'config_command'

Conflicts:
	beets/ui/commands.py
	docs/reference/config.rst
	test/_common.py
This commit is contained in:
Thomas Scholtes 2014-02-28 16:50:06 +01:00
commit c345df9155
6 changed files with 225 additions and 8 deletions

View file

@ -22,6 +22,8 @@ import os
import time
import itertools
import codecs
import yaml
import platform
import beets
from beets import ui
@ -34,6 +36,7 @@ from beets import importer
from beets import util
from beets.util import syspath, normpath, ancestry, displayable_path
from beets.util.functemplate import Template
from beets.util.confit import ConfigTypeError
from beets import library
from beets import config
@ -1245,3 +1248,58 @@ def write_func(lib, opts, args):
write_items(lib, decargs(args), opts.pretend)
write_cmd.func = write_func
default_commands.append(write_cmd)
config_cmd = ui.Subcommand('config', help='show or edit the user configuration')
config_cmd.parser.add_option('-p', '--paths', action='store_true',
help='show files that configuration was loaded from')
config_cmd.parser.add_option('-e', '--edit', action='store_true',
help='edit user configuration with $EDITOR')
config_cmd.parser.add_option('-d', '--defaults', action='store_true',
help='include the default configuration')
def _config_get(view):
try:
keys = view.keys()
except ConfigTypeError:
return view.get()
else:
return dict((key, _config_get(view[key])) for key in view.keys())
def config_func(lib, opts, args):
# Make sure lazy configuration is loaded
config.resolve()
if not opts.defaults:
# Remove default source
config.sources = [source for source in config.sources if not source.default]
if opts.paths:
for source in config.sources:
if source.filename:
print(source.filename)
elif opts.edit:
path = config.user_config_path()
if 'EDITOR' in os.environ:
editor = os.environ['EDITOR']
args = [editor, editor, path]
elif platform.system() == 'Darwin':
args = ['open', 'open', '-n', path]
elif platform.system() == 'Windows':
# On windows we can execute arbitrary files. The os will
# take care of starting an appropriate application
args = [path, path]
else:
# Assume Unix
args = ['xdg-open', 'xdg-open', path]
try:
os.execlp(*args)
except OSError:
raise ui.UserError("Could not edit configuration. Please"
"set the EDITOR environment variable.")
else:
config_dict = _config_get(config)
print(yaml.safe_dump(config_dict, default_flow_style=False))
config_cmd.func = config_func
default_commands.append(config_cmd)

View file

@ -629,12 +629,19 @@ class Configuration(RootView):
if read:
self.read()
def user_config_path(self):
"""Points to the location of the user configuration.
The file may not exist.
"""
return os.path.join(self.config_dir(), CONFIG_FILENAME)
def _add_user_source(self):
"""Add the configuration options from the YAML file in the
user's configuration directory (given by `config_dir`) if it
exists.
"""
filename = os.path.join(self.config_dir(), CONFIG_FILENAME)
filename = self.user_config_path()
if os.path.isfile(filename):
self.add(ConfigSource(load_yaml(filename) or {}, filename))
@ -734,3 +741,9 @@ class LazyConfig(Configuration):
# Buffer additions to beginning.
self._lazy_prefix[:0] = self.sources
del self.sources[:]
def clear(self):
"""Remove all sources from this configuration."""
del self.sources[:]
self._lazy_suffix = []
self._lazy_prefix = []

View file

@ -304,6 +304,30 @@ fields
Show the item and album metadata fields available for use in :doc:`query` and
:doc:`pathformat`. Includes any template fields provided by plugins.
.. _config-cmd:
config
``````
::
beet config [-pd]
beet config -e
Show or edit the user configuration. Without any options this command
prints a YAML representation of the current user configuration. If the
``--path`` option is given it instead prints the aboslute path to the
user configuration file. Note that this path may not exist. The
``--default`` option can be set with or without the ``--path`` option.
If it is set it also load the default configuration from the beets
package. Showing the configuration or the paths also works if an
additional ``--config`` option is given on the command line.
If the ``--edit`` option is given, beets will open the user configuration
in an editor. If the ``EDITOR`` environment variable is set it uses that
command to start the editor. Otherwise, beets tries the ``open`` command on
OSX, the ``xdg-open`` command on Unixes and will try to execute the
configuration file directly on Windows.
.. _global-flags:
Global Flags

View file

@ -3,13 +3,10 @@ Configuration
Beets has an extensive configuration system that lets you customize nearly
every aspect of its operation. To configure beets, you'll edit a file called
``config.yaml``. The location of this file depends on your OS:
* On Unix-like OSes, you want ``~/.config/beets/config.yaml``.
* On Windows, use ``%APPDATA%\beets\config.yaml``. This is usually in a
directory like ``C:\Users\You\AppData\Roaming``.
* On OS X, you can use either the Unix location or ``~/Library/Application
Support/beets/config.yaml``.
``config.yaml``. The ``beets config -p`` command shows you where beets
expects its configuration file to be placed. You can start editing it
right away by running ``beets config -e``. This will create the file if
it not already exists and open it in your default text editor.
It is also possible to customize the location of the configuration file and
even use multiple layers of configuration. See `Configuration Location`_,

View file

@ -272,3 +272,13 @@ def platform_posix():
yield
finally:
os.path = old_path
@contextmanager
def system_mock(name):
import platform
old_system = platform.system
platform.system = lambda: name
try:
yield
finally:
platform.system = old_system

115
test/test_config_command.py Normal file
View file

@ -0,0 +1,115 @@
import os
import yaml
from beets import ui
from beets import config
import _common
class ConfigCommandTest(_common.TestCase):
def setUp(self):
super(ConfigCommandTest, self).setUp()
self.io.install()
if 'EDITOR' in os.environ:
del os.environ['EDITOR']
os.environ['BEETSDIR'] = self.temp_dir
self.config_path = os.path.join(self.temp_dir, 'config.yaml')
with open(self.config_path, 'w') as file:
file.write('library: lib\n')
file.write('option: value')
self.cli_config_path = os.path.join(self.temp_dir, 'cli_config.yaml')
with open(self.cli_config_path, 'w') as file:
file.write('option: cli overwrite')
config.clear()
config._materialized = False
def tearDown(self):
super(ConfigCommandTest, self).tearDown()
self.execlp_restore()
def test_show_user_config(self):
ui._raw_main(['config'])
output = yaml.load(self.io.getoutput())
self.assertEqual(output['option'], 'value')
def test_show_user_config_with_defaults(self):
ui._raw_main(['config', '-d'])
output = yaml.load(self.io.getoutput())
self.assertEqual(output['option'], 'value')
self.assertEqual(output['library'], 'lib')
self.assertEqual(output['import']['timid'], False)
def test_show_user_config_with_cli(self):
ui._raw_main(['--config', self.cli_config_path, 'config'])
output = yaml.load(self.io.getoutput())
self.assertEqual(output['library'], 'lib')
self.assertEqual(output['option'], 'cli overwrite')
def test_config_paths(self):
ui._raw_main(['config', '-p'])
paths = self.io.getoutput().split('\n')
self.assertEqual(len(paths), 2)
self.assertEqual(paths[0], self.config_path)
def test_config_paths_with_cli(self):
ui._raw_main(['--config', self.cli_config_path, 'config', '-p'])
paths = self.io.getoutput().split('\n')
self.assertEqual(len(paths), 3)
self.assertEqual(paths[0], self.cli_config_path)
def test_edit_config_with_editor_env(self):
self.execlp_stub()
os.environ['EDITOR'] = 'myeditor'
ui._raw_main(['config', '-e'])
self.assertEqual(self._execlp_call, ['myeditor', self.config_path])
def test_edit_config_with_open(self):
self.execlp_stub()
with _common.system_mock('Darwin'):
ui._raw_main(['config', '-e'])
self.assertEqual(self._execlp_call, ['open', '-n', self.config_path])
def test_edit_config_with_xdg_open(self):
self.execlp_stub()
with _common.system_mock('Linux'):
ui._raw_main(['config', '-e'])
self.assertEqual(self._execlp_call, ['xdg-open', self.config_path])
def test_edit_config_with_windows_exec(self):
self.execlp_stub()
with _common.system_mock('Windows'):
ui._raw_main(['config', '-e'])
self.assertEqual(self._execlp_call, [self.config_path])
def test_config_editor_not_found(self):
def raise_os_error(*args):
raise OSError
os.execlp = raise_os_error
with self.assertRaises(ui.UserError) as user_error:
ui._raw_main(['config', '-e'])
self.assertIn('Could not edit configuration',
str(user_error.exception.args[0]))
def execlp_stub(self):
self._execlp_call = None
def _execlp_stub(file, *args):
self._execlp_call = [file] + list(args[1:])
self._orig_execlp = os.execlp
os.execlp = _execlp_stub
def execlp_restore(self):
if hasattr(self, '_orig_execlp'):
os.execlp = self._orig_execlp