Skip to content

Commit

Permalink
Merge pull request #1556 from HumorBaby/1554-fix-admin-set-None
Browse files Browse the repository at this point in the history
 admin: add unset command
  • Loading branch information
dgw authored Sep 6, 2019
2 parents bdc66f5 + 01bcf40 commit 91488be
Show file tree
Hide file tree
Showing 2 changed files with 104 additions and 28 deletions.
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])
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])
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

0 comments on commit 91488be

Please sign in to comment.