mirror of
https://github.com/Mikaela/Limnoria.git
synced 2024-11-30 23:09:23 +01:00
a66034f852
Signed-off-by: James Vega <jamessan@users.sourceforge.net>
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!
|
|
|