mirror of
https://github.com/beetbox/beets.git
synced 2026-02-27 09:41:51 +01:00
Merge branch 'remove_cascading_config' of https://github.com/geigerzaehler/beets into geigerzaehler-remove_cascading_config
This commit is contained in:
commit
7b544a2205
4 changed files with 233 additions and 83 deletions
|
|
@ -332,12 +332,11 @@ class ConfigView(object):
|
|||
return value
|
||||
|
||||
def as_filename(self):
|
||||
"""Get a string as a normalized filename, made absolute and with
|
||||
tilde expanded. If the value comes from a default source, the
|
||||
path is considered relative to the application's config
|
||||
directory. If it comes from another file source, the filename is
|
||||
expanded as if it were relative to that directory. Otherwise, it
|
||||
is relative to the current working directory.
|
||||
"""Get a string as a normalized as an absolute path.
|
||||
|
||||
If the value is a relative path it is expanded relative to the
|
||||
root configuration's ``config_dir()``. Tilde is also expaned
|
||||
with ``os.path.expanduser()``.
|
||||
"""
|
||||
path, source = self.first()
|
||||
if not isinstance(path, BASESTRING):
|
||||
|
|
@ -346,14 +345,10 @@ class ConfigView(object):
|
|||
))
|
||||
path = os.path.expanduser(STRING(path))
|
||||
|
||||
if source.default:
|
||||
if not os.path.isabs(path):
|
||||
# From defaults: relative to the app's directory.
|
||||
path = os.path.join(self.root().config_dir(), path)
|
||||
|
||||
elif source.filename is not None:
|
||||
# Relative to source filename's directory.
|
||||
path = os.path.join(os.path.dirname(source.filename), path)
|
||||
|
||||
return os.path.abspath(path)
|
||||
|
||||
def as_choice(self, choices):
|
||||
|
|
@ -508,20 +503,24 @@ def _package_path(name):
|
|||
return os.path.dirname(os.path.abspath(filepath))
|
||||
|
||||
def config_dirs():
|
||||
"""Returns a list of user configuration directories to be searched.
|
||||
"""Returns a list of candidates for user configuration directories
|
||||
on the system.
|
||||
"""
|
||||
paths = []
|
||||
if platform.system() == 'Darwin':
|
||||
paths = [UNIX_DIR_FALLBACK, MAC_DIR]
|
||||
paths.append(UNIX_DIR_FALLBACK)
|
||||
paths.append(MAC_DIR)
|
||||
if UNIX_DIR_VAR in os.environ:
|
||||
paths.append(os.environ[UNIX_DIR_VAR])
|
||||
elif platform.system() == 'Windows':
|
||||
paths.append(WINDOWS_DIR_FALLBACK)
|
||||
if WINDOWS_DIR_VAR in os.environ:
|
||||
paths = [os.environ[WINDOWS_DIR_VAR]]
|
||||
else:
|
||||
paths = [WINDOWS_DIR_FALLBACK]
|
||||
paths.append(os.environ[WINDOWS_DIR_VAR])
|
||||
else:
|
||||
# Assume Unix.
|
||||
paths = [UNIX_DIR_FALLBACK]
|
||||
paths.append(UNIX_DIR_FALLBACK)
|
||||
if UNIX_DIR_VAR in os.environ:
|
||||
paths.insert(0, os.environ[UNIX_DIR_VAR])
|
||||
paths.append(os.environ[UNIX_DIR_VAR])
|
||||
|
||||
# Expand and deduplicate paths.
|
||||
out = []
|
||||
|
|
@ -622,38 +621,24 @@ class Configuration(RootView):
|
|||
if read:
|
||||
self.read()
|
||||
|
||||
def _search_dirs(self):
|
||||
"""Yield directories that will be searched for configuration
|
||||
files for this application.
|
||||
def _add_user_source(self):
|
||||
"""Add ``ConfigSource`` for the configuration file in
|
||||
``config_dir()`` if it exists.
|
||||
"""
|
||||
# Application's environment variable.
|
||||
if self._env_var in os.environ:
|
||||
path = os.environ[self._env_var]
|
||||
yield os.path.abspath(os.path.expanduser(path))
|
||||
filename = os.path.join(self.config_dir(), CONFIG_FILENAME)
|
||||
if os.path.isfile(filename):
|
||||
self.add(ConfigSource(load_yaml(filename) or {}, filename))
|
||||
|
||||
# Standard configuration directories.
|
||||
for confdir in config_dirs():
|
||||
yield os.path.join(confdir, self.appname)
|
||||
|
||||
def _user_sources(self):
|
||||
"""Generate `ConfigSource` objects for each user configuration
|
||||
file in the program's search directories.
|
||||
"""
|
||||
for appdir in self._search_dirs():
|
||||
filename = os.path.join(appdir, CONFIG_FILENAME)
|
||||
if os.path.isfile(filename):
|
||||
yield ConfigSource(load_yaml(filename) or {}, filename)
|
||||
|
||||
def _default_source(self):
|
||||
"""Return the default-value source for this program or `None` if
|
||||
it does not exist.
|
||||
def _add_default_source(self):
|
||||
"""Adds ``ConfigSource`` for the default configuration of the
|
||||
package if it exists
|
||||
"""
|
||||
if self.modname:
|
||||
pkg_path = _package_path(self.modname)
|
||||
if pkg_path:
|
||||
filename = os.path.join(pkg_path, DEFAULT_FILENAME)
|
||||
if os.path.isfile(filename):
|
||||
return ConfigSource(load_yaml(filename), filename, True)
|
||||
self.add(ConfigSource(load_yaml(filename), filename, True))
|
||||
|
||||
def read(self, user=True, defaults=True):
|
||||
"""Find and read the files for this configuration and set them
|
||||
|
|
@ -662,27 +647,32 @@ class Configuration(RootView):
|
|||
set `user` or `defaults` to `False`.
|
||||
"""
|
||||
if user:
|
||||
for source in self._user_sources():
|
||||
self.add(source)
|
||||
self._add_user_source()
|
||||
if defaults:
|
||||
source = self._default_source()
|
||||
if source:
|
||||
self.add(source)
|
||||
self._add_default_source()
|
||||
|
||||
def config_dir(self):
|
||||
"""Get the path to the directory containing the highest-priority
|
||||
user configuration. If no user configuration is present, create a
|
||||
suitable directory before returning it.
|
||||
"""Path of the user configuration directory
|
||||
|
||||
If the BEETSDIR environment variable is set it returns its
|
||||
value. Otherwise look for existing ``beets/config.yaml`` in
|
||||
``config_dirs()`` and returns it. If none of the files exists it
|
||||
return the last path searched and makes sure the directory exists.
|
||||
"""
|
||||
dirs = list(self._search_dirs())
|
||||
if self._env_var in os.environ:
|
||||
path = os.environ[self._env_var]
|
||||
return os.path.abspath(os.path.expanduser(path))
|
||||
|
||||
dirs = []
|
||||
for confdir in config_dirs():
|
||||
dirs.append(os.path.join(confdir, self.appname))
|
||||
|
||||
# First, look for an existent configuration file.
|
||||
for appdir in dirs:
|
||||
if os.path.isfile(os.path.join(appdir, CONFIG_FILENAME)):
|
||||
return appdir
|
||||
|
||||
# As a fallback, create the first-listed directory name.
|
||||
appdir = dirs[0]
|
||||
# Fallback to the last path
|
||||
if not os.path.isdir(appdir):
|
||||
os.makedirs(appdir)
|
||||
return appdir
|
||||
|
|
|
|||
|
|
@ -317,7 +317,10 @@ import ...``.
|
|||
* ``-d DIRECTORY``: specify the library root directory.
|
||||
* ``-v``: verbose mode; prints out a deluge of debugging information. Please use
|
||||
this flag when reporting bugs.
|
||||
* ``-c FILE``: read a specified YAML configuration file.
|
||||
* ``-c FILE``: read a specified YAML :doc:`configuration file <config>`.
|
||||
|
||||
Beets also uses the ``BEETSDIR`` environment variable to look for
|
||||
configuration and data.
|
||||
|
||||
.. only:: man
|
||||
|
||||
|
|
|
|||
|
|
@ -5,17 +5,14 @@ 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 (including OS X), you want ``~/.config/beets/config.yaml``.
|
||||
* 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 also use ``~/Library/Application Support/beets/config.yaml``
|
||||
if you prefer that over the Unix-like ``~/.config``.
|
||||
* If you prefer a different location, set the ``BEETSDIR`` environment
|
||||
variable to a path; beets will then look for a ``config.yaml`` in that
|
||||
directory.
|
||||
* Or specify an *additional* configuration file to load using the ``--config
|
||||
/path/to/file`` option on the command line. The options will be combined
|
||||
with any options already specified your default config file.
|
||||
* On OS X, it is ``~/Library/Application Support/beets/config.yaml``.
|
||||
|
||||
It is also possible to customize the location of the configuration file
|
||||
and even use multiple layers of configuration. Just have a look at
|
||||
`Configuration Location`_.
|
||||
|
||||
The config file uses `YAML`_ syntax. You can use the full power of YAML, but
|
||||
most configuration options are simple key/value pairs. This means your config
|
||||
|
|
@ -554,6 +551,64 @@ fact, just shorthand for the explicit queries ``singleton:true`` and
|
|||
``comp:true``. In contrast, ``default`` is special and has no query equivalent:
|
||||
the ``default`` format is only used if no queries match.
|
||||
|
||||
|
||||
Configuration Location
|
||||
----------------------
|
||||
|
||||
Beets has three layers of configuration; each overwriting the previous
|
||||
one.
|
||||
|
||||
The first layer is the *default configuration*. It comes with your beets
|
||||
distribution and cannot be changed. The second configuration layer is
|
||||
the *user configuration*. Here you can customize the configuration to
|
||||
use when running ``beet`` on your command line.
|
||||
|
||||
The path for the user configuration is given by
|
||||
``$BEETSDIR/config.yaml``. Here ``BEETSDIR``, is the directory beets
|
||||
uses to store its application specific data and resolve relative paths.
|
||||
By default, ``BEETSDIR`` is determined by your system's convention for
|
||||
storing application configuration, but may be set by the ``BEETSDIR``
|
||||
environment variable. This allows you to manage mutliple beets
|
||||
libraries with separate configurations. To be more precise, the
|
||||
following algorithm is used to determine ``BEETSDIR``.
|
||||
|
||||
1. If the ``BEETSDIR`` environment variable is set, then use it and
|
||||
stop.
|
||||
|
||||
2. Otherwise, generate a platform-dependent list of directories to
|
||||
search.
|
||||
|
||||
- On Windows: ``~\AppData\Roaming\beets`` and then
|
||||
``%APPDATA%\beets``, if the environment variable is set
|
||||
|
||||
- On non-Mac Unixes: ``~/.config/beets`` and then
|
||||
``$XDG_CONFIG_DIR/beets``, if the environment variable is set
|
||||
|
||||
- On OS X: ``~/.config/beets``, then
|
||||
``~/Library/Application Support/beets``, and finally
|
||||
``$XDG_CONFIG_DIR/beets``, if the environment variable is set
|
||||
|
||||
3. Look in each directory in turn for a ``config.yaml``. Set
|
||||
``BEETSDIR`` to the *first* directory that contains this file. If no
|
||||
directory is found containing ``config.yaml``, then use the *last*
|
||||
directory in the list.
|
||||
|
||||
|
||||
Finally, the ``--config CONFIGFILE`` command line option serves as the
|
||||
third layer. ``CONFIGFILE`` is the path of a YAML file that contains
|
||||
additional configuration overwriting the user configuration. This is
|
||||
helpful, for example, if you have different strategies for importing
|
||||
files, each with its own set of importer configuration.
|
||||
|
||||
In addition some command line options overwrite configuration values.
|
||||
For example the command ::
|
||||
|
||||
$ beets --library /path/to/lib import --timid /path/to/import
|
||||
|
||||
uses the ``library`` and ``importer.timid`` values from the command line
|
||||
instead of the user configuration.
|
||||
|
||||
|
||||
.. _config-example:
|
||||
|
||||
Example
|
||||
|
|
|
|||
142
test/test_ui.py
142
test/test_ui.py
|
|
@ -471,9 +471,15 @@ class ConfigTest(_common.TestCase):
|
|||
self.test_cmd = self._make_test_cmd()
|
||||
commands.default_commands.append(self.test_cmd)
|
||||
|
||||
config_dir = os.path.join(self.temp_dir, '.config', 'beets')
|
||||
os.makedirs(config_dir)
|
||||
self.user_config_path = os.path.join(config_dir, 'config.yaml')
|
||||
# Default user configuration
|
||||
self.user_config_dir = os.path.join(self.temp_dir, '.config', 'beets')
|
||||
os.makedirs(self.user_config_dir)
|
||||
self.user_config_path = os.path.join(self.user_config_dir,
|
||||
'config.yaml')
|
||||
|
||||
# Custom BEETSDIR
|
||||
self.beetsdir = os.path.join(self.temp_dir, 'beetsdir')
|
||||
os.makedirs(self.beetsdir)
|
||||
|
||||
self._reset_config()
|
||||
|
||||
|
|
@ -567,27 +573,10 @@ class ConfigTest(_common.TestCase):
|
|||
ui._raw_main(['--config', config_path, 'test'])
|
||||
self.assertEqual(config['anoption'].get(), 'value')
|
||||
|
||||
def test_beetsdir_config_file_overwrites_defaults(self):
|
||||
with open(self.user_config_path, 'w') as file:
|
||||
file.write('anoption: value')
|
||||
|
||||
env_config_path = os.path.join(self.temp_dir, 'config.yaml')
|
||||
os.environ['BEETSDIR'] = self.temp_dir
|
||||
with open(env_config_path, 'w') as file:
|
||||
file.write('anoption: overwrite')
|
||||
|
||||
ui.main(['test'])
|
||||
self.assertEqual(config['anoption'].get(), 'overwrite')
|
||||
|
||||
def test_cli_config_file_overwrites_user_defaults(self):
|
||||
with open(self.user_config_path, 'w') as file:
|
||||
file.write('anoption: value')
|
||||
|
||||
env_config_path = os.path.join(self.temp_dir, 'config.yaml')
|
||||
os.environ['BEETSDIR'] = self.temp_dir
|
||||
with open(env_config_path, 'w') as file:
|
||||
file.write('anoption: overwrite')
|
||||
|
||||
cli_config_path = os.path.join(self.temp_dir, 'config.yaml')
|
||||
with open(cli_config_path, 'w') as file:
|
||||
file.write('anoption: cli overwrite')
|
||||
|
|
@ -595,6 +584,77 @@ class ConfigTest(_common.TestCase):
|
|||
ui._raw_main(['--config', cli_config_path, 'test'])
|
||||
self.assertEqual(config['anoption'].get(), 'cli overwrite')
|
||||
|
||||
def test_cli_config_file_overwrites_beetsdir_defaults(self):
|
||||
os.environ['BEETSDIR'] = self.beetsdir
|
||||
env_config_path = os.path.join(self.beetsdir, 'config.yaml')
|
||||
with open(env_config_path, 'w') as file:
|
||||
file.write('anoption: value')
|
||||
|
||||
cli_config_path = os.path.join(self.temp_dir, 'config.yaml')
|
||||
with open(cli_config_path, 'w') as file:
|
||||
file.write('anoption: cli overwrite')
|
||||
|
||||
ui._raw_main(['--config', cli_config_path, 'test'])
|
||||
self.assertEqual(config['anoption'].get(), 'cli overwrite')
|
||||
|
||||
@unittest.skip('Difficult to implement with optparse')
|
||||
def test_multiple_cli_config_files(self):
|
||||
cli_config_path_1 = os.path.join(self.temp_dir, 'config.yaml')
|
||||
cli_config_path_2 = os.path.join(self.temp_dir, 'config_2.yaml')
|
||||
|
||||
with open(cli_config_path_1, 'w') as file:
|
||||
file.write('first: value')
|
||||
|
||||
with open(cli_config_path_2, 'w') as file:
|
||||
file.write('second: value')
|
||||
|
||||
ui._raw_main(['--config', cli_config_path_1,
|
||||
'--config', cli_config_path_2, 'test'])
|
||||
self.assertEqual(config['first'].get(), 'value')
|
||||
self.assertEqual(config['second'].get(), 'value')
|
||||
|
||||
@unittest.skip('Difficult to implement with optparse')
|
||||
def test_multiple_cli_config_overwrite(self):
|
||||
cli_config_path = os.path.join(self.temp_dir, 'config.yaml')
|
||||
cli_overwrite_config_path = os.path.join(self.temp_dir,
|
||||
'overwrite_config.yaml')
|
||||
|
||||
with open(cli_config_path, 'w') as file:
|
||||
file.write('anoption: value')
|
||||
|
||||
with open(cli_overwrite_config_path, 'w') as file:
|
||||
file.write('anoption: overwrite')
|
||||
|
||||
ui._raw_main(['--config', cli_config_path,
|
||||
'--config', cli_overwrite_config_path, 'test'])
|
||||
self.assertEqual(config['anoption'].get(), 'cli overwrite')
|
||||
|
||||
def test_cli_config_paths_resolve_relative_to_user_dir(self):
|
||||
cli_config_path = os.path.join(self.temp_dir, 'config.yaml')
|
||||
with open(cli_config_path, 'w') as file:
|
||||
file.write('library: beets.db\n')
|
||||
file.write('statefile: state')
|
||||
|
||||
ui._raw_main(['--config', cli_config_path, 'test'])
|
||||
self.assertEqual(config['library'].as_filename(),
|
||||
os.path.join(self.user_config_dir, 'beets.db'))
|
||||
self.assertEqual(config['statefile'].as_filename(),
|
||||
os.path.join(self.user_config_dir, 'state'))
|
||||
|
||||
def test_cli_config_paths_resolve_relative_to_beetsdir(self):
|
||||
os.environ['BEETSDIR'] = self.beetsdir
|
||||
|
||||
cli_config_path = os.path.join(self.temp_dir, 'config.yaml')
|
||||
with open(cli_config_path, 'w') as file:
|
||||
file.write('library: beets.db\n')
|
||||
file.write('statefile: state')
|
||||
|
||||
ui._raw_main(['--config', cli_config_path, 'test'])
|
||||
self.assertEqual(config['library'].as_filename(),
|
||||
os.path.join(self.beetsdir, 'beets.db'))
|
||||
self.assertEqual(config['statefile'].as_filename(),
|
||||
os.path.join(self.beetsdir, 'state'))
|
||||
|
||||
def test_cli_config_file_loads_plugin_commands(self):
|
||||
plugin_path = os.path.join(_common.RSRC, 'beetsplug')
|
||||
|
||||
|
|
@ -606,6 +666,48 @@ class ConfigTest(_common.TestCase):
|
|||
ui._raw_main(['--config', cli_config_path, 'plugin'])
|
||||
self.assertTrue(plugins.find_plugins()[0].is_test_plugin)
|
||||
|
||||
def test_beetsdir_config(self):
|
||||
os.environ['BEETSDIR'] = self.beetsdir
|
||||
|
||||
env_config_path = os.path.join(self.beetsdir, 'config.yaml')
|
||||
with open(env_config_path, 'w') as file:
|
||||
file.write('anoption: overwrite')
|
||||
|
||||
config.read()
|
||||
self.assertEqual(config['anoption'].get(), 'overwrite')
|
||||
|
||||
def test_beetsdir_config_does_not_load_default_user_config(self):
|
||||
os.environ['BEETSDIR'] = self.beetsdir
|
||||
|
||||
with open(self.user_config_path, 'w') as file:
|
||||
file.write('anoption: value')
|
||||
|
||||
config.read()
|
||||
self.assertFalse(config['anoption'].exists())
|
||||
|
||||
def test_default_config_paths_resolve_relative_to_beetsdir(self):
|
||||
os.environ['BEETSDIR'] = self.beetsdir
|
||||
|
||||
config.read()
|
||||
self.assertEqual(config['library'].as_filename(),
|
||||
os.path.join(self.beetsdir, 'library.db'))
|
||||
self.assertEqual(config['statefile'].as_filename(),
|
||||
os.path.join(self.beetsdir, 'state.pickle'))
|
||||
|
||||
def test_beetsdir_config_paths_resolve_relative_to_beetsdir(self):
|
||||
os.environ['BEETSDIR'] = self.beetsdir
|
||||
|
||||
env_config_path = os.path.join(self.beetsdir, 'config.yaml')
|
||||
with open(env_config_path, 'w') as file:
|
||||
file.write('library: beets.db\n')
|
||||
file.write('statefile: state')
|
||||
|
||||
config.read()
|
||||
self.assertEqual(config['library'].as_filename(),
|
||||
os.path.join(self.beetsdir, 'beets.db'))
|
||||
self.assertEqual(config['statefile'].as_filename(),
|
||||
os.path.join(self.beetsdir, 'state'))
|
||||
|
||||
|
||||
class ShowdiffTest(_common.TestCase):
|
||||
def setUp(self):
|
||||
|
|
|
|||
Loading…
Reference in a new issue