mirror of
				https://github.com/Mikaela/Limnoria.git
				synced 2025-11-03 17:17:23 +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 expect 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 expect, 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 input 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.
 | 
						|
 |