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