From 63797af9d8dbfb80923b6aeda61f25b670468d3d Mon Sep 17 00:00:00 2001 From: dgw Date: Sun, 8 Nov 2020 23:12:18 -0600 Subject: [PATCH 1/2] cli.config: implement `set` and `unset` subcommands --- sopel/cli/config.py | 97 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/sopel/cli/config.py b/sopel/cli/config.py index c2728c12c4..974801f4d7 100644 --- a/sopel/cli/config.py +++ b/sopel/cli/config.py @@ -62,6 +62,27 @@ def build_parser(): get_parser.add_argument('option') utils.add_common_arguments(get_parser) + # sopel-config set
+ set_parser = subparsers.add_parser( + 'set', + help="Set a configuration option's value", + description="Set a configuration option's value", + ) + set_parser.add_argument('section') + set_parser.add_argument('option') + set_parser.add_argument('value') + utils.add_common_arguments(set_parser) + + # sopel-config unset
+ unset_parser = subparsers.add_parser( + 'unset', + help="Unset a configuration option", + description="Unset a configuration option", + ) + unset_parser.add_argument('section') + unset_parser.add_argument('option') + utils.add_common_arguments(unset_parser) + return parser @@ -167,6 +188,78 @@ def handle_get(options): return 0 # successful operation +def handle_set(options): + """Set the ``
`` setting to ````. + + :param options: parsed arguments + :type options: :class:`argparse.Namespace` + :return: 0 if everything went fine; + 1 if the section and/or key does not exist; + 2 if the settings can't be loaded + """ + try: + settings = utils.load_settings(options) + except Exception as error: + tools.stderr(error) + return 2 + + section = options.section + option = options.option + value = options.value + + # Make sure the section exists + if not settings.parser.has_section(section): + try: + settings.parser.add_section(section) + except (TypeError, ValueError): + tools.stderr('Invalid section name "%s"' % section) + return 1 + + # Update the option & save config file + settings.parser.set(section, option, value) + settings.save() + print('Updated option "{}.{}"'.format(section, option)) + return 0 # successful operation + + +def handle_unset(options): + """Unset the setting value of ``
``. + + :param options: parsed arguments + :type options: :class:`argparse.Namespace` + :return: 0 if everything went fine; + 1 if the section and/or key does not exist; + 2 if the settings can't be loaded + """ + try: + settings = utils.load_settings(options) + except Exception as error: + tools.stderr(error) + return 2 + + section = options.section + option = options.option + + # Make sure the section exists + if not settings.parser.has_section(section): + tools.stderr('Section "%s" does not exist' % section) + return 1 + + # Unset the option + removed = settings.parser.remove_option(section, option) + # Remove section completely if empty + if len(settings.parser.options(section)) == 0: + settings.parser.remove_section(section) + # Save config file + settings.save() + # Report result + if removed: + print('Unset option "{}.{}"'.format(section, option)) + else: + print('Option "{}.{}" was not set'.format(section, option)) + return 0 # successful operation + + def main(): """Console entry point for ``sopel-config``.""" parser = build_parser() @@ -184,3 +277,7 @@ def main(): return handle_init(options) elif action == 'get': return handle_get(options) + elif action == 'set': + return handle_set(options) + elif action == 'unset': + return handle_unset(options) From d49b67978099c4ddf42bb60fd402d5531e4cf4e5 Mon Sep 17 00:00:00 2001 From: dgw Date: Mon, 9 Nov 2020 12:26:32 -0600 Subject: [PATCH 2/2] cli.config: implement limited validation for `set` (in `[core]` only) Note that an invalid value already present in the config file will block using `sopel-config` commands **at all** for now. This is not new. Plugin-defined `StaticSection`s aren't loaded as such here. Only values present in the config file are parsed by `configparser`, and new values can't be validated unless `cli.utils.load_settings()` is reworked to load plugins' static sections too. --- sopel/cli/config.py | 41 +++++++++++++++++++++++++++++++---------- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/sopel/cli/config.py b/sopel/cli/config.py index 974801f4d7..644467b3db 100644 --- a/sopel/cli/config.py +++ b/sopel/cli/config.py @@ -6,6 +6,7 @@ import os from sopel import tools +from sopel.config import types from . import utils @@ -196,29 +197,49 @@ def handle_set(options): :return: 0 if everything went fine; 1 if the section and/or key does not exist; 2 if the settings can't be loaded + 3 if the input was not valid """ try: settings = utils.load_settings(options) except Exception as error: - tools.stderr(error) + tools.stderr('Cannot load settings: ' + str(error)) return 2 - section = options.section - option = options.option + section_name = options.section + option_name = options.option value = options.value # Make sure the section exists - if not settings.parser.has_section(section): + section = getattr(settings, section_name, False) + if not section: + tools.stderr( + 'Section "%s" does not exist' % section_name) + return 1 + + # Make sure option exists, if section is static + # TODO: With current (7.1-dev) settings loader, only `core` will ever be + # a StaticSection, meaning plugin sections' values won't be validated. + static_sec = isinstance(section, types.StaticSection) + if static_sec and not hasattr(section, option_name): + tools.stderr( + 'Section "%s" does not have an option "%s"' + % (section_name, option_name)) + return 1 + + # Validate value if possible + descriptor = getattr(section.__class__, option_name) if static_sec else None + if descriptor is not None: try: - settings.parser.add_section(section) - except (TypeError, ValueError): - tools.stderr('Invalid section name "%s"' % section) - return 1 + value = descriptor._parse(value, settings, section) + except ValueError as e: + tools.stderr( + 'Cannot set "{}.{}": {}'.format(section_name, option_name, str(e))) + return 3 # Update the option & save config file - settings.parser.set(section, option, value) + setattr(section, option_name, value) settings.save() - print('Updated option "{}.{}"'.format(section, option)) + print('Updated option "{}.{}"'.format(section_name, option_name)) return 0 # successful operation