mirror of
				https://github.com/Mikaela/Limnoria.git
				synced 2025-11-04 09:37:25 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			440 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
			
		
		
	
	
			440 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
Using the commands.wrap to parse your command's arguments.
 | 
						|
----------------------------------------------------------
 | 
						|
This document illustrates how to use the new 'wrap' function present in Supybot
 | 
						|
0.80 to handle argument parsing and validation for your plugin's commands.
 | 
						|
 | 
						|
Introduction
 | 
						|
============
 | 
						|
  What is 'wrap'?
 | 
						|
 | 
						|
To plugin developers for older (pre-0.80) versions of Supybot, one of the more
 | 
						|
annoying aspects of writing commands was handling the arguments that were
 | 
						|
passed in. In fact, many commands often had to duplicate parsing and
 | 
						|
verification code, resulting in lots of duplicated code for not a whole lot of
 | 
						|
action. So, instead of forcing plugin writers to come up with their own ways of
 | 
						|
cleaning it up, we wrote up the wrap function to handle all of it.
 | 
						|
 | 
						|
It allows a much simpler and more flexible way of checking things than before
 | 
						|
and it doesn't require that you know the bot internals to do things like check
 | 
						|
and see if a user exists, or check if a command name exists and whatnot.
 | 
						|
 | 
						|
If you are a plugin author this document is absolutely required reading, as it
 | 
						|
will massively ease the task of writing commands.
 | 
						|
 | 
						|
Using Wrap
 | 
						|
==========
 | 
						|
  How can I use 'wrap'?
 | 
						|
 | 
						|
First off, to get the wrap function, it is recommended (strongly) that you use
 | 
						|
the following import line:
 | 
						|
 | 
						|
   from supybot.commands import *
 | 
						|
 | 
						|
This will allow you to access the wrap command (and it allows you to do it
 | 
						|
without the commands prefix). Note that this line is added to the imports of
 | 
						|
plugin templates generated by the supybot-plugin-create script.
 | 
						|
 | 
						|
Let's write a quickie command that uses wrap to get a feel for how it makes our
 | 
						|
lives better. Let's write a command that repeats a string of text a given
 | 
						|
number of times. So you could say "repeat 3 foo" and it would say "foofoofoo".
 | 
						|
Not a very useful command, but it will serve our purpose just fine. Here's how
 | 
						|
it would be done without wrap:
 | 
						|
 | 
						|
   def repeat(self, irc, msg, args):
 | 
						|
       """<num> <text>
 | 
						|
 | 
						|
       Repeats <text> <num> times.
 | 
						|
       """
 | 
						|
       (num, text) = privmsg.getArgs(args, required=2)
 | 
						|
       try:
 | 
						|
           num = int(num)
 | 
						|
       except ValueError:
 | 
						|
           raise callbacks.ArgumentError
 | 
						|
       irc.reply(num * text)
 | 
						|
 | 
						|
Note that all of the argument validation and parsing takes up 5 of the 6 lines
 | 
						|
(and you should have seen it before we had privmsg.getArgs!). Now, here's what
 | 
						|
our command will look like with wrap applied:
 | 
						|
 | 
						|
   def repeat(self, irc, msg, args, num, text):
 | 
						|
       """<num> <text>
 | 
						|
 | 
						|
       Repeats <text> <num> times.
 | 
						|
       """
 | 
						|
       irc.reply(text * num)
 | 
						|
   repeat = wrap(repeat, ['int', 'text'])
 | 
						|
 | 
						|
Pretty short, eh? With wrap all of the argument parsing and validation is
 | 
						|
handled for us and we get the arguments we want, formatted how we want them,
 | 
						|
and converted into whatever types we want them to be - all in one simple
 | 
						|
function call that is used to wrap the function! So now the code inside each
 | 
						|
command really deals with how to execute the command and not how to deal with
 | 
						|
the input.
 | 
						|
 | 
						|
So, now that you see the benefits of wrap, let's figure out what stuff we have
 | 
						|
to do to use it.
 | 
						|
 | 
						|
Syntax Changes
 | 
						|
==============
 | 
						|
  How will I need to change my plugin commands to take advantage of 'wrap'?
 | 
						|
 | 
						|
There are two syntax changes to the old style that are implemented. First, the
 | 
						|
definition of the command function must be changed. The basic syntax for the
 | 
						|
