We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
admin: .set command can't handle spaces
I think I once discovered this a long, long time ago and forgot to document it. I vaguely remember hitting this problem way back when I first started using Sopel (at the time, Willie)… But here we are. I hit it again, and this time it's getting an issue so it can be fixed!

When PMing Sopel `.set section.key spam eggs sausage and spam`, the value in `section.key` of the config will become simply `spam`. Because `admin.py` uses `trigger.group(4)` as the value, it ignores anything after the first whitespace.

The `.set` command probably isn't used a whole lot, but it should still work as expected. As-is, it's useless for updating things like `core.default_time_format`, which almost _always_ contain spaces.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `sopel/modules/admin.py`
Content:
```
1 # coding=utf-8
2 """
3 admin.py - Sopel Admin Module
4 Copyright 2010-2011, Sean B. Palmer (inamidst.com) and Michael Yanovich
5 (yanovich.net)
6 Copyright © 2012, Elad Alfassa, <elad@fedoraproject.org>
7 Copyright 2013, Ari Koivula <ari@koivu.la>
8 
9 Licensed under the Eiffel Forum License 2.
10 
11 https://sopel.chat
12 """
13 from __future__ import unicode_literals, absolute_import, print_function, division
14 
15 from sopel.config.types import (
16     StaticSection, ValidatedAttribute, FilenameAttribute
17 )
18 import sopel.module
19 
20 
21 class AdminSection(StaticSection):
22     hold_ground = ValidatedAttribute('hold_ground', bool, default=False)
23     """Auto re-join on kick"""
24     auto_accept_invite = ValidatedAttribute('auto_accept_invite', bool,
25                                             default=True)
26 
27 
28 def configure(config):
29     config.define_section('admin', AdminSection)
30     config.admin.configure_setting('hold_ground',
31                                    "Automatically re-join after being kicked?")
32     config.admin.configure_setting('auto_accept_invite',
33                                    'Automatically join channels when invited?')
34 
35 
36 def setup(bot):
37     bot.config.define_section('admin', AdminSection)
38 
39 
40 @sopel.module.require_privmsg
41 @sopel.module.require_admin
42 @sopel.module.commands('join')
43 @sopel.module.priority('low')
44 @sopel.module.example('.join #example or .join #example key')
45 def join(bot, trigger):
46     """Join the specified channel. This is an admin-only command."""
47     channel, key = trigger.group(3), trigger.group(4)
48     if not channel:
49         return
50     elif not key:
51         bot.join(channel)
52     else:
53         bot.join(channel, key)
54 
55 
56 @sopel.module.require_privmsg
57 @sopel.module.require_admin
58 @sopel.module.commands('part')
59 @sopel.module.priority('low')
60 @sopel.module.example('.part #example')
61 def part(bot, trigger):
62     """Part the specified channel. This is an admin-only command."""
63     channel, _sep, part_msg = trigger.group(2).partition(' ')
64     if part_msg:
65         bot.part(channel, part_msg)
66     else:
67         bot.part(channel)
68 
69 
70 @sopel.module.require_privmsg
71 @sopel.module.require_owner
72 @sopel.module.commands('quit')
73 @sopel.module.priority('low')
74 def quit(bot, trigger):
75     """Quit from the server. This is an owner-only command."""
76     quit_message = trigger.group(2)
77     if not quit_message:
78         quit_message = 'Quitting on command from %s' % trigger.nick
79 
80     bot.quit(quit_message)
81 
82 
83 @sopel.module.require_privmsg
84 @sopel.module.require_admin
85 @sopel.module.commands('msg')
86 @sopel.module.priority('low')
87 @sopel.module.example('.msg #YourPants Does anyone else smell neurotoxin?')
88 def msg(bot, trigger):
89     """
90     Send a message to a given channel or nick. Can only be done in privmsg by an
91     admin.
92     """
93     if trigger.group(2) is None:
94         return
95 
96     channel, _sep, message = trigger.group(2).partition(' ')
97     message = message.strip()
98     if not channel or not message:
99         return
100 
101     bot.msg(channel, message)
102 
103 
104 @sopel.module.require_privmsg
105 @sopel.module.require_admin
106 @sopel.module.commands('me')
107 @sopel.module.priority('low')
108 def me(bot, trigger):
109     """
110     Send an ACTION (/me) to a given channel or nick. Can only be done in privmsg
111     by an admin.
112     """
113     if trigger.group(2) is None:
114         return
115 
116     channel, _sep, action = trigger.group(2).partition(' ')
117     action = action.strip()
118     if not channel or not action:
119         return
120 
121     msg = '\x01ACTION %s\x01' % action
122     bot.msg(channel, msg)
123 
124 
125 @sopel.module.event('INVITE')
126 @sopel.module.rule('.*')
127 @sopel.module.priority('low')
128 def invite_join(bot, trigger):
129     """
130     Join a channel sopel is invited to, if the inviter is an admin.
131     """
132     if trigger.admin or bot.config.admin.auto_accept_invite:
133         bot.join(trigger.args[1])
134         return
135 
136 
137 @sopel.module.event('KICK')
138 @sopel.module.rule(r'.*')
139 @sopel.module.priority('low')
140 def hold_ground(bot, trigger):
141     """
142     This function monitors all kicks across all channels sopel is in. If it
143     detects that it is the one kicked it'll automatically join that channel.
144 
145     WARNING: This may not be needed and could cause problems if sopel becomes
146     annoying. Please use this with caution.
147     """
148     if bot.config.admin.hold_ground:
149         channel = trigger.sender
150         if trigger.args[1] == bot.nick:
151             bot.join(channel)
152 
153 
154 @sopel.module.require_privmsg
155 @sopel.module.require_admin
156 @sopel.module.commands('mode')
157 @sopel.module.priority('low')
158 def mode(bot, trigger):
159     """Set a user mode on Sopel. Can only be done in privmsg by an admin."""
160     mode = trigger.group(3)
161     bot.write(('MODE ', bot.nick + ' ' + mode))
162 
163 
164 @sopel.module.require_privmsg("This command only works as a private message.")
165 @sopel.module.require_admin("This command requires admin privileges.")
166 @sopel.module.commands('set')
167 @sopel.module.example('.set core.owner Me')
168 def set_config(bot, trigger):
169     """See and modify values of sopels config object.
170 
171     Trigger args:
172         arg1 - section and option, in the form "section.option"
173         arg2 - value
174 
175     If there is no section, section will default to "core".
176     If value is None, the option will be deleted.
177     """
178     # Get section and option from first argument.
179     arg1 = trigger.group(3).split('.')
180     if len(arg1) == 1:
181         section_name, option = "core", arg1[0]
182     elif len(arg1) == 2:
183         section_name, option = arg1
184     else:
185         bot.reply("Usage: .set section.option value")
186         return
187     section = getattr(bot.config, section_name)
188     static_sec = isinstance(section, StaticSection)
189 
190     if static_sec and not hasattr(section, option):
191         bot.say('[{}] section has no option {}.'.format(section_name, option))
192         return
193 
194     # Display current value if no value is given.
195     value = trigger.group(4)
196     if not value:
197         if not static_sec and bot.config.parser.has_option(section, option):
198             bot.reply("Option %s.%s does not exist." % (section_name, option))
199             return
200         # Except if the option looks like a password. Censor those to stop them
201         # from being put on log files.
202         if option.endswith("password") or option.endswith("pass"):
203             value = "(password censored)"
204         else:
205             value = getattr(section, option)
206         bot.reply("%s.%s = %s" % (section_name, option, value))
207         return
208 
209     # Otherwise, set the value to one given as argument 2.
210     if static_sec:
211         descriptor = getattr(section.__class__, option)
212         try:
213             if isinstance(descriptor, FilenameAttribute):
214                 value = descriptor.parse(bot.config, descriptor, value)
215             else:
216                 value = descriptor.parse(value)
217         except ValueError as exc:
218             bot.say("Can't set attribute: " + str(exc))
219             return
220     setattr(section, option, value)
221 
222 
223 @sopel.module.require_privmsg
224 @sopel.module.require_admin
225 @sopel.module.commands('save')
226 @sopel.module.example('.save')
227 def save_config(bot, trigger):
228     """Save state of sopels config object to the configuration file."""
229     bot.config.save()
230
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/sopel/modules/admin.py b/sopel/modules/admin.py
--- a/sopel/modules/admin.py
+++ b/sopel/modules/admin.py
@@ -191,9 +191,13 @@
         bot.say('[{}] section has no option {}.'.format(section_name, option))
         return
 
+    delim = trigger.group(2).find(' ')
+    # Skip preceeding 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(4)
-    if not value:
+    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
@@ -207,6 +211,7 @@
         return
 
     # Otherwise, set the value to one given as argument 2.
+    value = trigger.group(2)[delim:]
     if static_sec:
         descriptor = getattr(section.__class__, option)
         try:

{"golden_diff": "diff --git a/sopel/modules/admin.py b/sopel/modules/admin.py\n--- a/sopel/modules/admin.py\n+++ b/sopel/modules/admin.py\n@@ -191,9 +191,13 @@\n         bot.say('[{}] section has no option {}.'.format(section_name, option))\n         return\n \n+    delim = trigger.group(2).find(' ')\n+    # Skip preceeding whitespaces, if any.\n+    while delim > 0 and delim < len(trigger.group(2)) and trigger.group(2)[delim] == ' ':\n+        delim = delim + 1\n+\n     # Display current value if no value is given.\n-    value = trigger.group(4)\n-    if not value:\n+    if delim == -1 or delim == len(trigger.group(2)):\n         if not static_sec and bot.config.parser.has_option(section, option):\n             bot.reply(\"Option %s.%s does not exist.\" % (section_name, option))\n             return\n@@ -207,6 +211,7 @@\n         return\n \n     # Otherwise, set the value to one given as argument 2.\n+    value = trigger.group(2)[delim:]\n     if static_sec:\n         descriptor = getattr(section.__class__, option)\n         try:\n", "issue": "admin: .set command can't handle spaces\nI think I once discovered this a long, long time ago and forgot to document it. I vaguely remember hitting this problem way back when I first started using Sopel (at the time, Willie)\u2026 But here we are. I hit it again, and this time it's getting an issue so it can be fixed!\r\n\r\nWhen PMing Sopel `.set section.key spam eggs sausage and spam`, the value in `section.key` of the config will become simply `spam`. Because `admin.py` uses `trigger.group(4)` as the value, it ignores anything after the first whitespace.\r\n\r\nThe `.set` command probably isn't used a whole lot, but it should still work as expected. As-is, it's useless for updating things like `core.default_time_format`, which almost _always_ contain spaces.\n", "before_files": [{"content": "# coding=utf-8\n\"\"\"\nadmin.py - Sopel Admin Module\nCopyright 2010-2011, Sean B. Palmer (inamidst.com) and Michael Yanovich\n(yanovich.net)\nCopyright \u00a9 2012, Elad Alfassa, <elad@fedoraproject.org>\nCopyright 2013, Ari Koivula <ari@koivu.la>\n\nLicensed under the Eiffel Forum License 2.\n\nhttps://sopel.chat\n\"\"\"\nfrom __future__ import unicode_literals, absolute_import, print_function, division\n\nfrom sopel.config.types import (\n    StaticSection, ValidatedAttribute, FilenameAttribute\n)\nimport sopel.module\n\n\nclass AdminSection(StaticSection):\n    hold_ground = ValidatedAttribute('hold_ground', bool, default=False)\n    \"\"\"Auto re-join on kick\"\"\"\n    auto_accept_invite = ValidatedAttribute('auto_accept_invite', bool,\n                                            default=True)\n\n\ndef configure(config):\n    config.define_section('admin', AdminSection)\n    config.admin.configure_setting('hold_ground',\n                                   \"Automatically re-join after being kicked?\")\n    config.admin.configure_setting('auto_accept_invite',\n                                   'Automatically join channels when invited?')\n\n\ndef setup(bot):\n    bot.config.define_section('admin', AdminSection)\n\n\n@sopel.module.require_privmsg\n@sopel.module.require_admin\n@sopel.module.commands('join')\n@sopel.module.priority('low')\n@sopel.module.example('.join #example or .join #example key')\ndef join(bot, trigger):\n    \"\"\"Join the specified channel. This is an admin-only command.\"\"\"\n    channel, key = trigger.group(3), trigger.group(4)\n    if not channel:\n        return\n    elif not key:\n        bot.join(channel)\n    else:\n        bot.join(channel, key)\n\n\n@sopel.module.require_privmsg\n@sopel.module.require_admin\n@sopel.module.commands('part')\n@sopel.module.priority('low')\n@sopel.module.example('.part #example')\ndef part(bot, trigger):\n    \"\"\"Part the specified channel. This is an admin-only command.\"\"\"\n    channel, _sep, part_msg = trigger.group(2).partition(' ')\n    if part_msg:\n        bot.part(channel, part_msg)\n    else:\n        bot.part(channel)\n\n\n@sopel.module.require_privmsg\n@sopel.module.require_owner\n@sopel.module.commands('quit')\n@sopel.module.priority('low')\ndef quit(bot, trigger):\n    \"\"\"Quit from the server. This is an owner-only command.\"\"\"\n    quit_message = trigger.group(2)\n    if not quit_message:\n        quit_message = 'Quitting on command from %s' % trigger.nick\n\n    bot.quit(quit_message)\n\n\n@sopel.module.require_privmsg\n@sopel.module.require_admin\n@sopel.module.commands('msg')\n@sopel.module.priority('low')\n@sopel.module.example('.msg #YourPants Does anyone else smell neurotoxin?')\ndef msg(bot, trigger):\n    \"\"\"\n    Send a message to a given channel or nick. Can only be done in privmsg by an\n    admin.\n    \"\"\"\n    if trigger.group(2) is None:\n        return\n\n    channel, _sep, message = trigger.group(2).partition(' ')\n    message = message.strip()\n    if not channel or not message:\n        return\n\n    bot.msg(channel, message)\n\n\n@sopel.module.require_privmsg\n@sopel.module.require_admin\n@sopel.module.commands('me')\n@sopel.module.priority('low')\ndef me(bot, trigger):\n    \"\"\"\n    Send an ACTION (/me) to a given channel or nick. Can only be done in privmsg\n    by an admin.\n    \"\"\"\n    if trigger.group(2) is None:\n        return\n\n    channel, _sep, action = trigger.group(2).partition(' ')\n    action = action.strip()\n    if not channel or not action:\n        return\n\n    msg = '\\x01ACTION %s\\x01' % action\n    bot.msg(channel, msg)\n\n\n@sopel.module.event('INVITE')\n@sopel.module.rule('.*')\n@sopel.module.priority('low')\ndef invite_join(bot, trigger):\n    \"\"\"\n    Join a channel sopel is invited to, if the inviter is an admin.\n    \"\"\"\n    if trigger.admin or bot.config.admin.auto_accept_invite:\n        bot.join(trigger.args[1])\n        return\n\n\n@sopel.module.event('KICK')\n@sopel.module.rule(r'.*')\n@sopel.module.priority('low')\ndef hold_ground(bot, trigger):\n    \"\"\"\n    This function monitors all kicks across all channels sopel is in. If it\n    detects that it is the one kicked it'll automatically join that channel.\n\n    WARNING: This may not be needed and could cause problems if sopel becomes\n    annoying. Please use this with caution.\n    \"\"\"\n    if bot.config.admin.hold_ground:\n        channel = trigger.sender\n        if trigger.args[1] == bot.nick:\n            bot.join(channel)\n\n\n@sopel.module.require_privmsg\n@sopel.module.require_admin\n@sopel.module.commands('mode')\n@sopel.module.priority('low')\ndef mode(bot, trigger):\n    \"\"\"Set a user mode on Sopel. Can only be done in privmsg by an admin.\"\"\"\n    mode = trigger.group(3)\n    bot.write(('MODE ', bot.nick + ' ' + mode))\n\n\n@sopel.module.require_privmsg(\"This command only works as a private message.\")\n@sopel.module.require_admin(\"This command requires admin privileges.\")\n@sopel.module.commands('set')\n@sopel.module.example('.set core.owner Me')\ndef set_config(bot, trigger):\n    \"\"\"See and modify values of sopels config object.\n\n    Trigger args:\n        arg1 - section and option, in the form \"section.option\"\n        arg2 - value\n\n    If there is no section, section will default to \"core\".\n    If value is None, the option will be deleted.\n    \"\"\"\n    # Get section and option from first argument.\n    arg1 = trigger.group(3).split('.')\n    if len(arg1) == 1:\n        section_name, option = \"core\", arg1[0]\n    elif len(arg1) == 2:\n        section_name, option = arg1\n    else:\n        bot.reply(\"Usage: .set section.option value\")\n        return\n    section = getattr(bot.config, section_name)\n    static_sec = isinstance(section, StaticSection)\n\n    if static_sec and not hasattr(section, option):\n        bot.say('[{}] section has no option {}.'.format(section_name, option))\n        return\n\n    # Display current value if no value is given.\n    value = trigger.group(4)\n    if not value:\n        if not static_sec and bot.config.parser.has_option(section, option):\n            bot.reply(\"Option %s.%s does not exist.\" % (section_name, option))\n            return\n        # Except if the option looks like a password. Censor those to stop them\n        # from being put on log files.\n        if option.endswith(\"password\") or option.endswith(\"pass\"):\n            value = \"(password censored)\"\n        else:\n            value = getattr(section, option)\n        bot.reply(\"%s.%s = %s\" % (section_name, option, value))\n        return\n\n    # Otherwise, set the value to one given as argument 2.\n    if static_sec:\n        descriptor = getattr(section.__class__, option)\n        try:\n            if isinstance(descriptor, FilenameAttribute):\n                value = descriptor.parse(bot.config, descriptor, value)\n            else:\n                value = descriptor.parse(value)\n        except ValueError as exc:\n            bot.say(\"Can't set attribute: \" + str(exc))\n            return\n    setattr(section, option, value)\n\n\n@sopel.module.require_privmsg\n@sopel.module.require_admin\n@sopel.module.commands('save')\n@sopel.module.example('.save')\ndef save_config(bot, trigger):\n    \"\"\"Save state of sopels config object to the configuration file.\"\"\"\n    bot.config.save()\n", "path": "sopel/modules/admin.py"}], "after_files": [{"content": "# coding=utf-8\n\"\"\"\nadmin.py - Sopel Admin Module\nCopyright 2010-2011, Sean B. Palmer (inamidst.com) and Michael Yanovich\n(yanovich.net)\nCopyright \u00a9 2012, Elad Alfassa, <elad@fedoraproject.org>\nCopyright 2013, Ari Koivula <ari@koivu.la>\n\nLicensed under the Eiffel Forum License 2.\n\nhttps://sopel.chat\n\"\"\"\nfrom __future__ import unicode_literals, absolute_import, print_function, division\n\nfrom sopel.config.types import (\n    StaticSection, ValidatedAttribute, FilenameAttribute\n)\nimport sopel.module\n\n\nclass AdminSection(StaticSection):\n    hold_ground = ValidatedAttribute('hold_ground', bool, default=False)\n    \"\"\"Auto re-join on kick\"\"\"\n    auto_accept_invite = ValidatedAttribute('auto_accept_invite', bool,\n                                            default=True)\n\n\ndef configure(config):\n    config.define_section('admin', AdminSection)\n    config.admin.configure_setting('hold_ground',\n                                   \"Automatically re-join after being kicked?\")\n    config.admin.configure_setting('auto_accept_invite',\n                                   'Automatically join channels when invited?')\n\n\ndef setup(bot):\n    bot.config.define_section('admin', AdminSection)\n\n\n@sopel.module.require_privmsg\n@sopel.module.require_admin\n@sopel.module.commands('join')\n@sopel.module.priority('low')\n@sopel.module.example('.join #example or .join #example key')\ndef join(bot, trigger):\n    \"\"\"Join the specified channel. This is an admin-only command.\"\"\"\n    channel, key = trigger.group(3), trigger.group(4)\n    if not channel:\n        return\n    elif not key:\n        bot.join(channel)\n    else:\n        bot.join(channel, key)\n\n\n@sopel.module.require_privmsg\n@sopel.module.require_admin\n@sopel.module.commands('part')\n@sopel.module.priority('low')\n@sopel.module.example('.part #example')\ndef part(bot, trigger):\n    \"\"\"Part the specified channel. This is an admin-only command.\"\"\"\n    channel, _sep, part_msg = trigger.group(2).partition(' ')\n    if part_msg:\n        bot.part(channel, part_msg)\n    else:\n        bot.part(channel)\n\n\n@sopel.module.require_privmsg\n@sopel.module.require_owner\n@sopel.module.commands('quit')\n@sopel.module.priority('low')\ndef quit(bot, trigger):\n    \"\"\"Quit from the server. This is an owner-only command.\"\"\"\n    quit_message = trigger.group(2)\n    if not quit_message:\n        quit_message = 'Quitting on command from %s' % trigger.nick\n\n    bot.quit(quit_message)\n\n\n@sopel.module.require_privmsg\n@sopel.module.require_admin\n@sopel.module.commands('msg')\n@sopel.module.priority('low')\n@sopel.module.example('.msg #YourPants Does anyone else smell neurotoxin?')\ndef msg(bot, trigger):\n    \"\"\"\n    Send a message to a given channel or nick. Can only be done in privmsg by an\n    admin.\n    \"\"\"\n    if trigger.group(2) is None:\n        return\n\n    channel, _sep, message = trigger.group(2).partition(' ')\n    message = message.strip()\n    if not channel or not message:\n        return\n\n    bot.msg(channel, message)\n\n\n@sopel.module.require_privmsg\n@sopel.module.require_admin\n@sopel.module.commands('me')\n@sopel.module.priority('low')\ndef me(bot, trigger):\n    \"\"\"\n    Send an ACTION (/me) to a given channel or nick. Can only be done in privmsg\n    by an admin.\n    \"\"\"\n    if trigger.group(2) is None:\n        return\n\n    channel, _sep, action = trigger.group(2).partition(' ')\n    action = action.strip()\n    if not channel or not action:\n        return\n\n    msg = '\\x01ACTION %s\\x01' % action\n    bot.msg(channel, msg)\n\n\n@sopel.module.event('INVITE')\n@sopel.module.rule('.*')\n@sopel.module.priority('low')\ndef invite_join(bot, trigger):\n    \"\"\"\n    Join a channel sopel is invited to, if the inviter is an admin.\n    \"\"\"\n    if trigger.admin or bot.config.admin.auto_accept_invite:\n        bot.join(trigger.args[1])\n        return\n\n\n@sopel.module.event('KICK')\n@sopel.module.rule(r'.*')\n@sopel.module.priority('low')\ndef hold_ground(bot, trigger):\n    \"\"\"\n    This function monitors all kicks across all channels sopel is in. If it\n    detects that it is the one kicked it'll automatically join that channel.\n\n    WARNING: This may not be needed and could cause problems if sopel becomes\n    annoying. Please use this with caution.\n    \"\"\"\n    if bot.config.admin.hold_ground:\n        channel = trigger.sender\n        if trigger.args[1] == bot.nick:\n            bot.join(channel)\n\n\n@sopel.module.require_privmsg\n@sopel.module.require_admin\n@sopel.module.commands('mode')\n@sopel.module.priority('low')\ndef mode(bot, trigger):\n    \"\"\"Set a user mode on Sopel. Can only be done in privmsg by an admin.\"\"\"\n    mode = trigger.group(3)\n    bot.write(('MODE ', bot.nick + ' ' + mode))\n\n\n@sopel.module.require_privmsg(\"This command only works as a private message.\")\n@sopel.module.require_admin(\"This command requires admin privileges.\")\n@sopel.module.commands('set')\n@sopel.module.example('.set core.owner Me')\ndef set_config(bot, trigger):\n    \"\"\"See and modify values of sopels config object.\n\n    Trigger args:\n        arg1 - section and option, in the form \"section.option\"\n        arg2 - value\n\n    If there is no section, section will default to \"core\".\n    If value is None, the option will be deleted.\n    \"\"\"\n    # Get section and option from first argument.\n    arg1 = trigger.group(3).split('.')\n    if len(arg1) == 1:\n        section_name, option = \"core\", arg1[0]\n    elif len(arg1) == 2:\n        section_name, option = arg1\n    else:\n        bot.reply(\"Usage: .set section.option value\")\n        return\n    section = getattr(bot.config, section_name)\n    static_sec = isinstance(section, StaticSection)\n\n    if static_sec and not hasattr(section, option):\n        bot.say('[{}] section has no option {}.'.format(section_name, option))\n        return\n\n    delim = trigger.group(2).find(' ')\n    # Skip preceeding whitespaces, if any.\n    while delim > 0 and delim < len(trigger.group(2)) and trigger.group(2)[delim] == ' ':\n        delim = delim + 1\n\n    # Display current value if no value is given.\n    if delim == -1 or delim == len(trigger.group(2)):\n        if not static_sec and bot.config.parser.has_option(section, option):\n            bot.reply(\"Option %s.%s does not exist.\" % (section_name, option))\n            return\n        # Except if the option looks like a password. Censor those to stop them\n        # from being put on log files.\n        if option.endswith(\"password\") or option.endswith(\"pass\"):\n            value = \"(password censored)\"\n        else:\n            value = getattr(section, option)\n        bot.reply(\"%s.%s = %s\" % (section_name, option, value))\n        return\n\n    # Otherwise, set the value to one given as argument 2.\n    value = trigger.group(2)[delim:]\n    if static_sec:\n        descriptor = getattr(section.__class__, option)\n        try:\n            if isinstance(descriptor, FilenameAttribute):\n                value = descriptor.parse(bot.config, descriptor, value)\n            else:\n                value = descriptor.parse(value)\n        except ValueError as exc:\n            bot.say(\"Can't set attribute: \" + str(exc))\n            return\n    setattr(section, option, value)\n\n\n@sopel.module.require_privmsg\n@sopel.module.require_admin\n@sopel.module.commands('save')\n@sopel.module.example('.save')\ndef save_config(bot, trigger):\n    \"\"\"Save state of sopels config object to the configuration file.\"\"\"\n    bot.config.save()\n", "path": "sopel/modules/admin.py"}]}