mirror of
				https://github.com/Mikaela/Limnoria.git
				synced 2025-10-30 23:27:24 +01:00 
			
		
		
		
	* Initial import of DocBook conversions of documentation
This commit is contained in:
		
							parent
							
								
									894b213fa1
								
							
						
					
					
						commit
						6c898a8757
					
				
							
								
								
									
										8
									
								
								docs/DocBook/Makefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								docs/DocBook/Makefile
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,8 @@ | ||||
| JADE=/usr/bin/jade | ||||
| STYLESHEET=/usr/share/sgml/docbook/stylesheet/dsssl/modular/html/docbook.dsl | ||||
| 
 | ||||
| example: example.sgml  | ||||
| 	$(JADE) -t xml -d $(STYLESHEET) $< | ||||
| 
 | ||||
| clean: | ||||
| 	rm -f *.html | ||||
							
								
								
									
										744
									
								
								docs/DocBook/example.sgml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										744
									
								
								docs/DocBook/example.sgml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,744 @@ | ||||
| <!DOCTYPE article PUBLIC "-//OASIS//DTD DocBook V4.1//EN"> | ||||
| 
 | ||||
| <article> | ||||
| 	<articleinfo> | ||||
| 		<authorgroup> | ||||
| 			<author> | ||||
| 				<firstname>Jeremiah</firstname> | ||||
| 				<surname>Fincher</surname> | ||||
| 			</author> | ||||
| 			<editor> | ||||
| 				<firstname>Daniel</firstname> | ||||
| 				<surname>DiPaolo</surname> | ||||
| 				<contrib>DocBook translator</contrib> | ||||
| 			</editor> | ||||
| 		</authorgroup> | ||||
| 		<title>Supybot plugin author example</title> | ||||
| 		<revhistory> | ||||
| 			<revision> | ||||
| 				<revnumber>0.1</revnumber> | ||||
| 				<date>13 Sep 2003</date> | ||||
| 				<revremark>Initial revision</revremark> | ||||
| 			</revision> | ||||
| 			<revision> | ||||
| 				<revnumber>0.2</revnumber> | ||||
| 				<date>14 Sep 2003</date> | ||||
| 				<revremark>Converted to DocBook</revremark> | ||||
| 			</revision> | ||||
| 		</revhistory> | ||||
| 	</articleinfo> | ||||
| 	<sect1> | ||||
| 		<title>Introduction</title> | ||||
| 		<para> | ||||
| 			Ok, so you want to write a callback for supybot.  Good, then this | ||||
| 			is the place to be.  We're going to start from the top (the | ||||
| 			highest level, where supybot code does the most work for you) and | ||||
| 			move lower after that. | ||||
| 		</para> | ||||
| 		<para> | ||||
| 			So have you used supybot?  If not, you need to go use it, get a | ||||
| 			feel for it, see how the various commands work and such. | ||||
| 		</para> | ||||
| 		<para> | ||||
| 			So now that we know you've used supybot, we'll start getting into | ||||
| 			details. | ||||
| 		</para> | ||||
| 	</sect1> | ||||
| 
 | ||||
| 	<sect1> | ||||
| 		<title>Creating your own plugin</title> | ||||
| 		<sect2> | ||||
| 			<title> | ||||
| 				Using <application>scripts/newplugin.py</application> | ||||
| 			</title> | ||||
| 			<para> | ||||
| 				First, the easiest way to start writing a module is to use the | ||||
| 				wizard provided, | ||||
| 				<application>scripts/newplugin.py</application>.  Here's an | ||||
| 				example session: | ||||
| 			</para> | ||||
| 			<screen> | ||||
| functor% scripts/newplugin.py | ||||
| What should the name of the plugin be? Random | ||||
| Supybot offers two major types of plugins: command-based and regexp- | ||||
| based.  Command-based plugins are the kind of plugins you've seen most | ||||
| when you've used supybot.  They're also the most featureful and | ||||
| easiest to write.  Commands can be nested,  for instance, whereas | ||||
| regexp-based callbacks can't do nesting.  That doesn't mean that | ||||
| you'll never want regexp-based callbacks. They offer a flexibility | ||||
| that command-based callbacks don't offer; however, they don't tie into | ||||
| the whole system as well.  If you need to combine a command-based | ||||
| callback with some regexp-based methods, you can do so by subclassing | ||||
| callbacks.PrivmsgCommandAndRegexp and then adding a class-level | ||||
| attribute "regexps" that is a sets.Set of methods that are regexp- | ||||
| based.  But you'll have to do that yourself after this wizard is | ||||
| finished :) | ||||
| Do you want a command-based plugin or a regexp-based plugin? [command/ | ||||
|                                                               regexp] command | ||||
| Sometimes you'll want a callback to be threaded.  If its methods | ||||
| (command or regexp-based, either one) will take a signficant amount | ||||
| of time to run, you'll want to thread them so they don't block | ||||
| the entire bot. | ||||
| 
 | ||||