new definition is:
 | 
						|
 | 
						|
   def commandname(self, irc, msg, args, <arg1>, <arg2>, ...):
 | 
						|
 | 
						|
Where arg1 and arg2 (up through as many as you want) are the variables that
 | 
						|
will store the parsed arguments. "Now where do these parsed arguments come
 | 
						|
from?" you ask. Well, that's where the second syntax change comes in. The
 | 
						|
second syntax change is the actual use of the wrap function itself to decorate
 | 
						|
our command names. The basic decoration syntax is:
 | 
						|
 | 
						|
   commandname = wrap(commandname, [converter1, converter2, ...])
 | 
						|
 | 
						|
NOTE: This should go on the line immediately following the body of the
 | 
						|
command's definition, so it can easily be located (and it obviously must go
 | 
						|
after the command's definition so that commandname is defined).
 | 
						|
 | 
						|
Each of the converters in the above listing should be one of the converters in
 | 
						|
commands.py (I will describe each of them in detail later.) The converters are
 | 
						|
applied in order to the arguments given to the command, generally taking
 | 
						|
arguments off of the front of the argument list as they go. Note that each of
 | 
						|
the arguments is actually a string containing the NAME of the converter to use
 | 
						|
and not a reference to the actual converter itself. This way we can have
 | 
						|
converters with names like int and not have to worry about polluting the
 | 
						|
builtin namespace by overriding the builtin int.
 | 
						|
 | 
						|
As you will find out when you look through the list of converters below, some
 | 
						|
of the converters actually take arguments. The syntax for supplying them (since
 | 
						|
we aren't actually calling the converters, but simply specifying them), is to
 | 
						|
wrap the converter name and args list into a tuple. For example:
 | 
						|
 | 
						|
 commandname = wrap(commandname, [(converterWithArgs, arg1, arg2),
 | 
						|
                                  converterWithoutArgs1, converterWithoutArgs2])
 | 
						|
 | 
						|
For the most part you won't need to use an argument with the converters you use
 | 
						|
either because the defaults are satisfactory or because it doesn't even take
 | 
						|
any.
 | 
						|
 | 
						|
Customizing Wrap
 | 
						|
================
 | 
						|
  Is there a way to affect how I apply my wraps? We call them contexts.
 | 
						|
 | 
						|
Converters alone are a pretty powerful tool, but for even more advanced (yet
 | 
						|
simpler!) argument handling you may want to use contexts. Contexts describe how
 | 
						|
the converters are applied to the arguments, while the converters themselves
 | 
						|
do the actual parsing and validation.
 | 
						|
 | 
						|
For example, one of the contexts is "optional". By using this context, you're
 | 
						|
saying that a given argument is not required, and if the supplied converter
 | 
						|
doesn't find anything it likes, we should use some default. Yet another
 | 
						|
example is the "reverse" context. This context tells the supplied converter to
 | 
						|
look at the last argument and work backwards instead of the normal
 | 
						|
first-to-last way of looking at arguments.
 | 
						|
 | 
						|
So, that should give you a feel for the role that contexts play. They are not
 | 
						|
by any means necessary to use wrap. All of the stuff we've done to this point
 | 
						|
will work as-is. However, contexts let you do some very powerful things in very
 | 
						|
easy ways, and are a good thing to know how to use.
 | 
						|
 | 
						|
Now, how do you use them? Well, they are in the global namespace of
 | 
						|
src/commands.py, so your previous import line will import them all; you can
 | 
						|
call them just as you call wrap. In fact, the way you use them is you simply
 | 
						|
call the context function you want to use, with the converter (and its
 | 
						|
arguments) as arguments. It's quite simple. Here's an example:
 | 
						|
 | 
						|
   commandname = wrap(commandname, [optional('int'), many('something')])
 | 
						|
 | 
						|
In this example, our command is looking for an optional integer argument first.
 | 
						|
Then, after that, any number of arguments which can be anything (as long as
 | 
						|
they are something, of course).
 | 
						|
 | 
						|
Do note, however, that the type of the arguments that are returned can be
 | 
						|
changed if you apply a context to it. So, optional("int") may very well return
 | 
						|
None as well as something that passes the "int" converter, because after all
 | 
						|
it's an optional argument and if it is None, that signifies that nothing was
 | 
						|
there. Also, for another example, many("something") doesn't return the same
 | 
						|
thing that just "something" would return, but rather a list of "something"s.
 | 
						|
 | 
						|
Converter List
 | 
						|
==============
 | 
						|
  What converters are available for me to use?
 | 
						|
 | 
						|
Below is a list of all the available converters to use with wrap. If the
 | 
						|
converter accepts any arguments, they are listed after it and if they are
 | 
						|
optional, the default value is shown.
 | 
						|
 | 
						|
"id", kind="integer"
 | 
						|
    Returns something that looks like an integer ID number. Takes an optional
 | 
						|
    "kind" argument for you to state what kind of ID you are looking for,
 | 
						|
    though this doesn't affect the integrity-checking. Basically requires that
 | 
						|
    the argument be an integer, does no other integrity-checking, and provides
 | 
						|
    a nice error message with the kind in it.
 | 
						|
 | 
						|
"ip"
 | 
						|
    Checks and makes sure the argument looks like a valid IP and then returns
 | 
						|
    it.
 | 
						|
 | 
						|
"int", type="integer", p=None
 | 
						|
    Gets an integer. The "type" text can be used to customize the error message
 | 
						|
    received when the argument is not an integer. "p" is an optional predicate
 | 
						|
    to test the integer with. If p(i) fails (where i is the integer arg parsed
 | 
						|
    out of the argument string), the arg will not be accepted.
 | 
						|
 | 
						|
"index"
 | 
						|
    Basically ("int", "index"), but with a twist. This will take a 1-based
 | 
						|
    index and turn it into a 0-based index (which is more useful in code). It
 | 
						|
    doesn't transform 0, and it maintains negative indices as is (note that it
 | 
						|
    does allow them!).
 | 
						|
 | 
						|
