mirror of
https://github.com/Mikaela/Limnoria.git
synced 2024-11-16 07:29:23 +01:00
178f70f946
Signed-off-by: James Vega <jamessan@users.sourceforge.net>
438 lines
17 KiB
Plaintext
438 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
|
|
actually 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 something 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, so 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, passing in the converter and its
|
|
arguments in 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!
|
|
|