Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve config cmd #4188

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions beets/ui/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1254,8 +1254,8 @@ def _raw_main(args, lib=None):
# starting.
if subargs and subargs[0] == 'config' \
and ('-e' in subargs or '--edit' in subargs):
from beets.ui.commands import config_edit
return config_edit()
from beets.ui.commands import config_open
return config_open()

test_lib = bool(lib)
subcommands, plugins, lib = _setup(options, lib)
Expand Down
105 changes: 101 additions & 4 deletions beets/ui/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,14 @@
"""


from optparse import Option
import os
import re
import operator
from platform import python_version
from collections import namedtuple, Counter
from itertools import chain
from functools import reduce

import beets
from beets import ui
Expand All @@ -38,7 +41,7 @@
from beets import config
from beets import logging

from . import _store_dict
from . import CommonOptionsParser, _store_dict

VARIOUS_ARTISTS = 'Various Artists'
PromptChoice = namedtuple('PromptChoice', ['short', 'long', 'callback'])
Expand Down Expand Up @@ -1704,6 +1707,8 @@ def config_func(lib, opts, args):
# Open in editor.
elif opts.edit:
config_edit()
elif opts.set is not None:
config_set(opts)

# Dump configuration.
else:
Expand All @@ -1714,6 +1719,69 @@ def config_func(lib, opts, args):
print("Empty configuration")


def get_from_dict(data_dict, map_list):
"""Get a value from a dictionnary with map list.
For ["key1", "key2"] as map_list and
{"key1":{"key2":True}} as data_dict.
The function will output True.
"""
return reduce(operator.getitem, map_list, data_dict)


def set_in_dict(data_dict, map_list, value):
"""Set a value in a dictionnary with map list.
For ["key1", "key2"] as map_list,
{"key1":{"key2":False}} as data_dict and True as value,
the function modify data_dict like this: {"key1":{"key2":True}}
"""
get_from_dict(data_dict, map_list[:-1])[map_list[-1]] = value


def config_set(opts):
"""Edit the configuration file, with the path of the setting
(ex. import.write) and the value.
"""
if not opts.set:
print("Config value not provided")
return

setting_path = opts.set["field"].split(".")

actual_value = None
try:
# Get the actual value of the modified settings
actual_value = get_from_dict(config, setting_path).get()
except KeyError as exc:
message = f"Could not edit configuration: {exc}"
raise ui.UserError(message)

value = None
try:
# Convert the string value to type of the setting
if isinstance(actual_value, bool):
value = util.str2bool(opts.set["value"])
elif isinstance(actual_value, str):
value = opts.set["value"]
elif isinstance(actual_value, int):
value = int(opts.set["value"])
elif isinstance(actual_value, float):
value = float(opts.set["value"])
else:
print("This type of the setting is not supported")
return
except ValueError as exc:
message = f"Could not edit configuration: {exc}"
raise ui.UserError(message)

# Set the new value in config
set_in_dict(config, setting_path, value)

# Update the file
path = config.user_config_path()
with open(path, 'w+') as file:
file.write(config.dump(full=opts.defaults, redact=opts.redact))


def config_edit():
"""Open a program to edit the user configuration.
An empty config file is created if no existing config file exists.
Expand All @@ -1725,21 +1793,49 @@ def config_edit():
open(path, 'w+').close()
util.interactive_open([path], editor)
except OSError as exc:
message = f"Could not edit configuration: {exc}"
message = f"Could not open configuration file: {exc}"
if not editor:
message += ". Please set the EDITOR environment variable"
raise ui.UserError(message)

class SetOption(Option):
ACTIONS = Option.ACTIONS + ("extend",)
STORE_ACTIONS = Option.STORE_ACTIONS + ("extend",)
TYPED_ACTIONS = Option.TYPED_ACTIONS + ("extend",)
ALWAYS_TYPED_ACTIONS = Option.ALWAYS_TYPED_ACTIONS + ("extend",)

def take_action(self, action, dest, opt, value, values, parser):
if action == "extend":
if "=" in value:
value = value.strip().split("=")
d = {"field": value[0],
"value": value[1]}
values.ensure_value(dest, {}).update(d)
return

print("Missing field or value")
else:
super.take_action(self, action, dest, opt, value, values, parser)

config_cmd = ui.Subcommand('config',
help='show or edit the user configuration')
help='show or edit the user configuration', parser=CommonOptionsParser(option_class=SetOption))
config_cmd.parser.add_option(
'-p', '--paths', action='store_true',
help='show files that configuration was loaded from'
)
config_cmd.parser.add_option(
'-s', '--set', action='extend',
help='edit a setting',
dest="set"
)
config_cmd.parser.add_option(
'-v', '--value', action='store',
help='value of the setting provided by edit',
dest="setting_value"
)
config_cmd.parser.add_option(
'-e', '--edit', action='store_true',
help='edit user configuration with $EDITOR'
help='open user configuration with $EDITOR'
)
config_cmd.parser.add_option(
'-d', '--defaults', action='store_true',
Expand All @@ -1750,6 +1846,7 @@ def config_edit():
dest='redact', default=True,
help='do not redact sensitive fields'
)

config_cmd.func = config_func
default_commands.append(config_cmd)

Expand Down