"color"
 | 
						|
    Accepts arguments that describe a text color code (e.g., "black", "light
 | 
						|
    blue") and returns the mIRC color code for that color. (Note that many
 | 
						|
    other IRC clients support the mIRC color code scheme, not just mIRC)
 | 
						|
 | 
						|
"now"
 | 
						|
    Simply returns the current timestamp as an arg, does not reference or
 | 
						|
    modify the argument list.
 | 
						|
 | 
						|
"url"
 | 
						|
    Checks for a valid URL.
 | 
						|
 | 
						|
"httpUrl"
 | 
						|
    Checks for a valid HTTP URL.
 | 
						|
 | 
						|
"long", type="long"
 | 
						|
    Basically the same as int minus the predicate, except that it converts the
 | 
						|
    argument to a long integer regardless of the size of the int.
 | 
						|
 | 
						|
"float", type="floating point number"
 | 
						|
    Basically the same as int minus the predicate, except that it converts the
 | 
						|
    argument to a float.
 | 
						|
 | 
						|
"nonInt", type="non-integer value"
 | 
						|
    Accepts everything but integers, and returns them unchanged. The "type"
 | 
						|
    value, as always, can be used to customize the error message that is
 | 
						|
    displayed upon failure.
 | 
						|
 | 
						|
"positiveInt"
 | 
						|
    Accepts only positive integers.
 | 
						|
 | 
						|
"nonNegativeInt"
 | 
						|
    Accepts only non-negative integers.
 | 
						|
 | 
						|
"letter"
 | 
						|
    Looks for a single letter. (Technically, it looks for any one-element
 | 
						|
    sequence).
 | 
						|
 | 
						|
"haveOp", action="do that"
 | 
						|
    Simply requires that the bot have ops in the channel that the command is
 | 
						|
    called in. The action parameter completes the error message: "I need to be
 | 
						|
    opped to ...".
 | 
						|
 | 
						|
"expiry"
 | 
						|
    Takes a number of seconds and adds it to the current time to create an
 | 
						|
    expiration timestamp.
 | 
						|
 | 
						|
"literal", literals, errmsg=None
 | 
						|
    Takes a required sequence or string (literals) and any argument that
 | 
						|
    uniquely matches the starting substring of one of the literals is
 | 
						|
    transformed into the full literal. For example, with ("literal", ("bar",
 | 
						|
    "baz", "qux")), you'd get "bar" for "bar", "baz" for "baz", and "qux" for
 | 
						|
    any of "q", "qu", or "qux". "b" and "ba" would raise errors because they
 | 
						|
    don't uniquely identify one of the literals in the list. You can override
 | 
						|
    errmsg to provide a specific (full) error message, otherwise the default
 | 
						|
    argument error message is displayed.
 | 
						|
 | 
						|
