diff --git a/.gitignore b/.gitignore index 2985e16b0..450fc4869 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ test-data test-conf test-logs *.pyc +docs/_build +docs/plugins diff --git a/docs/ADVANCED_PLUGIN_CONFIG b/docs/ADVANCED_PLUGIN_CONFIG.rst similarity index 100% rename from docs/ADVANCED_PLUGIN_CONFIG rename to docs/ADVANCED_PLUGIN_CONFIG.rst diff --git a/docs/ADVANCED_PLUGIN_TESTING b/docs/ADVANCED_PLUGIN_TESTING.rst similarity index 100% rename from docs/ADVANCED_PLUGIN_TESTING rename to docs/ADVANCED_PLUGIN_TESTING.rst diff --git a/docs/CAPABILITIES b/docs/CAPABILITIES.rst similarity index 99% rename from docs/CAPABILITIES rename to docs/CAPABILITIES.rst index 17f2f14ec..6a6b2c59b 100644 --- a/docs/CAPABILITIES +++ b/docs/CAPABILITIES.rst @@ -1,3 +1,7 @@ +============ +Capabilities +============ + Introduction ------------ diff --git a/docs/CONFIGURATION b/docs/CONFIGURATION.rst similarity index 99% rename from docs/CONFIGURATION rename to docs/CONFIGURATION.rst index f1c430a1c..9eeff4288 100644 --- a/docs/CONFIGURATION +++ b/docs/CONFIGURATION.rst @@ -1,3 +1,7 @@ +============= +Configuration +============= + Introduction ------------ So you've got your Supybot up and running and there are some things you diff --git a/docs/FAQ b/docs/FAQ.rst similarity index 98% rename from docs/FAQ rename to docs/FAQ.rst index df7b724ab..62c07f887 100644 --- a/docs/FAQ +++ b/docs/FAQ.rst @@ -1,3 +1,7 @@ +========================== +Frequently Asked Questions +========================== + How do I make my Supybot connect to multiple servers? Just use the `connect` command in the `Network` plugin. diff --git a/docs/GETTING_STARTED b/docs/GETTING_STARTED deleted file mode 100644 index 6207c4f72..000000000 --- a/docs/GETTING_STARTED +++ /dev/null @@ -1,171 +0,0 @@ -Introduction - - Ok, so you've decided to try out Supybot. That's great! The more people who -use Supybot, the more people can submit bugs and help us to make it the best -IRC bot in the world :) - - You should have already read through our install document (if you had to -manually install) before reading any further. Now we'll give you a whirlwind -tour as to how you can get Supybot setup and use Supybot effectively. - -Initial Setup - - Now that you have Supybot installed, you'll want to get it running. The -first thing you'll want to do is run supybot-wizard. Before running -supybot-wizard, you should be in the directory in which you want your -bot-related files to reside. The wizard will walk you through setting up a -base config file for your Supybot. Once you've completed the wizard, you will -have a config file called botname.conf. In order to get the bot running, run -'supybot botname.conf'. - -Listing Commands - - Ok, so let's assume your bot connected to the server and joined the channels -you told it to join. For now we'll assume you named your bot 'supybot' (you -probably didn't, but it'll make it much clearer in the examples that follow to -assume that you did). We'll also assume that you told it to join #channel (a -nice generic name for a channel, isn't it? :)) So what do you do with this bot -that you just made to join your channel? Try this in the channel:: - - supybot: list - - Replacing 'supybot' with the actual name you picked for your bot, of course. -Your bot should reply with a list of the plugins he currently has loaded. At -least Admin, Channel, Config, Misc, Owner, and User should be there; if you -used supybot-wizard to create your configuration file you may have many more -plugins loaded. The list command can also be used to list the commands in a -given plugin:: - - supybot: list Misc - - will list all the commands in the Misc plugin. If you want to see the help -for any command, just use the help command:: - - supybot: help help - supybot: help list - supybot: help load - - Sometimes more than one plugin will have a given command; for instance, the -"list" command exists in both the Misc and Config plugins (both loaded by -default). List, in this case, defaults to the Misc plugin, but you may want to -get the help for the list command in the Config plugin. In that case, you'll -want to give your command like this:: - - supybot: help config list - - Anytime your bot tells you that a given command is defined in several -plugins, you'll want to use this syntax ("plugin command") to disambiguate -which plugin's command you wish to call. For instance, if you wanted to call -the Config plugin's list command, then you'd need to say:: - - supybot: config list - - Rather than just 'list'. - -Making Supybot Recognize You - - If you ran the wizard, then it is almost certainly the case that you already -added an owner user for yourself. If not, however, you can add one via the -handy-dandy 'supybot-adduser' script. You'll want to run it while the bot is -not running (otherwise it could overwrite supybot-adduser's changes to your -user database before you get a chance to reload them). Just follow the -prompts, and when it asks if you want to give the user any capabilities, say -yes and then give yourself the 'owner' capability, restart the bot and you'll -be ready to load some plugins! - - Now, in order for the bot to recognize you as your owner user, you'll have to -identify with the bot. Open up a query window in your irc client ('/query' -should do it; if not, just know that you can't identify in a channel because it -requires sending your password to the bot). Then type this:: - - help identify - - And follow the instructions; the command you send will probably look like -this, with 'myowneruser' and 'myuserpassword' replaced:: - - identify myowneruser myuserpassword - - The bot will tell you that 'The operation succeeded' if you got the right -name and password. Now that you're identified, you can do anything that -requires any privilege: that includes all the commands in the Owner and Admin -plugins, which you may want to take a look at (using the list and help -commands, of course). One command in particular that you might want to use -(it's from the User plugin) is the 'hostmask add' command: it lets you add a -hostmask to your user record so the bot recognizes you by your hostmask instead -of requiring you always to identify with it before it recognizes you. Use the -'help' command to see how this command works. Here's how I often use it:: - - hostmask add myuser [hostmask] mypassword - - You may not have seen that '[hostmask]' syntax before. Supybot allows nested -commands, which means that any command's output can be nested as an argument to -another command. The hostmask command from the Misc plugin returns the -hostmask of a given nick, but if given no arguments, it returns the hostmask of -the person giving the command. So the command above adds the hostmask I'm -currently using to my user's list of recognized hostmasks. I'm only required -to give mypassword if I'm not already identified with the bot. - -Loading Plugins - - Let's take a look at loading other plugins. If you didn't use -supybot-wizard, though, you might do well to try it before playing around with -loading plugins yourself: each plugin has its own configure function that the -wizard uses to setup the appropriate registry entries if the plugin requires -any. - - If you do want to play around with loading plugins, you're going to need to -have the owner capability. - - Remember earlier when I told you to try 'help load'? That's the very command -you'll be using. Basically, if you want to load, say, the Games plugin, then -'load Games'. Simple, right? If you need a list of the plugins you can load, -you'll have to list the directory the plugins are in (using whatever command is -appropriate for your operating system, either 'ls' or 'dir'). - -Getting More From Your Supybot - - Another command you might find yourself needing somewhat often is the 'more' -command. The IRC protocol limits messages to 512 bytes, 60 or so of which must -be devoted to some bookkeeping. Sometimes, however, Supybot wants to send a -message that's longer than that. What it does, then, is break it into "chunks" -and send the first one, following it with '(X more messages)' where X is how -many more chunks there are. To get to these chunks, use the more command. One -way to try is to look at the default value of -supybot.replies.genericNoCapability -- it's so long that it'll stretch across two -messages:: - - $config default - supybot.replies.genericNoCapability - jemfinch|lambda: You're missing some capability - you need. This could be because you actually - possess the anti-capability for the capability - that's required of you, or because the channel - provides that anti-capability by default, or - because the global capabilities include that - anti-capability. Or, it could be because the - channel or the global defaultAllow is set to - False, meaning (1 more message) - $more - jemfinch|lambda: that no commands are allowed - unless explicitly in your capabilities. Either - way, you can't do what you want to do. - - So basically, the bot keeps, for each person it sees, a list of "chunks" -which are "released" one at a time by the 'more' command. In fact, you can -even get the more chunks for another user: if you want to see another chunk in -the last command jemfinch gave, for instance, you would just say 'more -jemfinch' after which, his "chunks" now belong to you. So, you would just need -to say 'more' to continue seeing chunks from jemfinch's initial command. - -Final Word - - You should now have a solid foundation for using Supybot. You can use the -'list' command to see what plugins your bot has loaded and what commands are in -those plugins; you can use the 'help' command to see how to use a specific -command, and you can use the 'more' command to continue a long response from -the bot. With these three commands, you should have a strong basis with which -to discover the rest of the features of Supybot! - - Do be sure to read our other documentation and make use of the resources we -provide for assistance; this website, the forums on it, and, of course, -#supybot on irc.freenode.net if you run into any trouble! diff --git a/docs/GETTING_STARTED.rst b/docs/GETTING_STARTED.rst new file mode 100644 index 000000000..a0c7fedd3 --- /dev/null +++ b/docs/GETTING_STARTED.rst @@ -0,0 +1,181 @@ +============================ +Getting Started with Supybot +============================ + +Introduction +------------ + +Ok, so you've decided to try out Supybot. That's great! The more people who +use Supybot, the more people can submit bugs and help us to make it the best +IRC bot in the world :) + +You should have already read through our install document (if you had to +manually install) before reading any further. Now we'll give you a whirlwind +tour as to how you can get Supybot setup and use Supybot effectively. + +Initial Setup +------------- + +Now that you have Supybot installed, you'll want to get it running. The first +thing you'll want to do is run supybot-wizard. Before running supybot-wizard, +you should be in the directory in which you want your bot-related files to +reside. The wizard will walk you through setting up a base config file for +your Supybot. Once you've completed the wizard, you will have a config file +called botname.conf. In order to get the bot running, run ``supybot +botname.conf``. + +Listing Commands +---------------- + +Ok, so let's assume your bot connected to the server and joined the channels +you told it to join. For now we'll assume you named your bot 'supybot' (you +probably didn't, but it'll make it much clearer in the examples that follow to +assume that you did). We'll also assume that you told it to join #channel (a +nice generic name for a channel, isn't it? :)) So what do you do with this +bot that you just made to join your channel? Try this in the channel:: + + supybot: list + +Replacing 'supybot' with the actual name you picked for your bot, of course. +Your bot should reply with a list of the plugins he currently has loaded. At +least `Admin`, `Channel`, `Config`, `Misc`, `Owner`, and `User` should be +there; if you used supybot-wizard to create your configuration file you may +have many more plugins loaded. The list command can also be used to list the +commands in a given plugin:: + + supybot: list Misc + +will list all the commands in the `Misc` plugin. If you want to see the help +for any command, just use the help command:: + + supybot: help help + supybot: help list + supybot: help load + +Sometimes more than one plugin will have a given command; for instance, the +"list" command exists in both the Misc and Config plugins (both loaded by +default). List, in this case, defaults to the Misc plugin, but you may want +to get the help for the list command in the Config plugin. In that case, +you'll want to give your command like this:: + + supybot: help config list + +Anytime your bot tells you that a given command is defined in several plugins, +you'll want to use this syntax ("plugin command") to disambiguate which +plugin's command you wish to call. For instance, if you wanted to call the +Config plugin's list command, then you'd need to say:: + + supybot: config list + +Rather than just 'list'. + +Making Supybot Recognize You +---------------------------- + +If you ran the wizard, then it is almost certainly the case that you already +added an owner user for yourself. If not, however, you can add one via the +handy-dandy 'supybot-adduser' script. You'll want to run it while the bot is +not running (otherwise it could overwrite supybot-adduser's changes to your +user database before you get a chance to reload them). Just follow the +prompts, and when it asks if you want to give the user any capabilities, say +yes and then give yourself the 'owner' capability, restart the bot and you'll +be ready to load some plugins! + +Now, in order for the bot to recognize you as your owner user, you'll have to +identify with the bot. Open up a query window in your irc client ('/query' +should do it; if not, just know that you can't identify in a channel because +it requires sending your password to the bot). Then type this:: + + help identify + +And follow the instructions; the command you send will probably look like +this, with 'myowneruser' and 'myuserpassword' replaced:: + + identify myowneruser myuserpassword + +The bot will tell you that 'The operation succeeded' if you got the right name +and password. Now that you're identified, you can do anything that requires +any privilege: that includes all the commands in the Owner and Admin plugins, +which you may want to take a look at (using the list and help commands, of +course). One command in particular that you might want to use (it's from the +User plugin) is the 'hostmask add' command: it lets you add a hostmask to your +user record so the bot recognizes you by your hostmask instead of requiring +you always to identify with it before it recognizes you. Use the 'help' +command to see how this command works. Here's how I often use it:: + + hostmask add myuser [hostmask] mypassword + +You may not have seen that '[hostmask]' syntax before. Supybot allows nested +commands, which means that any command's output can be nested as an argument +to another command. The hostmask command from the Misc plugin returns the +hostmask of a given nick, but if given no arguments, it returns the hostmask +of the person giving the command. So the command above adds the hostmask I'm +currently using to my user's list of recognized hostmasks. I'm only required +to give mypassword if I'm not already identified with the bot. + +Loading Plugins +--------------- + +Let's take a look at loading other plugins. If you didn't use supybot-wizard, +though, you might do well to try it before playing around with loading plugins +yourself: each plugin has its own configure function that the wizard uses to +setup the appropriate registry entries if the plugin requires any. + +If you do want to play around with loading plugins, you're going to need to +have the owner capability. + +Remember earlier when I told you to try ``help load``? That's the very command +you'll be using. Basically, if you want to load, say, the Games plugin, then +``load Games``. Simple, right? If you need a list of the plugins you can load, +you'll have to list the directory the plugins are in (using whatever command +is appropriate for your operating system, either 'ls' or 'dir'). + +Getting More From Your Supybot +------------------------------ + +Another command you might find yourself needing somewhat often is the 'more' +command. The IRC protocol limits messages to 512 bytes, 60 or so of which +must be devoted to some bookkeeping. Sometimes, however, Supybot wants to +send a message that's longer than that. What it does, then, is break it into +"chunks" and send the first one, following it with ``(X more messages)`` where +X is how many more chunks there are. To get to these chunks, use the `more` +command. One way to try is to look at the default value of +`supybot.replies.genericNoCapability` -- it's so long that it'll stretch +across two messages:: + + $config default + supybot.replies.genericNoCapability + jemfinch|lambda: You're missing some capability + you need. This could be because you actually + possess the anti-capability for the capability + that's required of you, or because the channel + provides that anti-capability by default, or + because the global capabilities include that + anti-capability. Or, it could be because the + channel or the global defaultAllow is set to + False, meaning (1 more message) + $more + jemfinch|lambda: that no commands are allowed + unless explicitly in your capabilities. Either + way, you can't do what you want to do. + +So basically, the bot keeps, for each person it sees, a list of "chunks" which +are "released" one at a time by the `more` command. In fact, you can even get +the more chunks for another user: if you want to see another chunk in the last +command jemfinch gave, for instance, you would just say `more jemfinch` after +which, his "chunks" now belong to you. So, you would just need to say `more` +to continue seeing chunks from jemfinch's initial command. + +Final Word +---------- + +You should now have a solid foundation for using Supybot. You can use the +`list` command to see what plugins your bot has loaded and what commands are +in those plugins; you can use the 'help' command to see how to use a specific +command, and you can use the 'more' command to continue a long response from +the bot. With these three commands, you should have a strong basis with which +to discover the rest of the features of Supybot! + +Do be sure to read our other documentation and make use of the resources we +provide for assistance; this website and, of course, #supybot on +irc.freenode.net if you run into any trouble! diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 000000000..3d511c6cd --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,89 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + -rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Supybot.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Supybot.qhc" + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ + "run these through (pdf)latex." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." diff --git a/docs/PLUGIN_TUTORIAL b/docs/PLUGIN_TUTORIAL.rst similarity index 80% rename from docs/PLUGIN_TUTORIAL rename to docs/PLUGIN_TUTORIAL.rst index 9986e6482..3e8959786 100644 --- a/docs/PLUGIN_TUTORIAL +++ b/docs/PLUGIN_TUTORIAL.rst @@ -1,9 +1,9 @@ -WRITING YOUR FIRST SUPYBOT PLUGIN +================================= +Writing Your First Supybot Plugin +================================= Introduction ============ - First things first - what you need to do before writing a Supybot plugin. - Ok, so you want to write a plugin 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. @@ -28,11 +28,11 @@ with just a few simple commands. a minimal plugin which we will enhance in later sections. The recommended way to start writing a plugin is to use the wizard provided, -'supybot-plugin-create'. Run this from within your local plugins directory, so -we will be able to load the plugin and test it out. +:command:`supybot-plugin-create`. Run this from within your local plugins +directory, so we will be able to load the plugin and test it out. It's very easy to follow, because basically all you have to do is answer three -questions. Here's an example session: +questions. Here's an example session:: [ddipaolo@quinn ../python/supybot]% supybot-plugin-create What should the name of the plugin be? Random @@ -55,9 +55,8 @@ each of those files and see what they're used for. README.txt ========== - Tell me about the plugin. - -In README.txt you put exactly what the boilerplate text says to put in there: +In :file:`README.txt` you put exactly what the boilerplate text says to put in +there: Insert a description of your plugin here, with any notes, etc. about using it. @@ -69,7 +68,7 @@ describe individual commands or anything like that, as those are defined within the plugin code itself as you'll see later. You also don't need to acknowledge any of the developers of the plugin as those too are handled elsewhere. -For our Random plugin, let's make README.txt say this: +For our Random plugin, let's make :file:`README.txt` say this: This plugin contains commands relating to random numbers, and includes: a simple random number generator, the ability to pick a @@ -82,73 +81,72 @@ simple it is! __init__.py =========== - Plugin properties and a few other bits. - -The next file we'll look at is __init__.py. If you're familiar with the Python -import mechanism, you'll know what this file is for. If you're not, think of it -as sort of the "glue" file that pulls all the files in this directory together -when you load the plugin. It's also where there are a few administrative items -live that you really need to maintain. +The next file we'll look at is :file:`__init__.py`. If you're familiar with +the Python import mechanism, you'll know what this file is for. If you're not, +think of it as sort of the "glue" file that pulls all the files in this +directory together when you load the plugin. It's also where there are a few +administrative items live that you really need to maintain. Let's go through the file. For the first 30 lines or so, you'll see the copyright notice that we use for our plugins, only with your name in place (as -prompted in 'supybot-plugin-create'). Feel free to use whatever license you -choose, we don't feel particularly attached to the boilerplate code so it's -yours to license as you see fit even if you don't modify it. For our example, -we'll leave it as is. +prompted in :command:`supybot-plugin-create`). Feel free to use whatever +license you choose, we don't feel particularly attached to the boilerplate +code so it's yours to license as you see fit even if you don't modify it. For +our example, we'll leave it as is. The plugin docstring immediately follows the copyright notice and it (like -README.txt) tells you precisely what it should contain: +:file:`README.txt`) tells you precisely what it should contain: Add a description of the plugin (to be presented to the user inside the wizard) here. This should describe *what* the plugin does. -The "wizard" that it speaks of is the 'supybot-wizard' script that is used to -create working Supybot config file. I imagine that in meeting the prerequisite -of "using a Supybot" first, most readers will have already encountered this -script. Basically, if the user selects to look at this plugin from the list of -plugins to load, it prints out that description to let the user know what it -does, so make sure to be clear on what the purpose of the plugin is. This -should be an abbreviated version of what we put in our README.txt, so let's put -this: +The "wizard" that it speaks of is the :command:`supybot-wizard` script that is +used to create working Supybot config file. I imagine that in meeting the +prerequisite of "using a Supybot" first, most readers will have already +encountered this script. Basically, if the user selects to look at this plugin +from the list of plugins to load, it prints out that description to let the +user know what it does, so make sure to be clear on what the purpose of the +plugin is. This should be an abbreviated version of what we put in our +:file:`README.txt`, so let's put this:: Provides a number of commands for selecting random things. -Next in __init__.py you see a few imports which are necessary, and then four -attributes that you need to modify for your bot and preferably keep up with as -you develop it: __version__, __author__, __contributors__, __url__. +Next in :file:`__init__.py` you see a few imports which are necessary, and +then four attributes that you need to modify for your bot and preferably keep +up with as you develop it: ``__version__``, ``__author__``, +``__contributors__``, ``__url__``. -__version__ is just a version string representing the current working version -of the plugin, and can be anything you want. If you use some sort of RCS, this -would be a good place to have it automatically increment the version string for -any time you edit any of the files in this directory. We'll just make ours -"0.1". +``__version__`` is just a version string representing the current working +version of the plugin, and can be anything you want. If you use some sort of +RCS, this would be a good place to have it automatically increment the version +string for any time you edit any of the files in this directory. We'll just +make ours "0.1". -__author__ should be an instance of the supybot.Author class. A supybot.Author -is simply created by giving it a full name, a short name (preferably IRC nick), -and an e-mail address (all of these are optional, though at least the second -one is expected). So, for example, to create my Author user (though I get to -cheat and use supybot.authors.strike since I'm a main dev, muahaha), I would -do: +``__author__`` should be an instance of the :class:`supybot.Author` class. A +:class:`supybot.Author` is simply created by giving it a full name, a short +name (preferably IRC nick), and an e-mail address (all of these are optional, +though at least the second one is expected). So, for example, to create my +Author user (though I get to cheat and use supybot.authors.strike since I'm a +main dev, muahaha), I would do:: __author__ = supybot.Author('Daniel DiPaolo', 'Strike', 'somewhere@someplace.xxx') Keep this in mind as we get to the next item... -__contributors__ is a dictionary mapping supybot.Author instances to lists of -things they contributed. If someone adds a command named foo to your plugin, -the list for that author should be ["foo"], or perhaps even ["added foo -command"]. The main author shouldn't be referenced here, as it is assumed that -everything that wasn't contributed by someone else was done by the main author. -For now we have no contributors, so we'll leave it blank. +``__contributors__`` is a dictionary mapping supybot.Author instances to lists +of things they contributed. If someone adds a command named foo to your +plugin, the list for that author should be ``["foo"]``, or perhaps even +``["added foo command"]``. The main author shouldn't be referenced here, as it +is assumed that everything that wasn't contributed by someone else was done by +the main author. For now we have no contributors, so we'll leave it blank. -Lastly, the __url__ attribute should just reference the download URL for the -plugin. Since this is just an example, we'll leave this blank. +Lastly, the ``__url__`` attribute should just reference the download URL for +the plugin. Since this is just an example, we'll leave this blank. -The rest of __init__.py really shouldn't be touched unless you are using -third-party modules in your plugin. If you are, then you need to take special -note of the section that looks like this: +The rest of :file:`__init__.py` really shouldn't be touched unless you are +using third-party modules in your plugin. If you are, then you need to take +special note of the section that looks like this:: import config import plugin @@ -158,22 +156,20 @@ note of the section that looks like this: # import them as well! As the comment says, this is one place where you need to make sure you import -the third-party modules, and that you call reload() on them as well. That way, -if we are reloading a plugin on a running bot it will actually reload the -latest code. We aren't using any third-party modules, so we can just leave this -bit alone. +the third-party modules, and that you call :func:`reload` on them as well. +That way, if we are reloading a plugin on a running bot it will actually +reload the latest code. We aren't using any third-party modules, so we can +just leave this bit alone. We're almost through the "boring" part and into the guts of writing Supybot plugins, let's take a look at the next file. config.py ========= - Making our plugin configurable - -config.py is, unsurprisingly, where all the configuration stuff related to -your plugin goes. If you're not familiar with Supybot's configuration system, -I recommend reading the config tutorial before going any further with this -section. +:file:`config.py` is, unsurprisingly, where all the configuration stuff +related to your plugin goes. If you're not familiar with Supybot's +configuration system, I recommend reading the config tutorial before going any +further with this section. So, let's plow through config.py line-by-line like we did the other files. @@ -208,7 +204,7 @@ so we'll leave this as is. Next, you'll see a line that looks very similar to the one in the configure function. This line is used not only to register the plugin prior to being called in configure, but also to store a bit of an alias to the plugin's config -group to make things shorter later on. So, this line should read: +group to make things shorter later on. So, this line should read:: Random = conf.registerPlugin('Random') @@ -224,8 +220,6 @@ Tutorial plugin.py ========= - The meat of the plugin - Here's the moment you've been waiting for, the overview of plugin.py and how to make our plugin actually do stuff. @@ -244,7 +238,7 @@ subclass of callbacks.Plugin for you to start with. The only real content it has is the boilerplate docstring, which you should modify to reflect what the boilerplate text says - it should be useful so that when someone uses the plugin help command to determine how to use this plugin, they'll know what they -need to do. Ours will read something like: +need to do. Ours will read something like:: """This plugin provides a few random number commands and some commands for getting random samples. Use the "seed" command to seed @@ -259,7 +253,7 @@ plugin do something. First of all, to get any random numbers we're going to need a random number generator (RNG). Pretty much everything in our plugin is going to use it, so we'll define it in the constructor of our plugin, __init__. Here we'll also seed it with the current time (standard practice for RNGs). -Here's what our __init__ looks like: +Here's what our __init__ looks like:: def __init__(self, irc): self.__parent = super(Random, self) @@ -276,7 +270,8 @@ to use the plugin name that you are working on instead. So, now we have a RNG in our plugin, let's write a command to get a random number. We'll start with a simple command named random that just returns a -random number from our RNG and takes no arguments. Here's what that looks like: +random number from our RNG and takes no arguments. Here's what that looks +like:: def random(self, irc, msg, args): """takes no arguments @@ -337,7 +332,7 @@ Now let's create a command with some arguments and see how we use those in our plugin commands. Let's allow the user to seed our RNG with their own seed value. We'll call the command seed and take just the seed value as the argument (which we'll require be a floating point value of some sort, though technically -it can be any hashable object). Here's what this command looks like: +it can be any hashable object). Here's what this command looks like:: def seed(self, irc, msg, args, seed): """ @@ -377,7 +372,7 @@ then assigns values to each argument in the arg list after the first four With this alone you'd be able to make some pretty usable plugin commands, but we'll go through two more commands to introduce a few more useful ideas. The next command we'll make is a sample command which gets a random sample of items -from a list provided by the user: +from a list provided by the user:: def sample(self, irc, msg, args, n, items): """ [ ...] @@ -419,7 +414,7 @@ tutorial. Now for the last command that we will add to our plugin.py. This last command will allow the bot users to roll an arbitrary n-sided die, with as many sides -as they so choose. Here's the code for this command: +as they so choose. Here's the code for this command:: def diceroll(self, irc, msg, args, n): """[] @@ -439,7 +434,7 @@ more advanced wrap line than we have used to this point, but to learn more about wrap, you should refer to the wrap tutorial And now that we're done adding plugin commands you should see the boilerplate -stuff at the bottom, which just consists of: +stuff at the bottom, which just consists of:: Class = Random @@ -448,8 +443,6 @@ with plugin.py! test.py ======= - Plugin tests go here. - Now that we've gotten our plugin written, we want to make sure it works. Sure, an easy way to do a somewhat quick check is to start up a bot, load the plugin, and run a few commands on it. If all goes well there, everything's probably @@ -474,7 +467,7 @@ testRandom, testSeed, testSample, and testDiceRoll. Any other methods you want to add are more free-form and should describe what you're testing (don't be afraid to use long names). -First we'll write the testRandom method: +First we'll write the testRandom method:: def testRandom(self): # difficult to test, let's just make sure it works @@ -488,7 +481,7 @@ can do. Next, testSeed. In this method we're just going to check that the command itself functions. In another test method later on we will check and make sure that the seed produces reproducible random numbers like we would hope it would, -but for now we just test it like we did random in 'testRandom': +but for now we just test it like we did random in 'testRandom':: def testSeed(self): # just make sure it works @@ -496,7 +489,7 @@ but for now we just test it like we did random in 'testRandom': Now for testSample. Since this one takes more arguments it makes sense that we test more scenarios in this one. Also this time we have to make sure that we -hit the error that we coded in there given the right conditions: +hit the error that we coded in there given the right conditions:: def testSample(self): self.assertError('sample 20 foo') @@ -510,7 +503,7 @@ right number of elements and that they are formatted correctly when we give 1, 2, or 3 element lists. And for the last of our basic "check to see that it works" functions, -testDiceRoll: +testDiceRoll:: def testDiceRoll(self): self.assertActionRegexp('diceroll', 'rolls a \d') @@ -520,7 +513,7 @@ should roll a single-digit number. And that's about all we can test reliably here, so that's all we do. Lastly, we wanted to check and make sure that seeding the RNG with seed -actually took effect like it's supposed to. So, we write another test method: +actually took effect like it's supposed to. So, we write another test method:: def testSeedActuallySeeds(self): # now to make sure things work repeatably @@ -539,28 +532,26 @@ another random number and make sure it is distinct from the prior one. Conclusion ========== - Now you're ready to write Supybot plugins! - You are now very well-prepared to write Supybot plugins. Now for a few words of wisdom with regards to Supybot plugin-writing. - * Read other people's plugins, especially the included plugins and ones by - the core developers. We (the Supybot dev team) can't possibly document - all the awesome things that Supybot plugins can do, but we try. - Nevertheless there are some really cool things that can be done that - aren't very well-documented. +* Read other people's plugins, especially the included plugins and ones by + the core developers. We (the Supybot dev team) can't possibly document + all the awesome things that Supybot plugins can do, but we try. + Nevertheless there are some really cool things that can be done that + aren't very well-documented. - * Hack new functionality into existing plugins first if writing a new - plugin is too daunting. +* Hack new functionality into existing plugins first if writing a new + plugin is too daunting. - * Come ask us questions in #supybot on Freenode or OFTC. Going back to the - first point above, the developers themselves can help you even more than - the docs can (though we prefer you read the docs first). +* Come ask us questions in #supybot on Freenode or OFTC. Going back to the + first point above, the developers themselves can help you even more than + the docs can (though we prefer you read the docs first). - * Share your plugins with the world and make Supybot all that more - attractive for other users so they will want to write their plugins for - Supybot as well. +* Share your plugins with the world and make Supybot all that more + attractive for other users so they will want to write their plugins for + Supybot as well. - * Read, read, read all the documentation. +* Read, read, read all the documentation. - * And of course, have fun writing your plugins. +* And of course, have fun writing your plugins. diff --git a/docs/STYLE b/docs/STYLE deleted file mode 100644 index df2970b24..000000000 --- a/docs/STYLE +++ /dev/null @@ -1,188 +0,0 @@ -==================================================================== -Code not following these style guidelines fastidiously is likely -(*very* likely) not to be accepted into the Supybot core. -==================================================================== - -Read PEP 8 (Guido's Style Guide) and know that we use almost all the -same style guidelines. - -Maximum line length is 79 characters. 78 is a safer bet, though. -This is **NON-NEGOTIABLE**. Your code will not be accepted while you -are violating this guidline. - -Identation is 4 spaces per level. No tabs. This also is -**NON-NEGOTIABLE**. Your code, again, will *never* be accepted while -you have literal tabs in it. - -Single quotes are used for all string literals that aren't docstrings. -They're just easier to type. - -Triple double quotes (""") are always used for docstrings. - -Raw strings (r'' or r"") should be used for regular expressions. - -Spaces go around all operators (except around '=' in default -arguments to functions) and after all commas (unless doing so keeps a -line within the 79 character limit). - -Functions calls should look like this: "foo(bar(baz(x), y))". They -should not look like "foo (bar (baz (x), y))", or like -"foo(bar(baz(x), y) )" or like anything else. I hate extraneous -spaces. - -Class names are StudlyCaps. Method and function names are camelCaps -(StudlyCaps with an initial lowercase letter). If variable and -attribute names can maintain readability without being camelCaps, -then they should be entirely in lowercase, otherwise they should also -use camelCaps. Plugin names are StudlyCaps. - -Imports should always happen at the top of the module, one import per -line (so if imports need to be added or removed later, it can be done -easily). - -Unless absolutely required by some external force, imports should be -ordered by the string length of the module imported. I just think it -looks prettier. - -A blank line should be between all consecutive method declarations in -a class definition. Two blank lines should be between all -consecutive class definitions in a file. Comments are even better -than blank lines for separating classes. - -Database filenames should generally begin with the name of the plugin -and the extension should be 'db'. plugins.DBHandler does this -already. - -Whenever creating a file descriptor or socket, keep a reference -around and be sure to close it. There should be no code like this: - s = urllib2.urlopen('url').read() -Instead, do this: - fd = urllib2.urlopen('url') - try: - s = fd.read() - finally: - fd.close() -This is to be sure the bot doesn't leak file descriptors. - -All plugin files should include a docstring decsribing what the -plugin does. This docstring will be returned when the user is -configuring the plugin. All plugin classes should also include a -docstring describing how to do things with the plugin; this docstring -will be returned when the user requests help on a plugin name. - -Method docstrings in classes deriving from callbacks.Privmsg should -include an argument list as their first line, and after that a blank -line followed by a longer description of what the command does. The -argument list is used by the 'syntax' command, and the longer -description is used by the 'help' command. - -Whenever joining more than two strings, use string interpolation, not -addition: - s = x + y + z # Bad. - s = '%s%s%s' % (x, y, z) # Good. - s = ''.join([x, y, z]) # Best, but not as general. -This has to do with efficiency; the intermediate string x+y is made -(and thus copied) before x+y+z is made, so it's less efficient. -People who use string concatenation in a for loop will be swiftly -kicked in the head. - -When writing strings that have formatting characters in them, don't -use anything but %s unless you absolutely must. In particular, %d -should never be used, it's less general than %s and serves no useful -purpose. If you got the %d wrong, you'll get an exception that says, -"foo instance can't be converted to an integer." But if you use %s, -you'll get to see your nice little foo instance, if it doesn't -convert to a string cleanly, and if it does convert cleanly, you'll -get to see what you expect to see. Basically, %d just sucks. - -As a corrolary to the above, note that sometimes %f is used, but on -when floats need to be formatted, e.g., %.2f. - -Use the log module to its fullest; when you need to print some values -to debug, use self.log.debug to do so, and leave those statements in -the code (commented out) so they can later be re-enabled. Remember -that once code is buggy, it tends to have more bugs, and you'll -probably need those print statements again. - -While on the topic of logs, note that we do not use % (i.e., -str.__mod__) with logged strings; we simple pass the format -parameters as additional arguments. The reason is simple: the -logging module supports it, and it's cleaner (fewer tokens/glyphs) to -read. - -While still on the topic of logs, it's also important to pick the -appropriate log level for given information. - DEBUG: Appropriate to tell a programmer *how* we're doing - something (i.e., debugging printfs, basically). If - you're trying to figure out why your code doesn't work, - DEBUG is the new printf -- use that, and leave the - statements in your code. - INFO: Appropriate to tell a user *what* we're doing, when what - we're doing isn't important for the user to pay attention - to. A user who likes to keep up with things should enjoy - watching our logging at the INFO level; it shouldn't be - too low-level, but it should give enough information that - it keeps him relatively interested at peak times. - WARNING: Appropriate to tell a user when we're doing something - that he really ought to pay attention to. Users should - see WARNING and think, "Hmm, should I tell the Supybot - developers about this?" Later, he should decide not to, - but it should give the user a moment to pause and think - about what's actually happening with his bot. - ERROR: Appropriate to tell a user when something has gone wrong. - Uncaught exceptions are ERRORs. Conditions that we - absolutely want to hear about should be errors. Things - that should *scare* the user should be errors. - CRITICAL: Not really appropriate. I can think of no absolutely - critical issue yet encountered in Supybot; the only - possible thing I can imagine is to notify the user that - the partition on which Supybot is running has filled up. - That would be a CRITICAL condition, but it would also be - hard to log :) - -All plugins should have test cases written for them. Even if it -doesn't actually test anything but just exists, it's good to have the -test there so there's a place to add more tests later (and so we can -be sure that all plugins are adequately documented; PluginTestCase -checks that every command has documentation) - -All uses of eval() that expect to get integrated in Supybot must be -approved by jemfinch, no exceptions. Chances are, it won't be -accepted. Have you looked at utils.safeEval? - -SQL table names should be all-lowercase and include underscores to -separate words. This is because SQL itself is case-insensitive. -This doesn't change, however the fact that variable/member names -should be camel case. - -SQL statements in code should put SQL words in ALL CAPS: -"""SELECT quote FROM quotes ORDER BY random() LIMIT 1""". This makes -SQL significantly easier to read. - -Common variable names: - L => an arbitrary list. - t => an arbitrary tuple. - x => an arbitrary float. - s => an arbitrary string. - f => an arbitrary function. - p => an arbitrary predicate. - i,n => an arbitrary integer. - cb => an arbitrary callback. - db => a database handle. - fd => a file-like object. - msg => an ircmsgs.IrcMsg object. - irc => an irclib.Irc object (or proxy) - nick => a string that is an IRC nick. - channel => a string that is an IRC channel. - hostmask => a string that is a user's IRC prefix. -When the semantic functionality (that is, the "meaning" of a variable -is obvious from context, one of these names should be used. This -just makes it easier for people reading our code to know what a -variable represents without scouring the surrounding code. - -Multiple variable assignments should always be surrounded with -parentheses -- i.e., if you're using the partition function, then -your assignment statement should look like -(good, bad) = partition(p, L). The parentheses make it obvious that -you're doing a multiple assignment, and that's important because I -hate reading code and wondering where a variable came from. diff --git a/docs/STYLE.rst b/docs/STYLE.rst new file mode 100644 index 000000000..6a21497c6 --- /dev/null +++ b/docs/STYLE.rst @@ -0,0 +1,213 @@ +================ +Style Guidelines +================ + +**Note:** Code not following these style guidelines fastidiously is likely +(*very* likely) not to be accepted into the Supybot core. + +* Read :pep:`8` (Guido's Style Guide) and know that we use almost all the + same style guidelines. + +* Maximum line length is 79 characters. 78 is a safer bet, though. + This is **NON-NEGOTIABLE**. Your code will not be accepted while you are + violating this guidline. + +* Identation is 4 spaces per level. No tabs. This also is + **NON-NEGOTIABLE**. Your code, again, will *never* be accepted while you + have literal tabs in it. + +* Single quotes are used for all string literals that aren't docstrings. + They're just easier to type. + +* Triple double quotes (``"""``) are always used for docstrings. + +* Raw strings (``r''`` or ``r""``) should be used for regular expressions. + +* Spaces go around all operators (except around ``=`` in default arguments to + functions) and after all commas (unless doing so keeps a line within the 79 + character limit). + +* Functions calls should look like ``foo(bar(baz(x), y))``. They should + not look like ``foo (bar (baz (x), y))``, or like ``foo(bar(baz(x), y) )`` + or like anything else. I hate extraneous spaces. + +* Class names are StudlyCaps. Method and function names are camelCaps + (StudlyCaps with an initial lowercase letter). If variable and attribute + names can maintain readability without being camelCaps, then they should be + entirely in lowercase, otherwise they should also use camelCaps. Plugin + names are StudlyCaps. + +* Imports should always happen at the top of the module, one import per line + (so if imports need to be added or removed later, it can be done easily). + +* Unless absolutely required by some external force, imports should be ordered + by the string length of the module imported. I just think it looks + prettier. + +* A blank line should be between all consecutive method declarations in a + class definition. Two blank lines should be between all consecutive class + definitions in a file. Comments are even better than blank lines for + separating classes. + +* Database filenames should generally begin with the name of the plugin and + the extension should be 'db'. plugins.DBHandler does this already. + +* Whenever creating a file descriptor or socket, keep a reference around and + be sure to close it. There should be no code like this:: + + s = urllib2.urlopen('url').read() + + Instead, do this:: + + fd = urllib2.urlopen('url') + try: + s = fd.read() + finally: + fd.close() + + This is to be sure the bot doesn't leak file descriptors. + +* All plugin files should include a docstring decsribing what the plugin does. + This docstring will be returned when the user is configuring the plugin. + All plugin classes should also include a docstring describing how to do + things with the plugin; this docstring will be returned when the user + requests help on a plugin name. + +* Method docstrings in classes deriving from callbacks.Privmsg should include + an argument list as their first line, and after that a blank line followed + by a longer description of what the command does. The argument list is used + by the ``syntax`` command, and the longer description is used by the + ``help`` command. + +* Whenever joining more than two strings, use string interpolation, not + addition:: + + s = x + y + z # Bad. + s = '%s%s%s' % (x, y, z) # Good. + s = ''.join([x, y, z]) # Best, but not as general. + + This has to do with efficiency; the intermediate string x+y is made (and + thus copied) before x+y+z is made, so it's less efficient. People who use + string concatenation in a for loop will be swiftly kicked in the head. + +* When writing strings that have formatting characters in them, don't use + anything but ``%s`` unless you absolutely must. In particular, ``%d`` should never + be used, it's less general than ``%s`` and serves no useful purpose. If you got + the ``%d`` wrong, you'll get an exception that says, "foo instance can't be + converted to an integer." But if you use ``%s``, you'll get to see your nice + little foo instance, if it doesn't convert to a string cleanly, and if it + does convert cleanly, you'll get to see what you expect to see. Basically, + ``%d`` just sucks. + +* As a corrolary to the above, note that sometimes ``%f`` is used, but on when + floats need to be formatted, e.g., ``%.2f``. + +* Use the log module to its fullest; when you need to print some values to + debug, use self.log.debug to do so, and leave those statements in the code + (commented out) so they can later be re-enabled. Remember that once code is + buggy, it tends to have more bugs, and you'll probably need those print + statements again. + +* While on the topic of logs, note that we do not use % (i.e., str.__mod__) + with logged strings; we simple pass the format parameters as additional + arguments. The reason is simple: the logging module supports it, and it's + cleaner (fewer tokens/glyphs) to read. + +* While still on the topic of logs, it's also important to pick the + appropriate log level for given information. + + * DEBUG: Appropriate to tell a programmer *how* we're doing something + (i.e., debugging printfs, basically). If you're trying to figure out why + your code doesn't work, DEBUG is the new printf -- use that, and leave the + statements in your code. + + * INFO: Appropriate to tell a user *what* we're doing, when what we're + doing isn't important for the user to pay attention to. A user who likes + to keep up with things should enjoy watching our logging at the INFO + level; it shouldn't be too low-level, but it should give enough + information that it keeps him relatively interested at peak times. + + * WARNING: Appropriate to tell a user when we're doing something that he + really ought to pay attention to. Users should see WARNING and think, + "Hmm, should I tell the Supybot developers about this?" Later, he should + decide not to, but it should give the user a moment to pause and think + about what's actually happening with his bot. + + * ERROR: Appropriate to tell a user when something has gone wrong. + Uncaught exceptions are ERRORs. Conditions that we absolutely want to + hear about should be errors. Things that should *scare* the user should + be errors. + + * CRITICAL: Not really appropriate. I can think of no absolutely critical + issue yet encountered in Supybot; the only possible thing I can imagine is + to notify the user that the partition on which Supybot is running has + filled up. That would be a CRITICAL condition, but it would also be hard + to log :) + + +* All plugins should have test cases written for them. Even if it doesn't + actually test anything but just exists, it's good to have the test there so + there's a place to add more tests later (and so we can be sure that all + plugins are adequately documented; PluginTestCase checks that every command + has documentation) + +* All uses of eval() that expect to get integrated in Supybot must be approved + by jemfinch, no exceptions. Chances are, it won't be accepted. Have you + looked at utils.safeEval? + +* SQL table names should be all-lowercase and include underscores to separate + words. This is because SQL itself is case-insensitive. This doesn't + change, however the fact that variable/member names should be camel case. + +* SQL statements in code should put SQL words in ALL CAPS:: + + """SELECT quote FROM quotes ORDER BY random() LIMIT 1""" + + This makes SQL significantly easier to read. + +* Common variable names + + - L => an arbitrary list. + + - t => an arbitrary tuple. + + - x => an arbitrary float. + + - s => an arbitrary string. + + - f => an arbitrary function. + + - p => an arbitrary predicate. + + - i,n => an arbitrary integer. + + - cb => an arbitrary callback. + + - db => a database handle. + + - fd => a file-like object. + + - msg => an ircmsgs.IrcMsg object. + + - irc => an irclib.Irc object (or proxy) + + - nick => a string that is an IRC nick. + + - channel => a string that is an IRC channel. + + - hostmask => a string that is a user's IRC prefix. + + When the semantic functionality (that is, the "meaning" of a variable is + obvious from context), one of these names should be used. This just makes it + easier for people reading our code to know what a variable represents + without scouring the surrounding code. + +* Multiple variable assignments should always be surrounded with parentheses + -- i.e., if you're using the partition function, then your assignment + statement should look like:: + + (good, bad) = partition(p, L) + + The parentheses make it obvious that you're doing a multiple assignment, and + that's important because I hate reading code and wondering where a variable + came from. diff --git a/docs/USING_UTILS b/docs/USING_UTILS deleted file mode 100644 index 744ea278b..000000000 --- a/docs/USING_UTILS +++ /dev/null @@ -1,343 +0,0 @@ -Using Supybot's utils module ----------------------------- -Supybot provides a wealth of utilities for plugin writers in the supybot.utils -module, this tutorial describes these utilities and shows you how to use them. - -str.py -====== -The Format Function - -The supybot.utils.str module provides a bunch of utility functions for -handling string values. This section contains a quick rundown of all of the -functions available, along with descriptions of the arguments they take. First -and foremost is the format function, which provides a lot of capability in -just one function that uses string-formatting style to accomplish a lot. So -much so that it gets its own section in this tutorial. All other functions -will be in other sections. format takes several arguments - first, the format -string (using the format characters described below), and then after that, -each individual item to be formatted. Do not attempt to use the % operator to -do the formatting because that will fall back on the normal string formatting -operator. The format function uses the following string formatting characters. - - * % - literal "%" - * i - integer - * s - string - * f - float - * r - repr - * b - form of the verb "to be" (takes an int) - * h - form of the verb "to have" (takes an int) - * L - commaAndify (takes a list of strings or a tuple of ([strings], and)) - * p - pluralize (takes a string) - * q - quoted (takes a string) - * n - n items (takes a 2-tuple of (n, item) or a 3-tuple of (n, between, item)) - * t - time, formatted (takes an int) - * u - url, wrapped in braces - -Here are a few examples to help elaborate on the above descriptions: - - >>> format("Error %q has been reported %n. For more information, see %u.", - "AttributeError", (5, "time"), "http://supybot.com") - - 'Error "AttributeError" has been reported 5 times. For more information, - see .' - - >>> i = 4 - >>> format("There %b %n at this time. You are only allowed %n at any given - time", i, (i, "active", "thread"), (5, "active", "thread")) - 'There are 4 active threads at this time. You are only allowed 5 active - threads at any given time' - - >>> i = 1 - >>> format("There %b %n at this time. You are only allowed %n at any given - time", i, (i, "active", "thread"), (5, "active", "thread")) - 'There is 1 active thread at this time. You are only allowed 5 active - threads at any given time' - - >>> ops = ["foo", "bar", "baz"] - >>> format("The following %n %h the %s capability: %L", (len(ops), "user"), - len(ops), "op", ops) - 'The following 3 users have the op capability: foo, bar, and baz' - -As you can see, you can combine all sorts of combinations of formatting -strings into one. In fact, that was the major motivation behind format. We -have specific functions that you can use individually for each of those -formatting types, but it became much easier just to use special formatting -chars and the format function than concatenating a bunch of strings that were -the result of other utils.str functions. - -The Other Functions - -These are the functions that can't be handled by format. They are sorted in -what I perceive to be the general order of usefulness (and I'm leaving the -ones covered by format for the next section). - - * ellipsisify(s, n) - Returns a shortened version of a string. Produces up - to the first n chars at the nearest word boundary. - - s: the string to be shortened - - n: the number of characters to shorten it to - - * perlReToPythonRe(s) - Converts a Perl-style regexp (e.g., "/abcd/i" or - "m/abcd/i") to an actual Python regexp (an re object) - - s: the regexp string - - * perlReToReplacer(s) - converts a perl-style replacement regexp (eg, - "s/foo/bar/g") to a Python function that performs such a replacement - - s: the regexp string - - * dqrepr(s) - Returns a repr() of s guaranteed to be in double quotes. - (Double Quote Repr) - - s: the string to be double-quote repr()'ed - - * toBool(s) - Determines whether or not a string means True or False and - returns the appropriate boolean value. True is any of "true", "on", - "enable", "enabled", or "1". False is any of "false", "off", "disable", - "disabled", or "0". - - s: the string to determine the boolean value for - - * rsplit(s, sep=None, maxsplit=-1) - functionally the same as str.split in - the Python standard library except splitting from the right instead of the - left. Python 2.4 has str.rsplit (which this function defers to for those - versions >= 2.4), but Python 2.3 did not. - - s: the string to be split - - sep: the separator to split on, defaults to whitespace - - maxsplit: the maximum number of splits to perform, -1 splits all - possible splits. - - * normalizeWhitespace(s) - reduces all multi-spaces in a string to a - single space - - s: the string to normalize - - * depluralize(s) - the opposite of pluralize - - s: the string to depluralize - - * unCommaThe(s) - Takes a string of the form "foo, the" and turns it into - "the foo" - - s: string, the - - * distance(s, t) - computes the levenshtein distance (or "edit distance") - between two strings - - s: the first string - - t: the second string - - * soundex(s, length=4) - computes the soundex for a given string - - s: the string to compute the soundex for - - length: the length of the soundex to generate - - * matchCase(s1, s2) - Matches the case of the first string in the second - string. - - s1: the first string - - s2: the string which will be made to match the case of the first - -The Commands Format Already Covers - -These commands aren't necessary because you can achieve them more easily by -using the format command, but they exist if you decide you want to use them -anyway though it is greatly discouraged for general use. - - * commaAndify(seq, comma=",", And="and") - transforms a list of items into - a comma separated list with an "and" preceding the last element. For - example, ["foo", "bar", "baz"] becomes "foo, bar, and baz". Is smart - enough to convert two-element lists to just "item1 and item2" as well. - - seq: the sequence of items (don't have to be strings, but need to - be 'str()'-able) - - comma: the character to use to separate the list - - And: the word to use before the last element - - * pluralize(s) - Returns the plural of a string. Put any exceptions to the - general English rules of pluralization in the plurals dictionary in - supybot.utils.str. - - s: the string to pluralize - - * nItems(n, item, between=None) - returns a string that describes a given - number of an item (with any string between the actual number and the item - itself), handles pluralization with the pluralize function above. Note - that the arguments here are in a different order since between is - optional. - - n: the number of items - - item: the type of item - - between: the optional string that goes between the number and the - type of item - - * quoted(s) - Returns the string surrounded by double-quotes. - - s: the string to quote - - * be(i) - Returns the proper form of the verb "to be" based on the number - provided (be(1) is "is", be(anything else) is "are") - - i: the number of things that "be" - - * has(i) - Returns the proper form of the verb "to have" based on the - number provided (has(1) is "has", has(anything else) is "have") - - i: the number of things that "has" - -structures.py -============= -Intro - -This module provides a number of useful data structures that aren't found in -the standard Python library. For the most part they were created as needed for -the bot and plugins themselves, but they were created in such a way as to be -of general use for anyone who needs a data structure that performs a like -duty. As usual in this document, I'll try and order these in order of -usefulness, starting with the most useful. - -The queue classes - -The structures module provides two general-purpose queue classes for you to -use. The "queue" class is a robust full-featured queue that scales up to -larger sized queues. The "smallqueue" class is for queues that will contain -fewer (less than 1000 or so) items. Both offer the same common interface, -which consists of: - - * a constructor which will optionally accept a sequence to start the queue - off with - * enqueue(item) - adds an item to the back of the queue - * dequeue() - removes (and returns) the item from the front of the queue - * peek() - returns the item from the front of the queue without removing - it - * reset() - empties the queue entirely - -In addition to these general-use queue classes, there are two other more -specialized queue classes as well. The first is the "TimeoutQueue" which holds -a queue of items until they reach a certain age and then they are removed from -the queue. It features the following: - - * TimeoutQueue(timeout, queue=None) - you must specify the timeout (in - seconds) in the constructor. Note that you can also optionally pass it a - queue which uses any implementation you wish to use whether it be one of - the above (queue or smallqueue) or if it's some custom queue you create - that implements the same interface. If you don't pass it a queue instance - to use, it will build its own using smallqueue. - * reset(), enqueue(item), dequeue() - all same as above queue classes - * setTimeout(secs) - allows you to change the timeout value - -And for the final queue class, there's the "MaxLengthQueue" class. As you may -have guessed, it's a queue that is capped at a certain specified length. It -features the following: - - * MaxLengthQueue(length, seq=()) - the constructor naturally requires that - you set the max length and it allows you to optionally pass in a sequence - to be used as the starting queue. The underlying implementation is - actually the queue from before. - * enqueue(item) - adds an item onto the back of the queue and if it would - push it over the max length, it dequeues the item on the front (it does - not return this item to you) - * all the standard methods from the queue class are inherited for this class - -The Other Structures - -The most useful of the other structures is actually very similar to the -"MaxLengthQueue". It's the "RingBuffer", which is essentially a MaxLengthQueue -which fills up to its maximum size and then circularly replaces the old -contents as new entries are added instead of dequeuing. It features the -following: - - * RingBuffer(size, seq=()) - as with the MaxLengthQueue you specify the - size of the RingBuffer and optionally give it a sequence. - * append(item) - adds item to the end of the buffer, pushing out an item - from the front if necessary - * reset() - empties out the buffer entirely - * resize(i) - shrinks/expands the RingBuffer to the size provided - * extend(seq) - append the items from the provided sequence onto the end - of the RingBuffer - -The next data structure is the TwoWayDictionary, which as the name implies is -a dictionary in which key-value pairs have mappings going both directions. It -features the following: - - * TwoWayDictionary(seq=(), **kwargs) - Takes an optional sequence of (key, - value) pairs as well as any key=value pairs specified in the constructor - as initial values for the two-way dict. - * other than that, no extra features that a normal Python dict doesn't - already offer with the exception that any (key, val) pair added to the - dict is also added as (val, key) as well, so the mapping goes both ways. - Elements are still accessed the same way you always do with Python - 'dict's. - -There is also a MultiSet class available, but it's very unlikely that it will -serve your purpose, so I won't go into it here. The curious coder can go check -the source and see what it's all about if they wish (it's only used once in our -code, in the Relay plugin). - -web.py -====== -The web portion of Supybot's utils module is mainly used for retrieving data -from websites but it also has some utility functions pertaining to HTML and -email text as well. The functions in web are listed below, once again in order -of usefulness. - - * getUrl(url, size=None, headers=None) - gets the data at the URL provided - and returns it as one large string - - url: the location of the data to be retrieved or a urllib2.Request - object to be used in the retrieval - - size: the maximum number of bytes to retrieve, defaults to None, - meaning that it is to try to retrieve all data - - headers: a dictionary mapping header types to header data - - * getUrlFd(url, headers=None) - returns a file-like object for a url - - url: the location of the data to be retrieved or a urllib2.Request - object to be used in the retrieval - - headers: a dictionary mapping header types to header data - - * htmlToText(s, tagReplace=" ") - strips out all tags in a string of HTML, - replacing them with the specified character - - s: the HTML text to strip the tags out of - - tagReplace: the string to replace tags with - - * strError(e) - pretty-printer for web exceptions, returns a descriptive - string given a web-related exception - - e: the exception to pretty-print - - * mungeEmail(s) - a naive e-mail obfuscation function, replaces "@" with - "AT" and "." with "DOT" - - s: the e-mail address to obfuscate - - * getDomain(url) - returns the domain of a URL - - url: the URL in question - -The Best of the Rest -==================== - Highlights the most useful of the remaining functionality in supybot.utils - -Intro - -Rather than document each of the remaining portions of the supybot.utils -module, I've elected to just pick out the choice bits from specific parts and -document those instead. Here they are, broken out by module name. - -supybot.utils.file - file utilities - - * touch(filename) - updates the access time of a file by opening it for - writing and immediately closing it - * mktemp(suffix="") - creates a decent random string, suitable for a - temporary filename with the given suffix, if - provided - * the AtomicFile class - used for files that need to be atomically - written, i.e., if there's a failure the original - file remains unmodified. For more info consult - file.py in src/utils - -supybot.utils.gen - general utilities - - * timeElapsed(elapsed, [lots of optional args]) - given the number of - seconds elapsed, returns a string with the English description of the - amount of time passed, consult gen.py in src/utils for the exact - argument list and documentation if you feel you could use this - function. - * exnToString(e) - improved exception-to-string function. Provides nicer - output than a simple str(e). - * InsensitivePreservingDict class - a dict class that is case-insensitive - when accessing keys - -supybot.utils.iter - iterable utilities - - * len(iterable) - returns the length of a given iterable - * groupby(key, iterable) - equivalent to the itertools.groupby function - available as of Python 2.4. Provided for - backwards compatibility. - * any(p, iterable) - Returns true if any element in the iterable satisfies - the predicate p - * all(p, iterable) - Returns true if all elements in the iterable satisfy - the predicate p - * choice(iterable) - Returns a random element from the iterable - - diff --git a/docs/USING_UTILS.rst b/docs/USING_UTILS.rst new file mode 100644 index 000000000..262895318 --- /dev/null +++ b/docs/USING_UTILS.rst @@ -0,0 +1,379 @@ +============================ +Using Supybot's utils module +============================ +Supybot provides a wealth of utilities for plugin writers in the supybot.utils +module, this tutorial describes these utilities and shows you how to use them. + +str.py +====== +The Format Function +------------------- + +The supybot.utils.str module provides a bunch of utility functions for +handling string values. This section contains a quick rundown of all of the +functions available, along with descriptions of the arguments they take. First +and foremost is the format function, which provides a lot of capability in +just one function that uses string-formatting style to accomplish a lot. So +much so that it gets its own section in this tutorial. All other functions +will be in other sections. format takes several arguments - first, the format +string (using the format characters described below), and then after that, +each individual item to be formatted. Do not attempt to use the % operator to +do the formatting because that will fall back on the normal string formatting +operator. The format function uses the following string formatting characters. + +* % - literal ``%`` +* i - integer +* s - string +* f - float +* r - repr +* b - form of the verb ``to be`` (takes an int) +* h - form of the verb ``to have`` (takes an int) +* L - commaAndify (takes a list of strings or a tuple of ([strings], and)) +* p - pluralize (takes a string) +* q - quoted (takes a string) +* n - n items (takes a 2-tuple of (n, item) or a 3-tuple of (n, between, item)) +* t - time, formatted (takes an int) +* u - url, wrapped in braces + +Here are a few examples to help elaborate on the above descriptions:: + + >>> format("Error %q has been reported %n. For more information, see %u.", + "AttributeError", (5, "time"), "http://supybot.com") + + 'Error "AttributeError" has been reported 5 times. For more information, + see .' + + >>> i = 4 + >>> format("There %b %n at this time. You are only allowed %n at any given + time", i, (i, "active", "thread"), (5, "active", "thread")) + 'There are 4 active threads at this time. You are only allowed 5 active + threads at any given time' + + >>> i = 1 + >>> format("There %b %n at this time. You are only allowed %n at any given + time", i, (i, "active", "thread"), (5, "active", "thread")) + 'There is 1 active thread at this time. You are only allowed 5 active + threads at any given time' + + >>> ops = ["foo", "bar", "baz"] + >>> format("The following %n %h the %s capability: %L", (len(ops), "user"), + len(ops), "op", ops) + 'The following 3 users have the op capability: foo, bar, and baz' + +As you can see, you can combine all sorts of combinations of formatting +strings into one. In fact, that was the major motivation behind format. We +have specific functions that you can use individually for each of those +formatting types, but it became much easier just to use special formatting +chars and the format function than concatenating a bunch of strings that were +the result of other utils.str functions. + +The Other Functions +------------------- + +These are the functions that can't be handled by format. They are sorted in +what I perceive to be the general order of usefulness (and I'm leaving the +ones covered by format for the next section). + +* ellipsisify(s, n) - Returns a shortened version of a string. Produces up to + the first n chars at the nearest word boundary. + + - s: the string to be shortened + - n: the number of characters to shorten it to + +* perlReToPythonRe(s) - Converts a Perl-style regexp (e.g., "/abcd/i" or + "m/abcd/i") to an actual Python regexp (an re object) + + - s: the regexp string + +* perlReToReplacer(s) - converts a perl-style replacement regexp (eg, + "s/foo/bar/g") to a Python function that performs such a replacement + + - s: the regexp string + +* dqrepr(s) - Returns a repr() of s guaranteed to be in double quotes. + (Double Quote Repr) + + - s: the string to be double-quote repr()'ed + +* toBool(s) - Determines whether or not a string means True or False and + returns the appropriate boolean value. True is any of "true", "on", + "enable", "enabled", or "1". False is any of "false", "off", "disable", + "disabled", or "0". + + - s: the string to determine the boolean value for + +* rsplit(s, sep=None, maxsplit=-1) - functionally the same as str.split in the + Python standard library except splitting from the right instead of the left. + Python 2.4 has str.rsplit (which this function defers to for those versions + >= 2.4), but Python 2.3 did not. + + - s: the string to be split + - sep: the separator to split on, defaults to whitespace + - maxsplit: the maximum number of splits to perform, -1 splits all possible + splits. + +* normalizeWhitespace(s) - reduces all multi-spaces in a string to a single + space + + - s: the string to normalize + +* depluralize(s) - the opposite of pluralize + + - s: the string to depluralize + +* unCommaThe(s) - Takes a string of the form "foo, the" and turns it into "the + foo" + + - s: string, the + +* distance(s, t) - computes the levenshtein distance (or "edit distance") + between two strings + + - s: the first string + - t: the second string + +* soundex(s, length=4) - computes the soundex for a given string + + - s: the string to compute the soundex for + - length: the length of the soundex to generate + +* matchCase(s1, s2) - Matches the case of the first string in the second + string. + + - s1: the first string + - s2: the string which will be made to match the case of the first + +The Commands Format Already Covers +---------------------------------- + +These commands aren't necessary because you can achieve them more easily by +using the format command, but they exist if you decide you want to use them +anyway though it is greatly discouraged for general use. + +* commaAndify(seq, comma=",", And="and") - transforms a list of items into a + comma separated list with an "and" preceding the last element. For example, + ["foo", "bar", "baz"] becomes "foo, bar, and baz". Is smart enough to + convert two-element lists to just "item1 and item2" as well. + + - seq: the sequence of items (don't have to be strings, but need to be + 'str()'-able) + - comma: the character to use to separate the list + - And: the word to use before the last element + +* pluralize(s) - Returns the plural of a string. Put any exceptions to the + general English rules of pluralization in the plurals dictionary in + supybot.utils.str. + + - s: the string to pluralize + +* nItems(n, item, between=None) - returns a string that describes a given + number of an item (with any string between the actual number and the item + itself), handles pluralization with the pluralize function above. Note that + the arguments here are in a different order since between is optional. + + - n: the number of items + - item: the type of item + - between: the optional string that goes between the number and the type of + item + +* quoted(s) - Returns the string surrounded by double-quotes. + + - s: the string to quote + +* be(i) - Returns the proper form of the verb "to be" based on the number + provided (be(1) is "is", be(anything else) is "are") + + - i: the number of things that "be" + +* has(i) - Returns the proper form of the verb "to have" based on the number + provided (has(1) is "has", has(anything else) is "have") + + - i: the number of things that "has" + +structures.py +============= +Intro +----- + +This module provides a number of useful data structures that aren't found in +the standard Python library. For the most part they were created as needed for +the bot and plugins themselves, but they were created in such a way as to be +of general use for anyone who needs a data structure that performs a like +duty. As usual in this document, I'll try and order these in order of +usefulness, starting with the most useful. + +The queue classes +----------------- + +The structures module provides two general-purpose queue classes for you to +use. The "queue" class is a robust full-featured queue that scales up to +larger sized queues. The "smallqueue" class is for queues that will contain +fewer (less than 1000 or so) items. Both offer the same common interface, +which consists of: + +* a constructor which will optionally accept a sequence to start the queue off + with +* enqueue(item) - adds an item to the back of the queue +* dequeue() - removes (and returns) the item from the front of the queue +* peek() - returns the item from the front of the queue without removing it +* reset() - empties the queue entirely + +In addition to these general-use queue classes, there are two other more +specialized queue classes as well. The first is the "TimeoutQueue" which holds +a queue of items until they reach a certain age and then they are removed from +the queue. It features the following: + +* TimeoutQueue(timeout, queue=None) - you must specify the timeout (in + seconds) in the constructor. Note that you can also optionally pass it a + queue which uses any implementation you wish to use whether it be one of the + above (queue or smallqueue) or if it's some custom queue you create that + implements the same interface. If you don't pass it a queue instance to use, + it will build its own using smallqueue. + + - reset(), enqueue(item), dequeue() - all same as above queue classes + - setTimeout(secs) - allows you to change the timeout value + +And for the final queue class, there's the "MaxLengthQueue" class. As you may +have guessed, it's a queue that is capped at a certain specified length. It +features the following: + +* MaxLengthQueue(length, seq=()) - the constructor naturally requires that you + set the max length and it allows you to optionally pass in a sequence to be + used as the starting queue. The underlying implementation is actually the + queue from before. + + - enqueue(item) - adds an item onto the back of the queue and if it would + push it over the max length, it dequeues the item on the front (it does + not return this item to you) + - all the standard methods from the queue class are inherited for this class + +The Other Structures +-------------------- + +The most useful of the other structures is actually very similar to the +"MaxLengthQueue". It's the "RingBuffer", which is essentially a MaxLengthQueue +which fills up to its maximum size and then circularly replaces the old +contents as new entries are added instead of dequeuing. It features the +following: + +* RingBuffer(size, seq=()) - as with the MaxLengthQueue you specify the size + of the RingBuffer and optionally give it a sequence. + + - append(item) - adds item to the end of the buffer, pushing out an item + from the front if necessary + - reset() - empties out the buffer entirely + - resize(i) - shrinks/expands the RingBuffer to the size provided + - extend(seq) - append the items from the provided sequence onto the end of + the RingBuffer + +The next data structure is the TwoWayDictionary, which as the name implies is +a dictionary in which key-value pairs have mappings going both directions. It +features the following: + +* TwoWayDictionary(seq=(), \**kwargs) - Takes an optional sequence of (key, + value) pairs as well as any key=value pairs specified in the constructor as + initial values for the two-way dict. + + - other than that, no extra features that a normal Python dict doesn't + already offer with the exception that any (key, val) pair added to the + dict is also added as (val, key) as well, so the mapping goes both ways. + Elements are still accessed the same way you always do with Python + 'dict's. + +There is also a MultiSet class available, but it's very unlikely that it will +serve your purpose, so I won't go into it here. The curious coder can go check +the source and see what it's all about if they wish (it's only used once in our +code, in the Relay plugin). + +web.py +====== +The web portion of Supybot's utils module is mainly used for retrieving data +from websites but it also has some utility functions pertaining to HTML and +email text as well. The functions in web are listed below, once again in order +of usefulness. + +* getUrl(url, size=None, headers=None) - gets the data at the URL provided and + returns it as one large string + + - url: the location of the data to be retrieved or a urllib2.Request object + to be used in the retrieval + - size: the maximum number of bytes to retrieve, defaults to None, meaning + that it is to try to retrieve all data + - headers: a dictionary mapping header types to header data + +* getUrlFd(url, headers=None) - returns a file-like object for a url + + - url: the location of the data to be retrieved or a urllib2.Request object + to be used in the retrieval + - headers: a dictionary mapping header types to header data + +* htmlToText(s, tagReplace=" ") - strips out all tags in a string of HTML, + replacing them with the specified character + + - s: the HTML text to strip the tags out of + - tagReplace: the string to replace tags with + +* strError(e) - pretty-printer for web exceptions, returns a descriptive + string given a web-related exception + + - e: the exception to pretty-print + +* mungeEmail(s) - a naive e-mail obfuscation function, replaces "@" with "AT" + and "." with "DOT" + + - s: the e-mail address to obfuscate + +* getDomain(url) - returns the domain of a URL + - url: the URL in question + +The Best of the Rest +==================== +Intro +----- + +Rather than document each of the remaining portions of the supybot.utils +module, I've elected to just pick out the choice bits from specific parts and +document those instead. Here they are, broken out by module name. + +supybot.utils.file - file utilities +----------------------------------- + +* touch(filename) - updates the access time of a file by opening it for + writing and immediately closing it + +* mktemp(suffix="") - creates a decent random string, suitable for a temporary + filename with the given suffix, if provided + +* the AtomicFile class - used for files that need to be atomically written, + i.e., if there's a failure the original file remains unmodified. For more + info consult file.py in src/utils + +supybot.utils.gen - general utilities +------------------------------------- + +* timeElapsed(elapsed, [lots of optional args]) - given the number of seconds + elapsed, returns a string with the English description of the amount of time + passed, consult gen.py in src/utils for the exact argument list and + documentation if you feel you could use this function. + +* exnToString(e) - improved exception-to-string function. Provides nicer + output than a simple str(e). + +* InsensitivePreservingDict class - a dict class that is case-insensitive when + accessing keys + +supybot.utils.iter - iterable utilities +--------------------------------------- + +* len(iterable) - returns the length of a given iterable + +* groupby(key, iterable) - equivalent to the itertools.groupby function + available as of Python 2.4. Provided for backwards compatibility. + +* any(p, iterable) - Returns true if any element in the iterable satisfies the + predicate p + +* all(p, iterable) - Returns true if all elements in the iterable satisfy the + predicate p + +* choice(iterable) - Returns a random element from the iterable diff --git a/docs/USING_WRAP b/docs/USING_WRAP.rst similarity index 70% rename from docs/USING_WRAP rename to docs/USING_WRAP.rst index a5a33d775..61fa66fbd 100644 --- a/docs/USING_WRAP +++ b/docs/USING_WRAP.rst @@ -1,12 +1,10 @@ -Using the commands.wrap to parse your command's arguments. ----------------------------------------------------------- +Using 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 @@ -23,10 +21,8 @@ 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: +the following import line:: from supybot.commands import * @@ -38,7 +34,7 @@ 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: +it would be done without wrap:: def repeat(self, irc, msg, args): """ @@ -54,7 +50,7 @@ it would be done without wrap: 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: +our command will look like with wrap applied:: def repeat(self, irc, msg, args, num, text): """ @@ -76,11 +72,9 @@ 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: +new definition is:: def commandname(self, irc, msg, args, , , ...): @@ -88,13 +82,15 @@ 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: +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). +.. 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 @@ -108,7 +104,7 @@ 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: +wrap the converter name and args list into a tuple. For example:: commandname = wrap(commandname, [(converterWithArgs, arg1, arg2), converterWithoutArgs1, converterWithoutArgs2]) @@ -119,8 +115,6 @@ 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 @@ -142,7 +136,7 @@ Now, how do you use them? Well, they are in the global namespace of src/commands.py, so your previous import line will import them all; you can call them just as you call wrap. In fact, the way you use them is you simply call the context function you want to use, with the converter (and its -arguments) as arguments. It's quite simple. Here's an example: +arguments) as arguments. It's quite simple. Here's an example:: commandname = wrap(commandname, [optional('int'), many('something')]) @@ -159,220 +153,269 @@ 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 +* 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 +* 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 +* 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 + + - 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 +* 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 +* now + + - Simply returns the current timestamp as an arg, does not reference or modify the argument list. -"url" - Checks for a valid URL. +* url -"httpUrl" - Checks for a valid HTTP URL. + - Checks for a valid URL. -"long", type="long" - Basically the same as int minus the predicate, except that it converts the +* 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 +* 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" +* 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. +* positiveInt -"nonNegativeInt" - Accepts only non-negative integers. + - Accepts only positive integers. -"letter" - Looks for a single letter. (Technically, it looks for any one-element +* 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 +* 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 +* 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 +* 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. + 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). +* to -"nick" - Checks that the arg is a valid nick on the current IRC server. + - Returns the string "to" if the arg is any form of "to" (case-insensitive). -"seenNick" - Checks that the arg is a nick that the bot has seen (NOTE: this is limited +* 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 +* 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 +* 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 +* 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 +* 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 +* 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 +* 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 +* 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: +* 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 +* 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. +* anything -"something", errorMsg=None, p=None - Takes anything but the empty string. errorMsg can be used to customize the + - 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. +* filename -"commandName" - Returns the canonical command name version of the given string (ie, the + - 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 +* 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. +* glob -"somethingWithoutSpaces" - Same as something, only with the exception of disallowing spaces of course. + - 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. -"capability" - Used to retrieve an argument that describes a capability. +* somethingWithoutSpaces -"channelDb" - Sets the channel appropriately in order to get to the databases for that + - 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. +* hostmask -"banmask" - Returns a generic banmask of the provided nick or hostmask argument. + - Returns the hostmask of any provided nick or hostmask argument. -"user" - Requires that the caller be a registered user. +* banmask -"matches", regexp, errmsg - Searches the args with the given regexp and returns the matches. If no + - 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 +* 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 +* 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. +* otherUser -"regexpMatcher" - Gets a matching regexp argument (m// or //). + - Returns the user specified by the username or hostmask in the argument. -"validChannel" - Gets a channel argument once it makes sure it's a valid channel. +* regexpMatcher -"regexpReplacer" - Gets a replacing regexp argument (s//). + - Gets a matching regexp argument (m// or //). -"owner" - Requires that the command caller has the "owner" capability. +* validChannel -"admin" - Requires that the command caller has the "admin" capability. + - Gets a channel argument once it makes sure it's a valid channel. -"checkCapability", capability - Checks to make sure that the caller has the specified capability. +* 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 diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 000000000..e41bd87a1 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,195 @@ +# -*- coding: utf-8 -*- +# +# Supybot documentation build configuration file, created by +# sphinx-quickstart on Sat Feb 27 12:42:30 2010. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +sys.path.append(os.path.abspath('../src')) +#sys.path.append(os.path.abspath('.')) + +# -- General configuration ----------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = ['sphinx.ext.autodoc'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'Supybot' +copyright = u'2010, Jeremiah Fincher and James Vega' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '0.83.4.1' +# The full version, including alpha/beta/rc tags. +release = '0.83.4.1+git' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of documents that shouldn't be included in the build. +#unused_docs = [] + +# List of directories, relative to source directory, that shouldn't be searched +# for source files. +exclude_trees = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +modindex_common_prefix = ['supybot'] + + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. Major themes that come with +# Sphinx are currently 'default' and 'sphinxdoc'. +html_theme = 'default' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_use_modindex = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = '' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'Supybotdoc' + + +# -- Options for LaTeX output -------------------------------------------------- + +# The paper size ('letter' or 'a4'). +#latex_paper_size = 'letter' + +# The font size ('10pt', '11pt' or '12pt'). +#latex_font_size = '10pt' + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'Supybot.tex', u'Supybot Documentation', + u'Jeremiah Fincher and James Vega', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# Additional stuff for the LaTeX preamble. +#latex_preamble = '' + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_use_modindex = True diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 000000000..db2919259 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,24 @@ +.. Supybot documentation master file, created by + sphinx-quickstart on Sat Feb 27 12:42:30 2010. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to Supybot's documentation! +=================================== + +Contents: + +.. toctree:: + :maxdepth: 0 + :glob: + + * + plugins + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` +