mirror of
https://github.com/Mikaela/Limnoria.git
synced 2024-11-30 14:59:34 +01:00
343 lines
16 KiB
Plaintext
343 lines
16 KiB
Plaintext
|
Advanced Plugin Config
|
||
|
----------------------
|
||
|
This tutorial covers some of the more advanced plugin config features available
|
||
|
to Supybot plugin authors.
|
||
|
|
||
|
What's This Tutorial For?
|
||
|
=========================
|
||
|
Brief overview of what this tutorial covers and the target audience.
|
||
|
|
||
|
Want to know the crazy advanced features available to you, the Supybot plugin
|
||
|
author? Well, this is the tutorial for you. This article assumes you've read
|
||
|
the Supybot plugin author tutorial since all the basics of plugin config are
|
||
|
handled there first.
|
||
|
|
||
|
In this tutorial we'll cover:
|
||
|
|
||
|
* Using the configure function more effectively by using the functions
|
||
|
provided in supybot.questions
|
||
|
* Creating config variable groups and config variables underneath those
|
||
|
groups.
|
||
|
* The built-in config variable types ("registry types") for use with config
|
||
|
variables
|
||
|
* Creating custom registry types to handle config variable values more
|
||
|
effectively
|
||
|
|
||
|
Using 'configure' effectively
|
||
|
=============================
|
||
|
How to use 'configure' effectively using the functions from
|
||
|
'supybot.questions'
|
||
|
|
||
|
In the original Supybot plugin author tutorial you'll note that we gloss over
|
||
|
the configure portion of the config.py file for the sake of keeping the
|
||
|
tutorial to a reasonable length. Well, now we're going to cover it in more
|
||
|
detail.
|
||
|
|
||
|
The supybot.questions module is a nice little module coded specifically to help
|
||
|
clean up the configure section of every plugin's config.py. The boilerplate
|
||
|
config.py code imports the four most useful functions from that module:
|
||
|
|
||
|
* "expect" is a very general prompting mechanism which can specify certain
|
||
|
inputs that it will accept and also specify a default response. It takes
|
||
|
the following arguments:
|
||
|
- prompt: The text to be displayed
|
||
|
- possibilities: The list of possible responses (can be the empty
|
||
|
list, [])
|
||
|
- default (optional): Defaults to None. Specifies the default value
|
||
|
to use if the user enters in no input.
|
||
|
- acceptEmpty (optional): Defaults to False. Specifies whether or not
|
||
|
to accept no input as an answer.
|
||
|
|
||
|
* "anything" is basically a special case of except which takes anything
|
||
|
(including no input) and has no default value specified. It takes only
|
||
|
one argument:
|
||
|
- prompt: The text to be displayed
|
||
|
|
||
|
* "something" is also a special case of except, requiring some input and
|
||
|
allowing an optional default. It takes the following arguments:
|
||
|
- prompt: The text to be displayed
|
||
|
- default (optional): Defaults to None. The default value to use if
|
||
|
the user doesn't input anything.
|
||
|
|
||
|
* "yn" is for "yes or no" questions and basically forces the user to input
|
||
|
a "y" for yes, or "n" for no. It takes the following arguments:
|
||
|
- prompt: The text to be displayed
|
||
|
- default (optional): Defaults to None. Default value to use if the
|
||
|
user doesn't put anything.
|
||
|
|
||
|
All of these functions, with the exception of "yn", return whatever string
|
||
|
results as the answer whether it be input from the user or specified as the
|
||
|
default when the user inputs nothing. The "yn" function returns True for "yes"
|
||
|
answers and False for "no" answers.
|
||
|
|
||
|
For the most part, the latter three should be sufficient, but we expose expect
|
||
|
to anyone who needs a more specialized configuration.
|
||
|
|
||
|
Let's go through a quick example configure that covers all four of these
|
||
|
functions. First I'll give you the code, and then we'll go through it,
|
||
|
discussing each usage of a supybot.questions function just to make sure you
|
||
|
realize what the code is actually doing. Here it is:
|
||
|
|
||
|
def configure(advanced):
|
||
|
# This will be called by supybot to configure this module. advanced is
|
||
|
# a bool that specifies whether the user identified himself as an advanced
|
||
|
# user or not. You should effect your configuration by manipulating the
|
||
|
# registry as appropriate.
|
||
|
from supybot.questions import expect, anything, something, yn
|
||
|
WorldDom = conf.registerPlugin('WorldDom', True)
|
||
|
if yn("""The WorldDom plugin allows for total world domination
|
||
|
with simple commands. Would you like these commands to
|
||
|
be enabled for everyone?""", default=False):
|
||
|
WorldDom.globalWorldDominationRequires.setValue("")
|
||
|
else:
|
||
|
cap = something("""What capability would you like to require for
|
||
|
this command to be used?""", default="Admin")
|
||
|
WorldDom.globalWorldDominationRequires.setValue(cap)
|
||
|
dir = expect("""What direction would you like to attack from in
|
||
|
your quest for world domination?""",
|
||
|
["north", "south", "east", "west", "ABOVE"],
|
||
|
default="ABOVE")
|
||
|
WorldDom.attackDirection.setValue(dir)
|
||
|
|
||
|
As you can see, this is the WorldDom plugin, which I am currently working on.
|
||
|
The first thing our configure function checks is to see whether or not the bot
|
||
|
owner would like the world domination commands in this plugin to be available
|
||
|
to everyone. If they say yes, we set the globalWorldDominationRequires
|
||
|
configuration variable to the empty string, signifying that no specific
|
||
|
capabilities are necessary. If they say no, we prompt them for a specific
|
||
|
capability to check for, defaulting to the "Admin" capability. Here they can
|
||
|
create their own custom capability to grant to folks which this plugin will
|
||
|
check for if they want, but luckily for the bot owner they don't really have to
|
||
|
do this since Supybot's capabilities system can be flexed to take care of this.
|
||
|
|
||
|
Lastly, we check to find out what direction they want to attack from as they
|
||
|
venture towards world domination. I prefer "death from above!", so I made that
|
||
|
the default response, but the more boring cardinal directions are available as
|
||
|
choices as well.
|
||
|
|
||
|
Using Config Groups
|
||
|
===================
|
||
|
A brief overview of how to use config groups to organize config variables
|
||
|
|
||
|
Supybot's Hierarchical Configuration
|
||
|
|
||
|
Supybot's configuration is inherently hierarchical, as you've probably already
|
||
|
figured out in your use of the bot. Naturally, it makes sense to allow plugin
|
||
|
authors to create their own hierarchies to organize their configuration
|
||
|
variables for plugins that have a lot of plugin options. If you've taken a look
|
||
|
at the plugins that Supybot comes with, you've probably noticed that several of
|
||
|
them take advantage of this. In this section of this tutorial we'll go over how
|
||
|
to make your own config hierarchy for your plugin.
|
||
|
|
||
|
Here's the brilliant part about Supybot config values which makes hierarchical
|
||
|
structuring all that much easier - values are groups. That is, any config value
|
||
|
you may already defined in your plugins can already be treated as a group, you
|
||
|
simply need to know how to add items to that group.
|
||
|
|
||
|
Now, if you want to just create a group that doesn't have an inherent value you
|
||
|
can do that as well, but you'd be surprised at how rarely you have to do that.
|
||
|
In fact if you look at most of the plugins that Supybot comes with, you'll only
|
||
|
find that we do this in a handful of spots yet we use the "values as groups"
|
||
|
feature quite a bit.
|
||
|
|
||
|
Creating a Config Group
|
||
|
=======================
|
||
|
|
||
|
As stated before, config variables themselves are groups, so you can create a
|
||
|
group simply by creating a configuration variable:
|
||
|
|
||
|
conf.registerGlobalValue(WorldDom, 'globalWorldDominationRequires',
|
||
|
registry.String('', """Determines the capability required to access the
|
||
|
world domination commands in this plugin."""))
|
||
|
|
||
|
As you probably know by now this creates the config variable
|
||
|
supybot.plugins.WorldDom.globalWorldDominationRequires which you can access/set
|
||
|
using the Config plugin directly on the running bot. What you may not have
|
||
|
known prior to this tutorial is that that variable is also a group.
|
||
|
Specifically, it is now the WorldDom.globalWorldDominationRequires group, and
|
||
|
we can add config variables to it! Unfortunately, this particular bit of
|
||
|
configuration doesn't really require anything underneath it, so let's create a
|
||
|
new group which does using the "create only a group, not a value" command.
|
||
|
|
||
|
Let's create a configurable list of targets for different types of attacks
|
||
|
(land, sea, air, etc.). We'll call the group attackTargets. Here's how you
|
||
|
create just a config group alone with no value assigned:
|
||
|
|
||
|
conf.registerGroup(WorldDom, 'attackTargets')
|
||
|
|
||
|
The first argument is just the group under which you want to create your new
|
||
|
group (and we got WorldDom from conf.registerPlugin which was in our
|
||
|
boilerplate code from the plugin creation wizard). The second argument is, of
|
||
|
course, the group name. So now we have WorldDom.attackTargets (or, fully,
|
||
|
supybot.plugins.WorldDom.attackTargets).
|
||
|
|
||
|
Adding Values to a Group
|
||
|
========================
|
||
|
|
||
|
Actually, you've already done this several times, just never to a custom group
|
||
|
of your own. You've always added config values to your plugin's config group.
|
||
|
With that in mind, the only slight modification needed is to simply point to
|
||
|
the new group:
|
||
|
|
||
|
conf.registerGlobalValue(WorldDom.attackTargets, 'air',
|
||
|
registry.SpaceSeparatedListOfStrings('', """Contains the list of air
|
||
|
targets."""))
|
||
|
|
||
|
And now we have a nice list of air targets! You'll notice that the first
|
||
|
argument is WorldDom.attackTargets, our new group. Make sure that the
|
||
|
conf.registerGroup call is made before this one or else you'll get a nasty
|
||
|
AttributeError.
|
||
|
|
||
|
The Built-in Registry Types
|
||
|
===========================
|
||
|
A rundown of all of the built-in registry types available for use with config
|
||
|
variables.
|
||
|
|
||
|
The "registry" module defines the following config variable types for your use
|
||
|
(I'll include the 'registry.' on each one since that's how you'll refer to it in
|
||
|
code most often). Most of them are fairly self-explanatory, so excuse the
|
||
|
boring descriptions:
|
||
|
|
||
|
* registry.Boolean - A simple true or false value. Also accepts the
|
||
|
following for true: "true", "on" "enable", "enabled", "1", and the
|
||
|
following for false: "false", "off", "disable", "disabled", "0",
|
||
|
|
||
|
* registry.Integer - Accepts any integer value, positive or negative.
|
||
|
|
||
|
* registry.NonNegativeInteger - Will hold any non-negative integer value.
|
||
|
|
||
|
* registry.PositiveInteger - Same as above, except that it doesn't accept 0
|
||
|
as a value.
|
||
|
|
||
|
* registry.Float - Accepts any floating point number.
|
||
|
|
||
|
* registry.PositiveFloat - Accepts any positive floating point number.
|
||
|
|
||
|
* registry.Probability - Accepts any floating point number between 0 and 1
|
||
|
(inclusive, meaning 0 and 1 are also valid).
|
||
|
|
||
|
* registry.String - Accepts any string that is not a valid Python command
|
||
|
|
||
|
* registry.NormalizedString - Accepts any string (with the same exception
|
||
|
above) but will normalize sequential whitespace to a single space..
|
||
|
|
||
|
* registry.StringSurroundedBySpaces - Accepts any string but assures that
|
||
|
it has a space preceding and following it. Useful for configuring a
|
||
|
string that goes in the middle of a response.
|
||
|
|
||
|
* registry.StringWithSpaceOnRight - Also accepts any string but assures
|
||
|
that it has a space after it. Useful for configuring a string that
|
||
|
begins a response.
|
||
|
|
||
|
* registry.Regexp - Accepts only valid (Perl or Python) regular expressions
|
||
|
|
||
|
* registry.SpaceSeparatedListOfStrings - Accepts a space-separated list of
|
||
|
strings.
|
||
|
|
||
|
There are a few other built-in registry types that are available but are not
|
||
|
usable in their current state, only by creating custom registry types, which
|
||
|
we'll go over in the next section.
|
||
|
|
||
|
Custom Registry Types
|
||
|
=====================
|
||
|
How to create and use your own custom registry types for use in customizing
|
||
|
plugin config variables.
|
||
|
|
||
|
Why Create Custom Registry Types?
|
||
|
|
||
|
For most configuration, the provided types in the registry module are
|
||
|
sufficient. However, for some configuration variables it's not only convenient
|
||
|
to use custom registry types, it's actually recommended. Customizing registry
|
||
|
types allows for tighter restrictions on the values that get set and for
|
||
|
greater error-checking than is possible with the provided types.
|
||
|
|
||
|
What Defines a Registry Type?
|
||
|
|
||
|
First and foremost, it needs to subclass one of the existing registry types
|
||
|
from the registry module, whether it be one of the ones in the previous section
|
||
|
or one of the other classes in registry specifically designed to be subclassed.
|
||
|
|
||
|
Also it defines a number of other nice things: a custom error message for your
|
||
|
type, customized value-setting (transforming the data you get into something
|
||
|
else if wanted), etc.
|
||
|
|
||
|
Creating Your First Custom Registry Type
|
||
|
|
||
|
As stated above, priority number one is that you subclass one of the types in
|
||
|
the registry module. Basically, you just subclass one of those and then
|
||
|
customize whatever you want. Then you can use it all you want in your own
|
||
|
plugins. We'll do a quick example to demonstrate.
|
||
|
|
||
|
We already have registry.Integer and registry.PositiveInteger, but let's say we
|
||
|
want to accept only negative integers. We can create our own NegativeInteger
|
||
|
registry type like so:
|
||
|
|
||
|
class NegativeInteger(registry.Integer):
|
||
|
"""Value must be a negative integer."""
|
||
|
def setValue(self, v):
|
||
|
if v >= 0:
|
||
|
self.error()
|
||
|
registry.Integer.setValue(self, v)
|
||
|
|
||
|
All we need to do is define a new error message for our custom registry type
|
||
|
(specified by the docstring for the class), and customize the setValue
|
||
|
function. Note that all you have to do when you want to signify that you've
|
||
|
gotten an invalid value is to call self.error(). Finally, we call the parent
|
||
|
class's setValue to actually set the value.
|
||
|
|
||
|
What Else Can I Customize?
|
||
|
|
||
|
Well, the error string and the setValue function are the most useful things
|
||
|
that are available for customization, but there are other things. For examples,
|
||
|
look at the actual built-in registry types defined in registry.py (in the src
|
||
|
directory distributed with the bot).
|
||
|
|
||
|
What Subclasses Can I Use?
|
||
|
|
||
|
Chances are one of the built-in types in the previous section will be
|
||
|
sufficient, but there are a few others of note which deserve mention:
|
||
|
|
||
|
* registry.Value - Provides all the core functionality of registry types
|
||
|
(including acting as a group for other config variables to reside
|
||
|
underneath), but nothing more.
|
||
|
|
||
|
* registry.OnlySomeStrings - Allows you to specify only a certain set of
|
||
|
strings as valid values. Simply override validStrings in the inheriting
|
||
|
class and you're ready to go.
|
||
|
|
||
|
* registry.SeparatedListOf - The generic class which is the parent class to
|
||
|
registry.SpaceSeparatedListOfStrings. Allows you to customize four
|
||
|
things: the type of sequence it is (list, set, tuple, etc.), what each
|
||
|
item must be (String, Boolean, etc.), what separates each item in the
|
||
|
sequence (using custom splitter/joiner functions), and whether or not
|
||
|
the sequence is to be sorted. Look at the definitions of
|
||
|
registry.SpaceSeparatedListOfStrings and
|
||
|
registry.CommaSeparatedListOfStrings at the bottom of registry.py for
|
||
|
more information. Also, there will be an example using this in the
|
||
|
section below.
|
||
|
|
||
|
Using My Custom Registry Type
|
||
|
|
||
|
Using your new registry type is relatively straightforward. Instead of using
|
||
|
whatever registry built-in you might have used before, now use your own custom
|
||
|
class. Let's say we define a registry type to handle a comma-separated list of
|
||
|
probabilities:
|
||
|
|
||
|
class CommaSeparatedListOfProbabilities(registry.SeparatedListOf):
|
||
|
Value = registry.Probability
|
||
|
def splitter(self, s):
|
||
|
return re.split(r'\s*,\s*', s)
|
||
|
joiner = ', '.join
|
||
|
|
||
|
Now, to use that type we simply have to specify it whenever we create a config
|
||
|
variable using it:
|
||
|
|
||
|
conf.registerGlobalValue(SomePlugin, 'someConfVar',
|
||
|
CommaSeparatedListOfProbabilities('0.0, 1.0', """Holds the list of
|
||
|
probabilities for whatever."""))
|
||
|
|
||
|
Note that we initialize it just the same as we do any other registry type, with
|
||
|
two arguments: the default value, and then the description of the config
|
||
|
variable.
|
||
|
|