"to"
 | 
						|
    Returns the string "to" if the arg is any form of "to" (case-insensitive).
 | 
						|
 | 
						|
"nick"
 | 
						|
    Checks that the arg is a valid nick on the current IRC server.
 | 
						|
 | 
						|
"seenNick"
 | 
						|
    Checks that the arg is a nick that the bot has seen (NOTE: this is limited
 | 
						|
    by the size of the history buffer that the bot has).
 | 
						|
 | 
						|
"channel"
 | 
						|
    Gets a channel to use the command in. If the channel isn't supplied, uses
 | 
						|
    the channel the message was sent in. If using a different channel, does
 | 
						|
    sanity-checking to make sure the channel exists on the current IRC network.
 | 
						|
 | 
						|
"inChannel"
 | 
						|
    Requires that the command be called from within any channel that the bot
 | 
						|
    is currently in or with one of those channels used as an argument to the
 | 
						|
    command.
 | 
						|
 | 
						|
"onlyInChannel"
 | 
						|
    Requires that the command be called from within any channel that the bot
 | 
						|
    is currently in.
 | 
						|
 | 
						|
"nickInChannel"
 | 
						|
    Requires that the argument be a nick that is in the current channel, and
 | 
						|
    returns that nick.
 | 
						|
 | 
						|
"networkIrc", errorIfNoMatch=False
 | 
						|
    Returns the IRC object of the specified IRC network. If one isn't
 | 
						|
    specified, the IRC object of the IRC network the command was called on is
 | 
						|
    returned.
 | 
						|
 | 
						|
"callerInGivenChannel"
 | 
						|
    Takes the given argument as a channel and makes sure that the caller is in
 | 
						|
    that channel.
 | 
						|
 | 
						|
"plugin", require=True
 | 
						|
    Returns the plugin specified by the arg or None. If require is True, an
 | 
						|
    error is raised if the plugin cannot be retrieved.
 | 
						|
 | 
						|
"boolean"
 | 
						|
    Converts the text string to a boolean value. Acceptable true values are:
 | 
						|
    "1", "true", "on", "enable", or "enabled" (case-insensitive). Acceptable
 | 
						|
    false values are: "0", false", "off", "disable", or "disabled"
 | 
						|
    (case-insensitive).
 | 
						|
 | 
						|
"lowered"
 | 
						|
    Returns the argument lowered (NOTE: it is lowered according to IRC
 | 
						|
    conventions, which does strange mapping with some punctuation characters).
 | 
						|
 | 
						|
"anything"
 | 
						|
    Returns anything as is.
 | 
						|
 | 
						|
"something", errorMsg=None, p=None
 | 
						|
    Takes anything but the empty string. errorMsg can be used to customize the
 | 
						|
    error message. p is any predicate function that can be used to test the
 | 
						|
    validity of the input.
 | 
						|
 | 
						|
"filename"
 | 
						|
    Used to get a filename argument.
 | 
						|
 | 
						|
"commandName"
 | 
						|
    Returns the canonical command name version of the given string (ie, the
 | 
						|
    string is lowercased and dashes and underscores are removed).
 | 
						|
 | 
						|
"text"
 | 
						|
    Takes the rest of the arguments as one big string. Note that this differs
 | 
						|
    from the "anything" context in that it clobbers the arg string when it's
 | 
						|
    done.  Using any converters after this is most likely incorrect.
 | 
						|
 | 
						|
"glob"
 | 
						|
    Gets a glob string. Basically, if there are no wildcards ('"*"', "?") in
 | 
						|
    the argument, returns *string*, making a glob string that matches anything
 | 
						|
    containing the given argument.
 | 
						|
 | 
						|
"somethingWithoutSpaces"
 | 
						|
    Same as something, only with the exception of disallowing spaces of course.
 | 
						|
 | 
						|
"capability"
 | 
						|
    Used to retrieve an argument that describes a capability.
 | 
						|
 | 
						|
"channelDb"
 | 
						|
    Sets the channel appropriately in order to get to the databases for that
 | 
						|
    channel (handles whether or not a given channel uses channel-specific
 | 
						|
    databases and whatnot).
 | 
						|
 | 
						|
"hostmask"
 | 
						|
    Returns the hostmask of any provided nick or hostmask argument.
 | 
						|
 | 
						|
"banmask"
 | 
						|
    Returns a generic banmask of the provided nick or hostmask argument.
 | 
						|
 | 
						|