| Does your plugin need to be threaded? [y/n] n | ||||
| Your new plugin template is in plugins/Random.py | ||||
| functor% | ||||
| 			</screen> | ||||
| 			<para> | ||||
| 				So that's what it looks like.  Now let's look at the source | ||||
| 				code (if you'd like to look at it in your programming editor, | ||||
| 				the whole plugin is available as | ||||
| 				<filename>examples/Random.py</filename>):  | ||||
| 			</para> | ||||
| 			<programlisting> | ||||
| #!/usr/bin/env python | ||||
| 
 | ||||
| ### | ||||
| # Copyright (c) 2002, Jeremiah Fincher | ||||
| # All rights reserved. | ||||
| # | ||||
| # Redistribution and use in source and binary forms, with or without | ||||
| # modification, are permitted provided that the following conditions are met: | ||||
| # | ||||
| #   * Redistributions of source code must retain the above copyright notice, | ||||
| #     this list of conditions, and the following disclaimer. | ||||
| #   * Redistributions in binary form must reproduce the above copyright notice, | ||||
| #     this list of conditions, and the following disclaimer in the | ||||
| #     documentation and/or other materials provided with the distribution. | ||||
| #   * Neither the name of the author of this software nor the name of | ||||
| #     contributors to this software may be used to endorse or promote products | ||||
| #     derived from this software without specific prior written consent. | ||||
| # | ||||
| # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||||
| # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||||
| # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | ||||
| # ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | ||||
| # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | ||||
| # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | ||||
| # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | ||||
| # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | ||||
| # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | ||||
| # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | ||||
| # POSSIBILITY OF SUCH DAMAGE. | ||||
| ### | ||||
| 
 | ||||
| """ | ||||
| Add the module docstring here.  This will be used by the setup.py script. | ||||
| """ | ||||
| 
 | ||||
| from baseplugin import * | ||||
| 
 | ||||
| import utils | ||||
| import privmsgs | ||||
| import 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. | ||||
|     from questions import expect, anything, something, yn | ||||
|     onStart.append('load Random') | ||||
| 
 | ||||
| example = utils.wrapLines(""" | ||||
| Add an example IRC session using this module here. | ||||
| """) | ||||
| 
 | ||||
| class Random(callbacks.Privmsg): | ||||
|     pass | ||||
| 
 | ||||
| 
 | ||||
| Class = Random | ||||
| 
 | ||||
| # vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: | ||||
| 			</programlisting> | ||||
| 		<sect2> | ||||
| 			<title>Customizing the boilerplate code</title> | ||||
| 			<para> | ||||
| 				So a few notes, before we customize it. | ||||
| 			</para> | ||||
| 			<para> | ||||
| 				You'll probably want to change the copyright notice to be your | ||||
| 				name.  It wouldn't stick even if you kept my name, so you | ||||
| 				might as well :) | ||||
| 			</para> | ||||
| 			<para> | ||||
| 				Describe what you want the plugin to do in the docstring. | ||||
| 				This is used in <application>scripts/setup.py</application> in | ||||
| 				order to explain to the user the purpose of the module.  It's | ||||
| 				also returned when someone asks the bot for help for a given | ||||
| 				module (instead of help for a certain command).  We'll change | ||||
| 				this one to <literal>"Lots of stuff relating to random | ||||
| 				numbers."</literal> | ||||
| 			</para> | ||||
| 			<para> | ||||
| 				Then there are the imports.  The <varname>utils</varname> | ||||
| 				module is used (in example, which we'll see later).  The | ||||
| 				<varname>callbacks</varname> module is used (the class you're | ||||
| 				given subclasses <varname>callbacks.Privmsg</varname>) but the | ||||
| 				<varname>privmsgs</varname> module isn't used.  That's | ||||
| 				alright; we can almost guarantee you'll use it, so we go ahead | ||||
| 				and add the import to the template. | ||||
| 			</para> | ||||
| 			<para> | ||||
| 				Then you see a <function>configure</function> function.  This | ||||
| 				the function that's called when users decide to add your | ||||
| 				module in <application>scripts/setup.py</application>.  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 | ||||
| 				plugins this is all you need; for more complex plugins, you | ||||
| 				might need to ask questions and add commands based on the | ||||
| 				answers. | ||||
| 			</para> | ||||
| 			<para>  | ||||
| 				Then there's an example string.  It's simply an example of | ||||
| 				usage of the plugin in practice. | ||||
| 				<application>scripts/setup.py</application> offers to show the | ||||
| 				user an example of the module usage; this is what it shows | ||||
| 				them.  You'll note that it's wrapped for you in | ||||
| 				<varname>utils.wrapLines</varname> so you don't have to bother | ||||
| 				with it; just paste a session directly out of your IRC client | ||||
| 				and you'll be set.   | ||||
| 			</para> | ||||
| 		<sect2> | ||||
| 			<title>Digging in: customizing the plugin class</title> | ||||
| 			<para> | ||||
| 				Now comes the meat of the plugin: the plugin class. | ||||
| 			</para> | ||||
| 			<para> | ||||
| 				What you're given is a skeleton: a simple subclass of | ||||
| 				<varname>callbacks.Privmsg</varname> for you to start with. | ||||
| 				Now let's add a command. | ||||
| 			</para> | ||||
| 			<para> | ||||
| 				I don't know what you know about random number generators, but | ||||
| 				the short of it is that they start at a certain number (a | ||||
| 				seed) and they continue (via some somewhat | ||||
| 				complicated/unpredictable algorithm) from there.  This seed | ||||
| 				(and the rest of the sequence, really) is all nice and | ||||
| 				packaged up in Python's <varname>random</varname> module, the | ||||
| 				<varname>Random</varname> object.  So the first thing we're | ||||
| 				going to have to do is give our plugin a | ||||
| 				<varname>Random</varname> object. | ||||
| 			</para> | ||||
| 			<para> | ||||
| 				Normally, when we want to give instances of a class an object, | ||||
| 				we'll do so in the <function>__init__</function> method.  And | ||||
| 				that works great for plugins, too.  The one thing you have to | ||||
| 				be careful of is that you call the superclass | ||||
| 				<function>__init__</function> method at the end of your own | ||||
| 				<function>__init__</function>.  So to add this | ||||
| 				<varname>random.Random</varname> object to our plugin, we can | ||||
| 				replace the <function>pass</function> statement with | ||||
| 				this: | ||||
| 			</para> | ||||
| 			<programlisting> | ||||
| def __init__(self): | ||||
|         self.rng = random.Random() | ||||
|         callbacks.Privmsg.__init__(self) | ||||
| 			</programlisting> | ||||
| 			<para> | ||||
| 				(<varname>rng</varname>is an abbreviation for "random number | ||||
| 				generator," in case you were curious) | ||||
| 			</para> | ||||
| 			<para> | ||||
| 				Do be careful not to give your <function>__init__</function> | ||||
| 				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. | ||||
| 			</para> | ||||
| 			<para> | ||||
| 				There's an easier way to get our plugin to have its own rng | ||||
| 				than to define an <function>__init__</function>.  Plugins are | ||||
| 				unique among classes because we're always certain that there | ||||
| 				will only be one instance -- supybot doesn't allow us to load | ||||
| 				multiple instances of a single plugin.  So instead of adding | ||||
| 				the rng in <function>__init__</function>, we can just add it | ||||
| 				as a attribute to the class itself.  Like so (replacing the | ||||
| 				<function>pass</function> statement again): | ||||
| 			</para> | ||||
| 			<programlisting> | ||||
|     rng = random.Random() | ||||
| 			</programlisting> | ||||
| 			<para> | ||||
| 				And we save two lines of code and make our code a little more | ||||
| 				clear :)  | ||||
| 			</para> | ||||
| 			<para> | ||||
| 				Now that we have an RNG, we need some way to get random | ||||
| 				numbers.  So first, we'll add a command that simply gets the | ||||
| 				next random number and gives it back to the user.  It takes no | ||||
| 				arguments, of course (what would you give it?).  Here's the | ||||
| 				command, and I'll follow that with the explanation of what | ||||
| 				each part means. | ||||
| 			</para> | ||||
| 			<programlisting> | ||||
|     def random(self, irc, msg, args): | ||||
|         """takes no arguments | ||||
| 
 | ||||
|         Returns the next random number generated by the random number | ||||
|         generator. | ||||
|         """ | ||||
|         irc.reply(msg, str(self.rng.random())) | ||||
| 			</programlisting> | ||||
| 			<para> | ||||
| 				And that's it!  Pretty simple, huh?  Anyway, you're probably | ||||
| 				wondering what all that <emphasis>means</emphasis>.  We'll | ||||
| 				start with the <function>def</function> statement: | ||||
| 			</para> | ||||
| 			<programlisting> | ||||
|     def random(self, irc, msg, args): | ||||
| 			</programlisting> | ||||
| 			<para> | ||||
| 				What that does is define a command | ||||
| 				<function>random</function>.  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!). | ||||
| 				<varname>irc</varname> is the <varname>Irc</varname> object | ||||
| 				passed to the command; <varname>msg</varname> is the original | ||||
| 				<varname>IrcMsg</varname> object.  But you're really not going | ||||
| 				to have to deal with either of these too much (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 | ||||
| 				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 <varname>Irc</varname> 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 | ||||
| 				<varname>IrcObjectProxy</varname>, 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. | ||||
| 			</para> | ||||
| 			<para> | ||||
| 				(In case you're curious, the answer is yes, you | ||||
| 				<emphasis>must</emphasis> name your arguments <varname>(self, | ||||
| 				irc, msg, args)</varname>.  The names of those arguments is | ||||
| 				one of the ways that supybot uses to determine which methods | ||||
| 				in a plugin class are commands and which aren't.  And while | ||||
| 				we're talking about naming restrictions, all your commands | ||||
| 				should be named in all-lowercase with no underscores.  Before | ||||
| 				calling a command, supybot always converts the command name to | ||||
| 				lowercase and removes all dashes and underscores.  On the | ||||
| 				other hand, you now know an easy way to make sure a method is | ||||
| 				never called (even if its arguments are <varname>(self, irc, | ||||
| 				msg, args)</varname>, however unlikely that may be).  Just | ||||
| 				name it with an underscore or an uppercase letter in it :))  | ||||
| 			</para> | ||||
| 			<para> | ||||
| 				You'll also note that the docstring is odd.  The wonderful | ||||
| 				thing about the supybot framework is that it's easy to write | ||||
| 				complete commands with help and everything: the docstring | ||||
| 				<emphasis>is</emphasis> the help!  Given the above docstring, | ||||
| 				this is what a supybot does: | ||||
| 			</para> | ||||
| 			<screen> | ||||
|     <angryman> jemfinch: random takes no arguments (for more help  | ||||
|                use the morehelp command) | ||||
|     <jemfinch> $morehelp random | ||||
|     <angryman> jemfinch: Returns the next random number from the  | ||||
|                current random number generator.  | ||||
| 			</screen> | ||||
| 			<para> | ||||
| 				'help <command>' 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 <command>' | ||||
| 				will reply with the remainder of the docstring.  So that | ||||
| 				explains the docstring.  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 | ||||
| 				<varname>IrcMsg</varname> (like the one passed into your | ||||
| 				function) and a string.  The <varname>IrcMsg</varname> 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 message (and | ||||
| 				oftentimes that turns out to be the case :)) the 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.rnd.random())" instead | ||||
| 				of simply "self.rng.random()" -- we had to give | ||||
| 				<function>irc.reply</function> a string. | ||||
| 			</para> | ||||
| 			<para> | ||||
| 				Anyway, now that we have an RNG, we have a need for seed!  Of | ||||
| 				course, Python gives us a good seed already (it uses the | ||||
| 				current time as a seed if we don't give it one) but users | ||||
| 				might want to be able to repeat "random" sequences, so letting | ||||
| 				them set the seed is a good thing.  So we'll add a seed | ||||
| 				command to give the RNG a specific seed: | ||||
| 			</para> | ||||
| 			<programlisting> | ||||
|     def seed(self, irc, msg, args): | ||||
|         """<seed> | ||||
| 
 | ||||
|         Sets the seed of the random number generator.  <seed> must be  | ||||
|         an int or a long. | ||||
|         """ | ||||
|         seed = privmsgs.getArgs(args) | ||||
|         try: | ||||
|             seed = long(seed) | ||||
|         except ValueError: | ||||
|             # It wasn't a valid long! | ||||
|             irc.error(msg, '<seed> must be a valid int or long.') | ||||
|             return | ||||
|         self.rng.seed(seed) | ||||
|         irc.reply(msg, conf.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. | ||||
| 			</para> | ||||
| 			<para> | ||||
| 				<function>privmsgs.getArgs</function> is a function you're | ||||
| 				going to be seeing a lot of when you write plugins for | ||||
| 				supybot.  What it does is basically give you the right number | ||||
| 				of arguments for your comamnd.  In this case, we want one | ||||
| 				argument.  But we might have been given any number of | ||||
| 				arguments by the user.  So | ||||
| 				<function>privmsgs.getArgs</function> joins them | ||||
| 				appropriately, leaving us with one single "seed" argument (by | ||||
| 				default, it returns one argument as a single value; more | ||||
| 				arguments are returned in a tuple/list).  Yes, we could've | ||||
| 				just said "seed = args[0]" and gotten the first argument, but | ||||
| 				what if the user didn't pass us an argument at all?  Then | ||||
| 				we've got to catch the <varname>IndexError</varname> from | ||||
| 				<varname>args[0]</varname> and complain to the user about it. | ||||
| 				<function>privmsgs.getArgs</function>, on the other hand, | ||||
| 				handles all that for us.  If the user didn't give us enough | ||||
| 				arguments, it'll reply with the help string for the command, | ||||
| 				thus saving us the effort. | ||||
| 			</para> | ||||
| 			<para> | ||||
| 				So we have the seed from | ||||
| 				<function>privmsgs.getArgs</function>.  But it's a string. | ||||
| 				The next three lines is pretty darn obvious: we're just | ||||
| 				converting the string to a int of some sort.  But if it's not, | ||||
| 				that's when we're going to call | ||||
| 				<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 <function>return</function> 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. | ||||
| 			</para> | ||||
| 			<para> | ||||
| 				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 | ||||
| 				<varname>conf.replySuccess</varname> 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! | ||||
| 			</para> | ||||
| 			<para> | ||||
| 					So that's a bit more complicated command.  But we still | ||||
| 					haven't dealt with multiple arguments.  Let's do that | ||||
| 					next. | ||||
| 			</para> | ||||
| 			<para> | ||||
| 				So these random numbers are useful, but they're not the kind | ||||
| 				of random numbers we usually want in Real Life.  In Real Life, | ||||
| 				we like to tell someone to "pick a number between 1 and 10." | ||||
| 				So let's write a function that does that.  Of course, we won't | ||||
| 				hardcode the 1 or the 10 into the function, but we'll take | ||||
| 				them as arguments.  First the function: | ||||
| 			</para> | ||||
| 			<programlisting> | ||||
|     def range(self, irc, msg, args): | ||||
|         """<start> <end> | ||||
| 
 | ||||
|         Returns a number between <start> and <end>, inclusive (i.e., the number | ||||
|         can be either of the endpoints. | ||||
|         """ | ||||
|         (start, end) = privmsgs.getArgs(args, needed=2) | ||||
|         try: | ||||
|             end = int(end) | ||||
|             start = int(start) | ||||
|         except ValueError: | ||||
|             irc.error(msg, '<start> and <end> must both be integers.') | ||||
|             return | ||||
|         # .randrange() doesn't include the endpoint, so we use end+1. | ||||
|         irc.reply(msg, str(self.rng.randrange(start, end+1))) | ||||
| 			</programlisting> | ||||
| 			<para> | ||||
| 				Pretty simple.  This is becoming old hat by now.  The only new | ||||
| 				thing here is the call to | ||||
| 				<function>privmsgs.getArgs</function>.  We have to make sure, | ||||
| 				since we want two values, to pass a keyword parameter "needed" | ||||
| 				into <function>privmsgs.getArgs</function>.  Of course, | ||||
| 				<function>privmsgs.getArgs</function> handles all the checking | ||||
| 				for missing arguments and whatnot so we don't have to. | ||||
| 			</para> | ||||
| 			<para> | ||||
| 				The <varname>Random</varname> object we're using offers us a | ||||
| 				"sample" method that takes a sequence and a number (we'll call | ||||
| 				it <varname>N</varname>) and returns a list of | ||||
| 				<varname>N</varname> items taken randomly from the sequence. | ||||
| 				So I'll show you an example that takes advantage of multiple | ||||
| 				arguments but doesn't use | ||||
| 				<function>privmsgs.getArgs</function> (and thus has to handle | ||||
| 				its own errors if the number of arguments isn't right). | ||||
| 				Here's the code: | ||||
| 			</para> | ||||
| 			<programlisting> | ||||
|     def sample(self, irc, msg, args): | ||||
|         """<number of items> [<text> ...] | ||||
| 
 | ||||
|         Returns a sample of the <number of items> taken from the remaining | ||||
|         arguments.  Obviously <number of items> must be less than the number | ||||
|         of arguments given. | ||||
|         """ | ||||
|         try: | ||||
|             n = int(args.pop(0)) | ||||
|         except IndexError: # raised by .pop(0) | ||||
|             raise callbacks.ArgumentError | ||||
|         except ValueError: | ||||
|             irc.error(msg, '<number of items> must be an integer.') | ||||
|             return | ||||
|         if n > len(args): | ||||
|             irc.error(msg, '<number of items> must be less than the number ' | ||||
|                            'of arguments.') | ||||
|             return | ||||
|         sample = self.rng.sample(args, n) | ||||
|         irc.reply(msg, utils.commaAndify(map(repr, sample))) | ||||
| 			</programlisting> | ||||
| 			<para> | ||||
| 				Most everything here is familiar.  The difference between this | ||||
| 				and the previous examples is that we're dealing with | ||||
| 				<varname>args</varname> directly, rather than through | ||||
| 				<function>getArgs</function>.  Since we already have the | ||||
| 				arguments in a list, it doesn't make any sense to have | ||||
| 				<function>privmsgs.getArgs</function> smush them all together | ||||
| 				into a big long string that we'll just have to re-split.  But | ||||
| 				we still want the nice error handling of | ||||
| 				<function>privmsgs.getArgs</function>.  So what do we do?  We | ||||
| 				raise <varname>callbacks.ArgumentError</varname>!  That's the | ||||
| 				secret juju that <function>privmsgs.getArgs</function> is | ||||
| 				doing; now we're just doing it ourself.  Someone up our | ||||
| 				callchain knows how to handle it so a neat error message is | ||||
| 				returned.  So in this function, if | ||||
| 				<function>.pop(0)</function> fails, we weren't given enough | ||||
| 				arguments and thus need to tell the user how to call us.   | ||||
| 			</para> | ||||
| 			<para> | ||||
| 				So we have the args, we have the number, we do a simple call | ||||
| 				to <function>random.sample</function> and then we do this | ||||
| 				funky <function>utils.commaAndify</function> to it.  Yeah, so | ||||
| 				I was running low on useful names :)  Anyway, what it does is | ||||
| 				take a list of strings and return a string with them joined by | ||||
| 				a comma, the last one being joined with a comma and "and".  So | ||||
| 				the list ['foo', 'bar', 'baz'] becomes "foo, bar, and baz". | ||||
| 				It's pretty useful for showing the user lists in a useful | ||||
| 				form.  We map the strings with <function>repr()</function> | ||||
| 				first just to surround them with quotes. | ||||
| 			</para> | ||||
| 			<para> | ||||
| 				So we have one more example.  Yes, I hear your groans, but | ||||
| 				it's pedagogically useful :)  This time we're going to write a | ||||
| 				command that makes the bot roll a die.  It'll take one | ||||
| 				argument (the number of sides on the die) and will respond | ||||
| 				with the equivalent of "/me rolls a __" where __ is the number | ||||
| 				the bot rolled.  So here's the code:  | ||||
| 			</para> | ||||
| 			<programlisting> | ||||
|     def diceroll(self, irc, msg, args): | ||||
|         """[<number of sides>] | ||||
| 
 | ||||
|         Rolls a die with <number of sides> sides.  The default number | ||||
|         of sides is 6. | ||||
|         """ | ||||
|         try: | ||||
|             n = privmsgs.getArgs(args, needed=0, optional=1) | ||||
|             if not n: | ||||
|                 n = 6 | ||||
|             n = int(n) | ||||
|         except ValueError: | ||||
|             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 | ||||
| 			</programlisting> | ||||
| 			<para> | ||||
| 				There's a lot of stuff you haven't seen before in there.  The | ||||
| 				most important, though, is the first thing you'll notice | ||||
| 				that's different: the <function>privmsg.getArgs</function> | ||||
| 				call.  Here we're offering a default argument in case the user | ||||
| 				is too lazy to supply one (or just wants a nice, standard | ||||
| 				six-sided die :))  <function>privmsgs.getArgs</function> | ||||
| 				supports that; we'll just tell it that we don't | ||||
| 				<emphasis>need</emphasis> any arguments (via | ||||
| 				<varname>needed=0</varname>) and that we <emphasis>might | ||||
| 				like</emphasis> one argument (<varname>optional=1</varname>). | ||||
| 				If the user provides an argument, we'll get it -- if they | ||||
| 				don't, we'll just get an empty string.  Hence the "if not n: n | ||||
| 				= 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 <varname>IrcMsg</varname> 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 <varname>IrcMsg</varname> object. | ||||
| 				<function>ircutils.replyTo</function> simply takes an | ||||
| 				<varname>IrcMsg</varname> 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> | ||||
| 				So that's our plugin.  5 commands, each building in | ||||
| 				complexity.  You should now be able to write most anything you | ||||
| 				want to do in Supybot.  Except regexp-based plugins, but | ||||
| 				that's a story for another day (and those aren't nearly as | ||||
| 				cool as these command-based callbacks anyway :)).  Now we need | ||||
| 				to flesh it out to make it a full-fledged plugin. | ||||
| 			</para> | ||||
| 		</sect2> | ||||
| 		<sect2> | ||||
| 			<title>Finishing touches</title> | ||||
| 			<para> | ||||
| 				Let's take a look at that <function>configure</function> | ||||
| 				function <application>scripts/newplugin.py</application> 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 | ||||
| 				<application>scripts/setup.py</application> 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 | ||||
| 				<varname>Random</varname> 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 <varname>questions</varname> 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 | ||||
| 				<application>scripts/setup.py</application> creates for the bot. | ||||
| 			</para> | ||||
| 			<para> | ||||
| 				Now the only thing missing from our plugin is an example. | ||||
| 				Here, I'll make one really quickly: | ||||
| 			</para> | ||||
| 			<screen> | ||||
|     <jemfinch> $list Random | ||||
|     <angryman> diceroll, random, range, sample, seed | ||||
|     <jemfinch> $random | ||||
|     <angryman> 0.478084042957 | ||||
|     <jemfinch> $random | ||||
|     <angryman> 0.960634332773 | ||||
|     <jemfinch> $seed 50 | ||||
|     <angryman> The operation succeeded. | ||||
|     <jemfinch> $random | ||||
|     <angryman> 0.497536568759 | ||||
|     <jemfinch> $seed 50 | ||||
|     <angryman> The operation succeeded. | ||||
|     <jemfinch> $random | ||||
|     <angryman> 0.497536568759 | ||||
|     <jemfinch> $range 1 10 | ||||
|     <angryman> 3 | ||||
|     <jemfinch> $range 1 10000000000000 | ||||
|     <angryman> 6374111614437 | ||||
|     <jemfinch> $diceroll | ||||
|     * angryman rolls a 2 | ||||
|     <jemfinch> $diceroll | ||||
|     * angryman rolls a 3 | ||||
|     <jemfinch> $diceroll 100 | ||||
|     * angryman rolls a 97 | ||||
| 			</screen> | ||||
| 			<para> | ||||
| 				So we'll throw this into our example string (where the | ||||
| 				template says to put it) and then we're done!  We've written | ||||
| 				our own plugin from scratch (well, from the boilerplate that | ||||
| 				we got from <application>scripts/newplugin.py</application> | ||||
| 				:)) and survived!  Now go write more plugins for supybot, and | ||||
| 				send them to me so I can use them too :)  | ||||
| 			</para> | ||||
| 		</sect2> | ||||
| 	</sect1> | ||||
| </article> | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Daniel DiPaolo
						Daniel DiPaolo