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

admin: add unset command #1556

Merged
merged 4 commits into from
Sep 6, 2019
Merged
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
2 changes: 2 additions & 0 deletions sopel/config/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,8 @@ def __get__(self, instance, owner=None):

def __set__(self, instance, value):
if value is None:
if self.default == NO_DEFAULT:
raise ValueError('Cannot unset an option with a required value.')
instance._parser.remove_option(instance._section_name, self.name)
return
value = self.serialize(value)
Expand Down
130 changes: 102 additions & 28 deletions sopel/modules/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,19 @@ def setup(bot):
bot.config.define_section('admin', AdminSection)


class InvalidSection(Exception):
def __init__(self, section):
super(InvalidSection, self).__init__(self, 'Section [{}] does not exist.'.format(section))
self.section = section


class InvalidSectionOption(Exception):
def __init__(self, section, option):
super(InvalidSectionOption, self).__init__(self, 'Section [{}] does not have option \'{}\'.'.format(section, option))
self.section = section
self.option = option


def _get_config_channels(channels):
"""List"""
for channel_info in channels:
Expand Down Expand Up @@ -245,57 +258,83 @@ def mode(bot, trigger):
bot.write(('MODE', bot.nick + ' ' + mode))


@sopel.module.require_privmsg("This command only works as a private message.")
@sopel.module.require_admin("This command requires admin privileges.")
@sopel.module.commands('set')
@sopel.module.example('.set core.owner Me')
def set_config(bot, trigger):
"""See and modify values of Sopel's config object.
def parse_section_option_value(config, trigger):
"""Parse trigger for set/unset to get relevant config elements.

Trigger args:
arg1 - section and option, in the form "section.option"
arg2 - value
:param config: Sopel's config
:param trigger: IRC line trigger
:return: A tuple with ``(section, section_name, static_sec, option, value)``
:raises InvalidSection: section does not exist
:raises InvalidSectionOption: option does not exist for section

If there is no section, section will default to "core".
If value is None, the option will be deleted.
The ``value`` is optional and can be returned as ``None`` if omitted from command.
"""
# Get section and option from first argument.
match = trigger.group(3)
if match is None:
bot.reply("Usage: .set section.option value")
return
raise ValueError # Invalid command

# Get section and option from first argument.
arg1 = match.split('.')
if len(arg1) == 1:
section_name, option = "core", arg1[0]
elif len(arg1) == 2:
section_name, option = arg1
else:
bot.reply("Usage: .set section.option value")
return
section = getattr(bot.config, section_name)
raise ValueError # invalid command format

section = getattr(config, section_name, False)
if not section:
raise InvalidSection(section_name)
static_sec = isinstance(section, StaticSection)

if static_sec and not hasattr(section, option):
bot.say('[{}] section has no option {}.'.format(section_name, option))
return
raise InvalidSectionOption(section_name, option) # Option not found in section

if not static_sec and not config.parser.has_option(section_name, option):
raise InvalidSectionOption(section_name, option) # Option not found in section

delim = trigger.group(2).find(' ')
# Skip preceding whitespaces, if any.
while delim > 0 and delim < len(trigger.group(2)) and trigger.group(2)[delim] == ' ':
delim = delim + 1

# Display current value if no value is given.
value = trigger.group(2)[delim:]
if delim == -1 or delim == len(trigger.group(2)):
if not static_sec and bot.config.parser.has_option(section, option):
bot.reply("Option %s.%s does not exist." % (section_name, option))
return
# Except if the option looks like a password. Censor those to stop them
# from being put on log files.
value = None

return (section, section_name, static_sec, option, value)


@sopel.module.require_privmsg("This command only works as a private message.")
@sopel.module.require_admin("This command requires admin privileges.")
@sopel.module.commands('set')
@sopel.module.example('.set core.owner Me')
def set_config(bot, trigger):
"""See and modify values of Sopel's config object.

Trigger args:
arg1 - section and option, in the form "section.option"
arg2 - value

If there is no section, section will default to "core".
If value is not provided, the current value will be displayed.
"""
try:
section, section_name, static_sec, option, value = parse_section_option_value(bot.config, trigger)
except ValueError:
bot.reply('Usage: {}set section.option [value]'.format(bot.config.core.help_prefix))
return
except (InvalidSection, InvalidSectionOption) as exc:
bot.say(exc.args[1])
Exirel marked this conversation as resolved.
Show resolved Hide resolved
return

# Display current value if no value is given
if not value:
if option.endswith("password") or option.endswith("pass"):
value = "(password censored)"
else:
value = getattr(section, option)
bot.reply("%s.%s = %s" % (section_name, option, value))
bot.reply("%s.%s = %s (%s)" % (section_name, option, value, type(value).__name__))
return

# Owner-related settings cannot be modified interactively. Any changes to these
Expand All @@ -305,8 +344,7 @@ def set_config(bot, trigger):
.format(section_name, option))
return

# Otherwise, set the value to one given as argument 2.
value = trigger.group(2)[delim:]
# Otherwise, set the value to one given
if static_sec:
descriptor = getattr(section.__class__, option)
try:
Expand All @@ -318,6 +356,42 @@ def set_config(bot, trigger):
bot.say("Can't set attribute: " + str(exc))
return
setattr(section, option, value)
bot.say("OK. Set '{}.{}' successfully.".format(section_name, option))


@sopel.module.require_privmsg("This command only works as a private message.")
@sopel.module.require_admin("This command requires admin privileges.")
@sopel.module.commands('unset')
@sopel.module.example('.unset core.owner')
def unset_config(bot, trigger):
"""Unset value of Sopel's config object.

Unsetting a value will reset it to the default specified in the config
definition.

Trigger args:
arg1 - section and option, in the form "section.option"

If there is no section, section will default to "core".
"""
try:
section, section_name, static_sec, option, value = parse_section_option_value(bot.config, trigger)
except ValueError:
bot.reply('Usage: {}unset section.option [value]'.format(bot.config.core.help_prefix))
return
except (InvalidSection, InvalidSectionOption) as exc:
bot.say(exc.args[1])
Exirel marked this conversation as resolved.
Show resolved Hide resolved
return

if value:
bot.reply('Invalid command; no value should be provided to unset.')
return

try:
setattr(section, option, None)
bot.say("OK. Unset '{}.{}' successfully.".format(section_name, option))
except ValueError:
bot.reply('Cannot unset {}.{}; it is a required option.'.format(section_name, option))


@sopel.module.require_privmsg
Expand Down