"user"
 | 
						|
    Requires that the caller be a registered user.
 | 
						|
 | 
						|
"matches", regexp, errmsg
 | 
						|
    Searches the args with the given regexp and returns the matches. If no
 | 
						|
    match is found, errmsg is given.
 | 
						|
 | 
						|
"public"
 | 
						|
    Requires that the command be sent in a channel instead of a private
 | 
						|
    message.
 | 
						|
 | 
						|
"private"
 | 
						|
    Requires that the command be sent in a private message instead of a
 | 
						|
    channel.
 | 
						|
 | 
						|
"otherUser"
 | 
						|
    Returns the user specified by the username or hostmask in the argument.
 | 
						|
 | 
						|
"regexpMatcher"
 | 
						|
    Gets a matching regexp argument (m// or //).
 | 
						|
 | 
						|
"validChannel"
 | 
						|
    Gets a channel argument once it makes sure it's a valid channel.
 | 
						|
 | 
						|
"regexpReplacer"
 | 
						|
    Gets a replacing regexp argument (s//).
 | 
						|
 | 
						|
"owner"
 | 
						|
    Requires that the command caller has the "owner" capability.
 | 
						|
 | 
						|
"admin"
 | 
						|
    Requires that the command caller has the "admin" capability.
 | 
						|
 | 
						|
"checkCapability", capability
 | 
						|
    Checks to make sure that the caller has the specified capability.
 | 
						|
 | 
						|
"checkChannelCapability", capability
 | 
						|
    Checks to make sure that the caller has the specified capability on the
 | 
						|
    channel the command is called in.
 | 
						|
 | 
						|
Contexts List
 | 
						|
=============
 | 
						|
  What contexts are available for me to use?
 | 
						|
 | 
						|
The list of available contexts is below. Unless specified otherwise, it can be
 | 
						|
assumed that the type returned by the context itself matches the type of the
 | 
						|
converter it is applied to.
 | 
						|
 | 
						|
any
 | 
						|
    Looks for any number of arguments matching the supplied converter. Will
 | 
						|
    return a sequence of converted arguments or None.
 | 
						|
 | 
						|
many
 | 
						|
    Looks for multiple arguments matching the supplied converter. Expects at
 | 
						|
    least one to work, otherwise it will fail. Will return the sequence of
 | 
						|
    converted arguments.
 | 
						|
 | 
						|
optional
 | 
						|
    Look for an argument that satisfies the supplied converter, but if it's not
 | 
						|
    the type I'm expecting or there are no arguments for us to check, then use
 | 
						|
    the default value. Will return the converted argument as is or None.
 | 
						|
 | 
						|
additional
 | 
						|
    Look for an argument that satisfies the supplied converter, making sure
 | 
						|
    that it's the right type. If there aren't any arguments to check, then use
 | 
						|
    the default value. Will return the converted argument as is or None.
 | 
						|
 | 
						|
rest
 | 
						|
    Treat the rest of the arguments as one big string, and then convert. If the
 | 
						|
    conversion is unsuccessful, restores the arguments.
 | 
						|
 | 
						|
getopts
 | 
						|
    Handles --option style arguments. Each option should be a key in a
 | 
						|
    dictionary that maps to the name of the converter that is to be used on
 | 
						|
    that argument. To make the option take no argument, use "" as the converter
 | 
						|
    name in the dictionary. For no conversion, use None as the converter name
 | 
						|
    in the dictionary.
 | 
						|
 | 
						|
first
 | 
						|
    Tries each of the supplied converters in order and returns the result of
 | 
						|
    the first successfully applied converter.
 | 
						|
 | 
						|
reverse
 | 
						|
    Reverse the argument list, apply the converters, and then reverse the
 | 
						|
    argument list back.
 | 
						|
 | 
						|
commalist
 | 
						|
    Looks for a comma separated list of arguments that match the supplied
 | 
						|
    converter. Returns a list of the successfully converted arguments. If any
 | 
						|
    of the arguments fail, this whole context fails.
 | 
						|
 | 
						|
 | 
						|
Final Word
 | 
						|
==========
 | 
						|
 | 
						|
Now that you know how to use wrap, and you have a list of converters and
 | 
						|
contexts you can use, your task of writing clean, simple, and safe plugin code
 | 
						|
should become much easier. Enjoy!
 | 
						|
 |