More updating Docbook stuff

This commit is contained in:
Daniel DiPaolo 2004-09-05 20:05:25 +00:00
parent a4b3d66748
commit b5d7045359
2 changed files with 90 additions and 171 deletions

View File

@ -36,6 +36,11 @@
<revnumber>0.4</revnumber>
<date>26 Feb 2004</date>
<revremark>Converted to use Supybot DTD</revremark>
</revision>
<revision>
<revnumber>0.5</revnumber>
<date>4 Sep 2004</date>
<revremark>Updated Docbook translation</revremark>
</revision>
</revhistory>
</articleinfo>
@ -56,7 +61,6 @@
details.
</para>
</sect1>
<sect1>
<title>Creating your own plugin</title>
<sect2>
@ -105,7 +109,7 @@ functor%
#!/usr/bin/env python
###
# Copyright (c) 2002, Jeremiah Fincher
# Copyright (c) 2004, Jeremiah Fincher
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
@ -137,20 +141,24 @@ functor%
Add the module docstring here. This will be used by the setup.py script.
"""
from baseplugin import *
__revision__ = "$Id$"
__author__ = ''
import utils
import privmsgs
import callbacks
import supybot.plugins as plugins
import supybot.conf as conf
import supybot.utils as utils
import supybot.privmsgs as privmsgs
import supybot.callbacks as callbacks
def configure(onStart, afterConnect, advanced):
# This will be called by setup.py to configure this module. onStart and
# afterConnect are both lists. Append to onStart the commands you would
# like to be run when the bot is started; append to afterConnect the
# commands you would like to be run when the bot has finished connecting.
# This will be called by setup.py 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 questions import expect, anything, something, yn
onStart.append('load Random')
conf.registerPlugin('Random', True)
class Random(callbacks.Privmsg):
pass
@ -193,10 +201,8 @@ Class = Random
Then you see a <function>configure</function> function. This
the function that's called when users decide to add your
module in <script>scripts/setup.py</script>. You'll
note that by default it simply adds <literal>"load
Example"</literal> (where 'Example' is the name you provided
as the name of your plugin, so in our case it is
<literal>"load Random"</literal>) at the bottom. For many
note that by default it simply registers the plugin to be
automatically loaded on startup. For many
plugins this is all you need; for more complex plugins, you
might need to ask questions and add commands based on the
answers.
@ -248,10 +254,8 @@ def __init__(self):
any arguments (other than <varname>self</varname>, of course).
There's no way anything will ever get to them! If you have
some sort of initial values you need to get to your plugin
before it can do anything interesting, add a command that gets
those values. By convention, those commands begin with
"start" -- check out the Relay and Enforcer plugins for
examples of such commands.
before it can do anything interesting, you should get those
values from the registry.
</para>
<para>
There's an easier way to get our plugin to have its own rng
@ -285,7 +289,7 @@ def __init__(self):
Returns the next random number generated by the random number
generator.
"""
irc.reply(msg, str(self.rng.random()))
irc.reply(str(self.rng.random()))
</programlisting>
<para>
And that's it! Pretty simple, huh? Anyway, you're probably
@ -297,7 +301,7 @@ def __init__(self):
</programlisting>
<para>
What that does is define a command
<function>random</function>. You can call it by saying
<botcommand>random</botcommand>. You can call it by saying
"@random" (or whatever prefix character your specific bot
uses). The arguments are a bit less obvious.
<varname>self</varname> is self-evident (hah!).
@ -308,22 +312,20 @@ def __init__(self):
(with the exception of calling <function>irc.reply</function>
or <function>irc.error</function>). What you're
<emphasis>really</emphasis> interested in is the
<varname>args</varname> arg. That if a list of all the
<varname>args</varname> arg. That is a list of all the
arguments passed to your command, pre-parsed and already
evaluated (i.e., you never have to worry about nested
commands, or handling double quoted strings, or splitting on
whitespace -- the work has already been done for you). You
can read about the <classname>Irc</classname> object in
whitespace &ndash; the work has already been done for you).
You can read about the <classname>Irc</classname> object in
<filename>irclib.py</filename> (you won't find
<function>.reply</function> or <function>.error</function>
there, though, because you're actually getting an
<classname>IrcObjectProxy</classname>, but that's beyond the
level we want to describe here :)). You can read about the
<varname>msg</varname> object in
<filename>ircmsgs.py</filename>. But again, aside from
calling <function>irc.reply</function> or
<function>irc.error</function>, you'll very rarely be using
these objects.
<filename>ircmsgs.py</filename>. But again, you'll very
rarely be using these objects.
</para>
<para>
(In case you're curious, the answer is yes, you
@ -348,39 +350,28 @@ def __init__(self):
this is what a supybot does:
</para>
<ircsession>
&lt;angryman&gt; jemfinch: random takes no arguments (for more help
use the morehelp command)
&lt;jemfinch&gt; $morehelp random
&lt;angryman&gt; jemfinch: Returns the next random number from the
current random number generator.
&lt;jemfinch&gt; @help random
&lt;angryman&gt; jemfinch: (random takes no arguments) -- Returns the
next random number from the random number generator.
</ircsession>
<para>
'help &lt;command&gt;' replies with the command name followed
by the first line of the command's docstring; there should be
a blank line following, and then 'morehelp &lt;command&gt;'
will reply with the remainder of the docstring. So that
explains the docstring. Now on to the actual body of the
function:
Now on to the actual body of the function:
</para>
<programlisting>
irc.reply(msg, str(self.rng.random()))
</programlisting>
<para>
<function>irc.reply</function> takes two arguments, an
<classname>IrcMsg</classname> (like the one passed into your
function) and a string. The <classname>IrcMsg</classname> is
used to determine who the reply should go to and whether or
not it should be sent in private message (commands sent in
private are replied to in private). The string is the reply
to be sent. Don't worry about length restrictions or anything
-- if the string you want to send is too big for an IRC
<function>irc.reply</function> simply takes one simple
argument: a string The string is the reply to be sent. Don't
worry about length restrictions or anything
&ndash; if the string you want to send is too big for an IRC
message (and oftentimes that turns out to be the case :)) the
supybot framework handles that entirely transparently to you.
Supybot framework handles that entirely transparently to you.
Do make sure, however, that you give
<function>irc.reply</function> a string. It doesn't take
anything else (sometimes even unicode fails!). That's why we
have "str(self.rng.random())" instead of simply
"self.rng.random()" -- we had to give
"self.rng.random()" &ndash; we had to give
<function>irc.reply</function> a string.
</para>
<para>
@ -406,14 +397,15 @@ def __init__(self):
irc.error(msg, '&lt;seed&gt; must be a valid int or long.')
return
self.rng.seed(seed)
irc.reply(msg, conf.replySuccess)
irc.replySuccess()
</programlisting>
<para>
So this one's a bit more complicated. But it's still pretty
simple. The method name is "seed" so that'll be the command
name. The arguments are the same, the docstring is of the
same form, so we don't need to go over that again. The body
of the function, however, is significantly different.
simple. The method name is <botcommand>seed</botcommand> so
that'll be the command name. The arguments are the same, the
docstring is of the same form, so we don't need to go over
that again. The body of the function, however, is
significantly different.
</para>
<para>
<function>privmsgs.getArgs</function> is a function you're
@ -444,26 +436,25 @@ def __init__(self):
<function>irc.error</function>. It has the same interface as
we saw before in <function>irc.reply</function>, but it makes
sure to remind the user that an error has been encountered
(currently, that means it puts "Error: " at the beginning of
the message). After erroring, we return. It's important to
remember this <keyword>return</keyword> here; otherwise,
we'll just keep going down through the function and try to use
this "seed" variable that never got assigned. A good general
rule of thumb is that any time you use
<function>irc.error</function>, you'll want to return
immediately afterwards.
(currently, that means it puts <literal>"Error: "</literal> at
the beginning of the message). After erroring, we return.
It's important to remember this <keyword>return</keyword>
here; otherwise, we'll just keep going down through the
function and try to use this <varname>seed</varname> variable
that never got assigned. A good general rule of thumb is that
any time you use <function>irc.error</function>, you'll want
to return immediately afterwards.
</para>
<para>
Then we set the seed -- that's a simple function on our rng
object. Assuming that succeeds (and doesn't raise an
Then we set the seed &ndash; that's a simple function on our
rng object. Assuming that succeeds (and doesn't raise an
exception, which it shouldn't, because we already read the
documentation and know that it should work) we reply to say
that everything worked fine. That's what
<varname>conf.replySuccess</varname> says. By default, it has
the very dry (and appropriately robot-like) "The operation
<function>irc.replySuccess</function> says. By default, it
has the very dry (and appropriately robot-like) "The operation
succeeded." but you're perfectly welcome to customize it
yourself -- <filename>conf.py</filename> was written to be
modified!
yourself &ndash; the registry was written to be modified!
</para>
<para>
So that's a bit more complicated command. But we still
@ -529,14 +520,14 @@ def __init__(self):
except IndexError: # raised by .pop(0)
raise callbacks.ArgumentError
except ValueError:
irc.error(msg, '&lt;number of items&gt; must be an integer.')
irc.error('&lt;number of items&gt; must be an integer.')
return
if n &gt; len(args):
irc.error(msg, '&lt;number of items&gt; must be less than the number '
irc.error('&lt;number of items&gt; must be less than the number '
'of arguments.')
return
sample = self.rng.sample(args, n)
irc.reply(msg, utils.commaAndify(map(repr, sample)))
irc.reply(utils.commaAndify(map(repr, sample)))
</programlisting>
<para>
Most everything here is familiar. The difference between this
@ -592,8 +583,7 @@ def __init__(self):
irc.error(msg, 'Dice have integer numbers of sides. Use one.')
return
s = 'rolls a %s' % self.rng.randrange(1, n+1)
irc.queueMsg(ircmsgs.action(ircutils.replyTo(msg), s))
raise callbacks.CannotNest
irc.reply(s, action=True)
</programlisting>
<para>
There's a lot of stuff you haven't seen before in there. The
@ -611,33 +601,10 @@ def __init__(self):
= 6", where we provide the default.
</para>
<para>
Later, though, you'll see something other than
<function>irc.reply</function>. This is
<function>irc.queueMsg</function>, the general interface for
sending messages to the server. It's what
<function>irc.reply</function> is using under the covers. It
takes an <classname>IrcMsg</classname> object. Fortunately,
that's exactly what's returned by
<function>ircmsgs.action</function>. An action message, just
in case you don't know, is a /me kind of message.
<function>ircmsgs.action</function> is a helper function that
takes a target (a place to send the message, either a channel
or a person) and a payload (the thing to /me) and returns the
appropriate <classname>IrcMsg</classname> object.
<function>ircutils.replyTo</function> simply takes an
<classname>IrcMsg</classname> and returns where we should
reply to; if the message was originally sent to a channel,
we'll reply to there, if it was originally sent to us
privately, we'll reply in private.
</para>
<para>
At the end, you might be surprised by the "raise
callbacks.CannotNest". That's used simply because at the
moment you can't nest actions (just like you can't nest
anything that doesn't go through
<function>irc.reply</function>). That raise just makes sure
the user finds this out if he tries to nest this like "@rot13
[diceroll]".
You'll also note that <function>irc.reply</function> was given
a keyword argument here, <varname>action</varname>. This
means that the reply is to be made as an action rather than a
normal reply.
</para>
<para>
So that's our plugin. 5 commands, each building in
@ -649,59 +616,12 @@ def __init__(self):
</para>
</sect2>
<sect2>
<title>Finishing touches</title>
<title>Using the registry in your plugin</title>
<para>
Let's take a look at that <function>configure</function>
function <script>scripts/newplugin.py</script> made
for us. Here it is, in case you've forgotten:
</para>
<programlisting>
def configure(onStart, afterConnect, advanced):
# This will be called by setup.py to configure this module. onStart and
# afterConnect are both lists. Append to onStart the commands you would
# like to be run when the bot is started; append to afterConnect the
# commands you would like to be run when the bot has finished connecting.
from questions import expect, anything, something, yn
onStart.append('load Random')
</programlisting>
<para>
You remember when you first started running supybot and ran
<script>scripts/setup.py</script> and it asked you
all those questions? Well, now's your chance to ask other
users some questions of your own. In our case, with our
<plugin>Random</plugin> plugin, it might be nice to offer
the user the ability to specify a seed to use whenever the
plugin is loaded. So let's ask him if he wants to do that,
and if so, let's ask him what the seed should be.
</para>
<programlisting>
def configure(onStart, afterConnect, advanced):
# This will be called by setup.py to configure this module. onStart and
# afterConnect are both lists. Append to onStart the commands you would
# like to be run when the bot is started; append to afterConnect the
# commands you would like to be run when the bot has finished connecting.
from questions import expect, anything, something, yn
onStart.append('load Random')
if yn('Do you want to specify a seed to be used for the RNG')=='y':
seed = something('What seed? It must be an int or long.')
while not seed.isdigit():
print 'That\'s not a valid seed.'
seed = something('What seed?')
onStart.append('seed %s' % seed)
</programlisting>
<para>
As you can see, what the <module>questions</module> module
does is fairly self-evident: <function>yn</function> returns
either 'y' or 'n'; <function>something</function> returns
<emphasis>something</emphasis> (but not nothing; for nothing,
you'd want <function>anything</function>). So basically we
ask some questions until we get a good seed. Then we do this
"onStart.append('seed %s' % seed)" doohickey.
<varname>onStart</varname> is a list of the commands to run
when the bot starts; we're just throwing our little piece into
it. These commands will then be written into the template
<script>scripts/setup.py</script> creates for the bot.
TODO: Describe the registry and how to write a proper plugin
configure function.
</para>
</sect2>
<para>
We've written our own plugin from scratch (well, from the
boilerplate that we got from
@ -709,6 +629,5 @@ def configure(onStart, afterConnect, advanced):
survived! Now go write more plugins for supybot, and send
them to me so I can use them too :)
</para>
</sect2>
</sect1>
</article>

View File

@ -304,9 +304,9 @@ Then we set the seed -- that's a simple function on our rng object.
Assuming that succeeds (and doesn't raise an exception, which it
shouldn't, because we already read the documentation and know that it
should work) we reply to say that everything worked fine. That's what
conf.replySuccess says. By default, it has the very dry (and
irc.replySuccess says. By default, it has the very dry (and
appropriately robot-like) "The operation succeeded." but you're
perfectly welcome to customize it yourself -- conf.py was written to
perfectly welcome to customize it yourself -- the registry was written to
be modified!
So that's a bit more complicated command. But we still haven't dealt