mirror of
https://github.com/Mikaela/Limnoria.git
synced 2024-11-30 06:49:24 +01:00
Initial pass at Sphinx documentation.
Rename all existing documentation files to *.rst. Fix up some of the formatting to work better as reStructuredText. Add Sphinx's output directories to gitignore. Signed-off-by: James Vega <jamessan@users.sourceforge.net>
This commit is contained in:
parent
166f32dcb0
commit
a8d2e35fb1
2
.gitignore
vendored
2
.gitignore
vendored
@ -3,3 +3,5 @@ test-data
|
|||||||
test-conf
|
test-conf
|
||||||
test-logs
|
test-logs
|
||||||
*.pyc
|
*.pyc
|
||||||
|
docs/_build
|
||||||
|
docs/plugins
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
|
============
|
||||||
|
Capabilities
|
||||||
|
============
|
||||||
|
|
||||||
Introduction
|
Introduction
|
||||||
------------
|
------------
|
||||||
|
|
@ -1,3 +1,7 @@
|
|||||||
|
=============
|
||||||
|
Configuration
|
||||||
|
=============
|
||||||
|
|
||||||
Introduction
|
Introduction
|
||||||
------------
|
------------
|
||||||
So you've got your Supybot up and running and there are some things you
|
So you've got your Supybot up and running and there are some things you
|
@ -1,3 +1,7 @@
|
|||||||
|
==========================
|
||||||
|
Frequently Asked Questions
|
||||||
|
==========================
|
||||||
|
|
||||||
How do I make my Supybot connect to multiple servers?
|
How do I make my Supybot connect to multiple servers?
|
||||||
|
|
||||||
Just use the `connect` command in the `Network` plugin.
|
Just use the `connect` command in the `Network` plugin.
|
@ -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::
|
|
||||||
|
|
||||||
<jemfinch|lambda> $config default
|
|
||||||
supybot.replies.genericNoCapability
|
|
||||||
<lambdaman> 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)
|
|
||||||
<jemfinch|lambda> $more
|
|
||||||
<lambdaman> 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!
|
|
181
docs/GETTING_STARTED.rst
Normal file
181
docs/GETTING_STARTED.rst
Normal file
@ -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::
|
||||||
|
|
||||||
|
<jemfinch|lambda> $config default
|
||||||
|
supybot.replies.genericNoCapability
|
||||||
|
<lambdaman> 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)
|
||||||
|
<jemfinch|lambda> $more
|
||||||
|
<lambdaman> 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!
|
89
docs/Makefile
Normal file
89
docs/Makefile
Normal file
@ -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 <target>' where <target> 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."
|
@ -1,9 +1,9 @@
|
|||||||
WRITING YOUR FIRST SUPYBOT PLUGIN
|
=================================
|
||||||
|
Writing Your First Supybot Plugin
|
||||||
|
=================================
|
||||||
|
|
||||||
Introduction
|
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
|
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
|
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.
|
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.
|
a minimal plugin which we will enhance in later sections.
|
||||||
|
|
||||||
The recommended way to start writing a plugin is to use the wizard provided,
|
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
|
:command:`supybot-plugin-create`. Run this from within your local plugins
|
||||||
we will be able to load the plugin and test it out.
|
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
|
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
|
[ddipaolo@quinn ../python/supybot]% supybot-plugin-create
|
||||||
What should the name of the plugin be? Random
|
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
|
README.txt
|
||||||
==========
|
==========
|
||||||
Tell me about the plugin.
|
In :file:`README.txt` you put exactly what the boilerplate text says to put in
|
||||||
|
there:
|
||||||
In 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
|
Insert a description of your plugin here, with any notes, etc. about
|
||||||
using it.
|
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
|
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.
|
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
|
This plugin contains commands relating to random numbers, and
|
||||||
includes: a simple random number generator, the ability to pick a
|
includes: a simple random number generator, the ability to pick a
|
||||||
@ -82,73 +81,72 @@ simple it is!
|
|||||||
|
|
||||||
__init__.py
|
__init__.py
|
||||||
===========
|
===========
|
||||||
Plugin properties and a few other bits.
|
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,
|
||||||
The next file we'll look at is __init__.py. If you're familiar with the Python
|
think of it as sort of the "glue" file that pulls all the files in this
|
||||||
import mechanism, you'll know what this file is for. If you're not, think of it
|
directory together when you load the plugin. It's also where there are a few
|
||||||
as sort of the "glue" file that pulls all the files in this directory together
|
administrative items live that you really need to maintain.
|
||||||
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
|
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
|
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
|
prompted in :command:`supybot-plugin-create`). Feel free to use whatever
|
||||||
choose, we don't feel particularly attached to the boilerplate code so it's
|
license you choose, we don't feel particularly attached to the boilerplate
|
||||||
yours to license as you see fit even if you don't modify it. For our example,
|
code so it's yours to license as you see fit even if you don't modify it. For
|
||||||
we'll leave it as is.
|
our example, we'll leave it as is.
|
||||||
|
|
||||||
The plugin docstring immediately follows the copyright notice and it (like
|
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
|
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) here. This should describe *what* the plugin does.
|
||||||
|
|
||||||
The "wizard" that it speaks of is the 'supybot-wizard' script that is used to
|
The "wizard" that it speaks of is the :command:`supybot-wizard` script that is
|
||||||
create working Supybot config file. I imagine that in meeting the prerequisite
|
used to create working Supybot config file. I imagine that in meeting the
|
||||||
of "using a Supybot" first, most readers will have already encountered this
|
prerequisite of "using a Supybot" first, most readers will have already
|
||||||
script. Basically, if the user selects to look at this plugin from the list of
|
encountered this script. Basically, if the user selects to look at this plugin
|
||||||
plugins to load, it prints out that description to let the user know what it
|
from the list of plugins to load, it prints out that description to let the
|
||||||
does, so make sure to be clear on what the purpose of the plugin is. This
|
user know what it does, so make sure to be clear on what the purpose of the
|
||||||
should be an abbreviated version of what we put in our README.txt, so let's put
|
plugin is. This should be an abbreviated version of what we put in our
|
||||||
this:
|
:file:`README.txt`, so let's put this::
|
||||||
|
|
||||||
Provides a number of commands for selecting random things.
|
Provides a number of commands for selecting random things.
|
||||||
|
|
||||||
Next in __init__.py you see a few imports which are necessary, and then four
|
Next in :file:`__init__.py` you see a few imports which are necessary, and
|
||||||
attributes that you need to modify for your bot and preferably keep up with as
|
then four attributes that you need to modify for your bot and preferably keep
|
||||||
you develop it: __version__, __author__, __contributors__, __url__.
|
up with as you develop it: ``__version__``, ``__author__``,
|
||||||
|
``__contributors__``, ``__url__``.
|
||||||
|
|
||||||
__version__ is just a version string representing the current working version
|
``__version__`` is just a version string representing the current working
|
||||||
of the plugin, and can be anything you want. If you use some sort of RCS, this
|
version of the plugin, and can be anything you want. If you use some sort of
|
||||||
would be a good place to have it automatically increment the version string for
|
RCS, this would be a good place to have it automatically increment the version
|
||||||
any time you edit any of the files in this directory. We'll just make ours
|
string for any time you edit any of the files in this directory. We'll just
|
||||||
"0.1".
|
make ours "0.1".
|
||||||
|
|
||||||
__author__ should be an instance of the supybot.Author class. A supybot.Author
|
``__author__`` should be an instance of the :class:`supybot.Author` class. A
|
||||||
is simply created by giving it a full name, a short name (preferably IRC nick),
|
:class:`supybot.Author` is simply created by giving it a full name, a short
|
||||||
and an e-mail address (all of these are optional, though at least the second
|
name (preferably IRC nick), and an e-mail address (all of these are optional,
|
||||||
one is expected). So, for example, to create my Author user (though I get to
|
though at least the second one is expected). So, for example, to create my
|
||||||
cheat and use supybot.authors.strike since I'm a main dev, muahaha), I would
|
Author user (though I get to cheat and use supybot.authors.strike since I'm a
|
||||||
do:
|
main dev, muahaha), I would do::
|
||||||
|
|
||||||
__author__ = supybot.Author('Daniel DiPaolo', 'Strike',
|
__author__ = supybot.Author('Daniel DiPaolo', 'Strike',
|
||||||
'somewhere@someplace.xxx')
|
'somewhere@someplace.xxx')
|
||||||
|
|
||||||
Keep this in mind as we get to the next item...
|
Keep this in mind as we get to the next item...
|
||||||
|
|
||||||
__contributors__ is a dictionary mapping supybot.Author instances to lists of
|
``__contributors__`` is a dictionary mapping supybot.Author instances to lists
|
||||||
things they contributed. If someone adds a command named foo to your plugin,
|
of things they contributed. If someone adds a command named foo to your
|
||||||
the list for that author should be ["foo"], or perhaps even ["added foo
|
plugin, the list for that author should be ``["foo"]``, or perhaps even
|
||||||
command"]. The main author shouldn't be referenced here, as it is assumed that
|
``["added foo command"]``. The main author shouldn't be referenced here, as it
|
||||||
everything that wasn't contributed by someone else was done by the main author.
|
is assumed that everything that wasn't contributed by someone else was done by
|
||||||
For now we have no contributors, so we'll leave it blank.
|
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
|
Lastly, the ``__url__`` attribute should just reference the download URL for
|
||||||
plugin. Since this is just an example, we'll leave this blank.
|
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
|
The rest of :file:`__init__.py` really shouldn't be touched unless you are
|
||||||
third-party modules in your plugin. If you are, then you need to take special
|
using third-party modules in your plugin. If you are, then you need to take
|
||||||
note of the section that looks like this:
|
special note of the section that looks like this::
|
||||||
|
|
||||||
import config
|
import config
|
||||||
import plugin
|
import plugin
|
||||||
@ -158,22 +156,20 @@ note of the section that looks like this:
|
|||||||
# import them as well!
|
# import them as well!
|
||||||
|
|
||||||
As the comment says, this is one place where you need to make sure you import
|
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,
|
the third-party modules, and that you call :func:`reload` on them as well.
|
||||||
if we are reloading a plugin on a running bot it will actually reload the
|
That way, if we are reloading a plugin on a running bot it will actually
|
||||||
latest code. We aren't using any third-party modules, so we can just leave this
|
reload the latest code. We aren't using any third-party modules, so we can
|
||||||
bit alone.
|
just leave this bit alone.
|
||||||
|
|
||||||
We're almost through the "boring" part and into the guts of writing Supybot
|
We're almost through the "boring" part and into the guts of writing Supybot
|
||||||
plugins, let's take a look at the next file.
|
plugins, let's take a look at the next file.
|
||||||
|
|
||||||
config.py
|
config.py
|
||||||
=========
|
=========
|
||||||
Making our plugin configurable
|
:file:`config.py` is, unsurprisingly, where all the configuration stuff
|
||||||
|
related to your plugin goes. If you're not familiar with Supybot's
|
||||||
config.py is, unsurprisingly, where all the configuration stuff related to
|
configuration system, I recommend reading the config tutorial before going any
|
||||||
your plugin goes. If you're not familiar with Supybot's configuration system,
|
further with this section.
|
||||||
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.
|
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
|
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
|
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
|
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')
|
Random = conf.registerPlugin('Random')
|
||||||
|
|
||||||
@ -224,8 +220,6 @@ Tutorial
|
|||||||
|
|
||||||
plugin.py
|
plugin.py
|
||||||
=========
|
=========
|
||||||
The meat of the plugin
|
|
||||||
|
|
||||||
Here's the moment you've been waiting for, the overview of plugin.py and how to
|
Here's the moment you've been waiting for, the overview of plugin.py and how to
|
||||||
make our plugin actually do stuff.
|
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
|
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
|
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
|
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
|
"""This plugin provides a few random number commands and some
|
||||||
commands for getting random samples. Use the "seed" command to seed
|
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
|
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__.
|
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 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):
|
def __init__(self, irc):
|
||||||
self.__parent = super(Random, self)
|
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
|
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
|
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):
|
def random(self, irc, msg, args):
|
||||||
"""takes no arguments
|
"""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
|
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
|
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
|
(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):
|
def seed(self, irc, msg, args, seed):
|
||||||
"""<seed>
|
"""<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
|
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
|
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
|
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):
|
def sample(self, irc, msg, args, n, items):
|
||||||
"""<number of items> <item1> [<item2> ...]
|
"""<number of items> <item1> [<item2> ...]
|
||||||
@ -419,7 +414,7 @@ tutorial.
|
|||||||
|
|
||||||
Now for the last command that we will add to our plugin.py. This last command
|
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
|
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):
|
def diceroll(self, irc, msg, args, n):
|
||||||
"""[<number of sides>]
|
"""[<number of sides>]
|
||||||
@ -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
|
about wrap, you should refer to the wrap tutorial
|
||||||
|
|
||||||
And now that we're done adding plugin commands you should see the boilerplate
|
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
|
Class = Random
|
||||||
|
|
||||||
@ -448,8 +443,6 @@ with plugin.py!
|
|||||||
|
|
||||||
test.py
|
test.py
|
||||||
=======
|
=======
|
||||||
Plugin tests go here.
|
|
||||||
|
|
||||||
Now that we've gotten our plugin written, we want to make sure it works. Sure,
|
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,
|
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
|
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
|
to add are more free-form and should describe what you're testing (don't be
|
||||||
afraid to use long names).
|
afraid to use long names).
|
||||||
|
|
||||||
First we'll write the testRandom method:
|
First we'll write the testRandom method::
|
||||||
|
|
||||||
def testRandom(self):
|
def testRandom(self):
|
||||||
# difficult to test, let's just make sure it works
|
# 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
|
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
|
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,
|
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):
|
def testSeed(self):
|
||||||
# just make sure it works
|
# 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
|
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
|
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):
|
def testSample(self):
|
||||||
self.assertError('sample 20 foo')
|
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.
|
2, or 3 element lists.
|
||||||
|
|
||||||
And for the last of our basic "check to see that it works" functions,
|
And for the last of our basic "check to see that it works" functions,
|
||||||
testDiceRoll:
|
testDiceRoll::
|
||||||
|
|
||||||
def testDiceRoll(self):
|
def testDiceRoll(self):
|
||||||
self.assertActionRegexp('diceroll', 'rolls a \d')
|
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.
|
here, so that's all we do.
|
||||||
|
|
||||||
Lastly, we wanted to check and make sure that seeding the RNG with seed
|
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):
|
def testSeedActuallySeeds(self):
|
||||||
# now to make sure things work repeatably
|
# now to make sure things work repeatably
|
||||||
@ -539,8 +532,6 @@ another random number and make sure it is distinct from the prior one.
|
|||||||
|
|
||||||
Conclusion
|
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
|
You are now very well-prepared to write Supybot plugins. Now for a few words of
|
||||||
wisdom with regards to Supybot plugin-writing.
|
wisdom with regards to Supybot plugin-writing.
|
||||||
|
|
188
docs/STYLE
188
docs/STYLE
@ -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.
|
|
213
docs/STYLE.rst
Normal file
213
docs/STYLE.rst
Normal file
@ -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.
|
343
docs/USING_UTILS
343
docs/USING_UTILS
@ -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 <http://supybot.com>.'
|
|
||||||
|
|
||||||
>>> 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
|
|
||||||
|
|
||||||
|
|
379
docs/USING_UTILS.rst
Normal file
379
docs/USING_UTILS.rst
Normal file
@ -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 <http://supybot.com>.'
|
||||||
|
|
||||||
|
>>> 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
|
@ -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
|
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.
|
0.80 to handle argument parsing and validation for your plugin's commands.
|
||||||
|
|
||||||
Introduction
|
Introduction
|
||||||
============
|
============
|
||||||
What is 'wrap'?
|
|
||||||
|
|
||||||
To plugin developers for older (pre-0.80) versions of Supybot, one of the more
|
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
|
annoying aspects of writing commands was handling the arguments that were
|
||||||
passed in. In fact, many commands often had to duplicate parsing and
|
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
|
Using Wrap
|
||||||
==========
|
==========
|
||||||
How can I use 'wrap'?
|
|
||||||
|
|
||||||
First off, to get the wrap function, it is recommended (strongly) that you use
|
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 *
|
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
|
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".
|
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
|
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):
|
def repeat(self, irc, msg, args):
|
||||||
"""<num> <text>
|
"""<num> <text>
|
||||||
@ -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
|
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
|
(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):
|
def repeat(self, irc, msg, args, num, text):
|
||||||
"""<num> <text>
|
"""<num> <text>
|
||||||
@ -76,11 +72,9 @@ to do to use it.
|
|||||||
|
|
||||||
Syntax Changes
|
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
|
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
|
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, <arg1>, <arg2>, ...):
|
def commandname(self, irc, msg, args, <arg1>, <arg2>, ...):
|
||||||
|
|
||||||
@ -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
|
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
|
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
|
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, ...])
|
commandname = wrap(commandname, [converter1, converter2, ...])
|
||||||
|
|
||||||
NOTE: This should go on the line immediately following the body of the
|
.. note::
|
||||||
command's definition, so it can easily be located (and it obviously must go
|
|
||||||
after the command's definition so that commandname is defined).
|
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
|
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
|
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
|
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
|
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
|
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),
|
commandname = wrap(commandname, [(converterWithArgs, arg1, arg2),
|
||||||
converterWithoutArgs1, converterWithoutArgs2])
|
converterWithoutArgs1, converterWithoutArgs2])
|
||||||
@ -119,8 +115,6 @@ any.
|
|||||||
|
|
||||||
Customizing Wrap
|
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
|
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
|
simpler!) argument handling you may want to use contexts. Contexts describe how
|
||||||
the converters are applied to the arguments, while the converters themselves
|
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
|
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 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
|
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')])
|
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
|
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
|
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
|
converter accepts any arguments, they are listed after it and if they are
|
||||||
optional, the default value is shown.
|
optional, the default value is shown.
|
||||||
|
|
||||||
"id", kind="integer"
|
* id, kind="integer"
|
||||||
Returns something that looks like an integer ID number. Takes an optional
|
|
||||||
|
- 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,
|
"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
|
though this doesn't affect the integrity-checking. Basically requires that
|
||||||
the argument be an integer, does no other integrity-checking, and provides
|
the argument be an integer, does no other integrity-checking, and provides
|
||||||
a nice error message with the kind in it.
|
a nice error message with the kind in it.
|
||||||
|
|
||||||
"ip"
|
* ip
|
||||||
Checks and makes sure the argument looks like a valid IP and then returns
|
|
||||||
|
- Checks and makes sure the argument looks like a valid IP and then returns
|
||||||
it.
|
it.
|
||||||
|
|
||||||
"int", type="integer", p=None
|
* int, type="integer", p=None
|
||||||
Gets an integer. The "type" text can be used to customize the error message
|
|
||||||
|
- 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
|
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
|
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.
|
out of the argument string), the arg will not be accepted.
|
||||||
|
|
||||||
"index"
|
* index
|
||||||
Basically ("int", "index"), but with a twist. This will take a 1-based
|
|
||||||
|
- 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
|
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
|
doesn't transform 0, and it maintains negative indices as is (note that it
|
||||||
does allow them!).
|
does allow them!).
|
||||||
|
|
||||||
"color"
|
* color
|
||||||
Accepts arguments that describe a text color code (e.g., "black", "light
|
|
||||||
|
- 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
|
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)
|
other IRC clients support the mIRC color code scheme, not just mIRC)
|
||||||
|
|
||||||
"now"
|
* now
|
||||||
Simply returns the current timestamp as an arg, does not reference or
|
|
||||||
|
- Simply returns the current timestamp as an arg, does not reference or
|
||||||
modify the argument list.
|
modify the argument list.
|
||||||
|
|
||||||
"url"
|
* url
|
||||||
Checks for a valid URL.
|
|
||||||
|
|
||||||
"httpUrl"
|
- Checks for a valid URL.
|
||||||
Checks for a valid HTTP URL.
|
|
||||||
|
|
||||||
"long", type="long"
|
* httpUrl
|
||||||
Basically the same as int minus the predicate, except that it converts the
|
|
||||||
|
- 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.
|
argument to a long integer regardless of the size of the int.
|
||||||
|
|
||||||
"float", type="floating point number"
|
* float, type="floating point number"
|
||||||
Basically the same as int minus the predicate, except that it converts the
|
|
||||||
|
- Basically the same as int minus the predicate, except that it converts the
|
||||||
argument to a float.
|
argument to a float.
|
||||||
|
|
||||||
"nonInt", type="non-integer value"
|
* nonInt, type="non-integer value"
|
||||||
Accepts everything but integers, and returns them unchanged. The "type"
|
|
||||||
|
- Accepts everything but integers, and returns them unchanged. The "type"
|
||||||
value, as always, can be used to customize the error message that is
|
value, as always, can be used to customize the error message that is
|
||||||
displayed upon failure.
|
displayed upon failure.
|
||||||
|
|
||||||
"positiveInt"
|
* positiveInt
|
||||||
Accepts only positive integers.
|
|
||||||
|
|
||||||
"nonNegativeInt"
|
- Accepts only positive integers.
|
||||||
Accepts only non-negative integers.
|
|
||||||
|
|
||||||
"letter"
|
* nonNegativeInt
|
||||||
Looks for a single letter. (Technically, it looks for any one-element
|
|
||||||
|
- Accepts only non-negative integers.
|
||||||
|
|
||||||
|
* letter
|
||||||
|
|
||||||
|
- Looks for a single letter. (Technically, it looks for any one-element
|
||||||
sequence).
|
sequence).
|
||||||
|
|
||||||
"haveOp", action="do that"
|
* haveOp, action="do that"
|
||||||
Simply requires that the bot have ops in the channel that the command is
|
|
||||||
|
- 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
|
called in. The action parameter completes the error message: "I need to be
|
||||||
opped to ...".
|
opped to ...".
|
||||||
|
|
||||||
"expiry"
|
* expiry
|
||||||
Takes a number of seconds and adds it to the current time to create an
|
|
||||||
|
- Takes a number of seconds and adds it to the current time to create an
|
||||||
expiration timestamp.
|
expiration timestamp.
|
||||||
|
|
||||||
"literal", literals, errmsg=None
|
* literal, literals, errmsg=None
|
||||||
Takes a required sequence or string (literals) and any argument that
|
|
||||||
|
- Takes a required sequence or string (literals) and any argument that
|
||||||
uniquely matches the starting substring of one of the literals is
|
uniquely matches the starting substring of one of the literals is
|
||||||
transformed into the full literal. For example, with ("literal", ("bar",
|
transformed into the full literal. For example, with ``("literal", ("bar",
|
||||||
"baz", "qux")), you'd get "bar" for "bar", "baz" for "baz", and "qux" for
|
"baz", "qux"))``, you'd get "bar" for "bar", "baz" for "baz", and "qux"
|
||||||
any of "q", "qu", or "qux". "b" and "ba" would raise errors because they
|
for any of "q", "qu", or "qux". "b" and "ba" would raise errors because
|
||||||
don't uniquely identify one of the literals in the list. You can override
|
they don't uniquely identify one of the literals in the list. You can
|
||||||
errmsg to provide a specific (full) error message, otherwise the default
|
override errmsg to provide a specific (full) error message, otherwise the
|
||||||
argument error message is displayed.
|
default argument error message is displayed.
|
||||||
|
|
||||||
"to"
|
* to
|
||||||
Returns the string "to" if the arg is any form of "to" (case-insensitive).
|
|
||||||
|
|
||||||
"nick"
|
- Returns the string "to" if the arg is any form of "to" (case-insensitive).
|
||||||
Checks that the arg is a valid nick on the current IRC server.
|
|
||||||
|
|
||||||
"seenNick"
|
* nick
|
||||||
Checks that the arg is a nick that the bot has seen (NOTE: this is limited
|
|
||||||
|
- 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).
|
by the size of the history buffer that the bot has).
|
||||||
|
|
||||||
"channel"
|
* channel
|
||||||
Gets a channel to use the command in. If the channel isn't supplied, uses
|
|
||||||
|
- 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
|
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.
|
sanity-checking to make sure the channel exists on the current IRC network.
|
||||||
|
|
||||||
"inChannel"
|
* inChannel
|
||||||
Requires that the command be called from within any channel that the bot
|
|
||||||
|
- 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
|
is currently in or with one of those channels used as an argument to the
|
||||||
command.
|
command.
|
||||||
|
|
||||||
"onlyInChannel"
|
* onlyInChannel
|
||||||
Requires that the command be called from within any channel that the bot
|
|
||||||
|
- Requires that the command be called from within any channel that the bot
|
||||||
is currently in.
|
is currently in.
|
||||||
|
|
||||||
"nickInChannel"
|
* nickInChannel
|
||||||
Requires that the argument be a nick that is in the current channel, and
|
|
||||||
|
- Requires that the argument be a nick that is in the current channel, and
|
||||||
returns that nick.
|
returns that nick.
|
||||||
|
|
||||||
"networkIrc", errorIfNoMatch=False
|
* networkIrc, errorIfNoMatch=False
|
||||||
Returns the IRC object of the specified IRC network. If one isn't
|
|
||||||
|
- 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
|
specified, the IRC object of the IRC network the command was called on is
|
||||||
returned.
|
returned.
|
||||||
|
|
||||||
"callerInGivenChannel"
|
* callerInGivenChannel
|
||||||
Takes the given argument as a channel and makes sure that the caller is in
|
|
||||||
|
- Takes the given argument as a channel and makes sure that the caller is in
|
||||||
that channel.
|
that channel.
|
||||||
|
|
||||||
"plugin", require=True
|
* plugin, require=True
|
||||||
Returns the plugin specified by the arg or None. If require is True, an
|
|
||||||
|
- Returns the plugin specified by the arg or None. If require is True, an
|
||||||
error is raised if the plugin cannot be retrieved.
|
error is raised if the plugin cannot be retrieved.
|
||||||
|
|
||||||
"boolean"
|
* boolean
|
||||||
Converts the text string to a boolean value. Acceptable true values are:
|
|
||||||
|
- Converts the text string to a boolean value. Acceptable true values are:
|
||||||
"1", "true", "on", "enable", or "enabled" (case-insensitive). Acceptable
|
"1", "true", "on", "enable", or "enabled" (case-insensitive). Acceptable
|
||||||
false values are: "0", false", "off", "disable", or "disabled"
|
false values are: "0", false", "off", "disable", or "disabled"
|
||||||
(case-insensitive).
|
(case-insensitive).
|
||||||
|
|
||||||
"lowered"
|
* lowered
|
||||||
Returns the argument lowered (NOTE: it is lowered according to IRC
|
|
||||||
|
- Returns the argument lowered (NOTE: it is lowered according to IRC
|
||||||
conventions, which does strange mapping with some punctuation characters).
|
conventions, which does strange mapping with some punctuation characters).
|
||||||
|
|
||||||
"anything"
|
* anything
|
||||||
Returns anything as is.
|
|
||||||
|
|
||||||
"something", errorMsg=None, p=None
|
- Returns anything as is.
|
||||||
Takes anything but the empty string. errorMsg can be used to customize the
|
|
||||||
|
* 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
|
error message. p is any predicate function that can be used to test the
|
||||||
validity of the input.
|
validity of the input.
|
||||||
|
|
||||||
"filename"
|
* filename
|
||||||
Used to get a filename argument.
|
|
||||||
|
|
||||||
"commandName"
|
- Used to get a filename argument.
|
||||||
Returns the canonical command name version of the given string (ie, the
|
|
||||||
|
* commandName
|
||||||
|
|
||||||
|
- Returns the canonical command name version of the given string (ie, the
|
||||||
string is lowercased and dashes and underscores are removed).
|
string is lowercased and dashes and underscores are removed).
|
||||||
|
|
||||||
"text"
|
* text
|
||||||
Takes the rest of the arguments as one big string. Note that this differs
|
|
||||||
|
- 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
|
from the "anything" context in that it clobbers the arg string when it's
|
||||||
done. Using any converters after this is most likely incorrect.
|
done. Using any converters after this is most likely incorrect.
|
||||||
|
|
||||||
"glob"
|
* glob
|
||||||
Gets a glob string. Basically, if there are no wildcards ('"*"', "?") in
|
|
||||||
the argument, returns *string*, making a glob string that matches anything
|
|
||||||
containing the given argument.
|
|
||||||
|
|
||||||
"somethingWithoutSpaces"
|
- Gets a glob string. Basically, if there are no wildcards (``*``, ``?``) in
|
||||||
Same as something, only with the exception of disallowing spaces of course.
|
the argument, returns ``*string*``, making a glob string that matches
|
||||||
|
anything containing the given argument.
|
||||||
|
|
||||||
"capability"
|
* somethingWithoutSpaces
|
||||||
Used to retrieve an argument that describes a capability.
|
|
||||||
|
|
||||||
"channelDb"
|
- Same as something, only with the exception of disallowing spaces of course.
|
||||||
Sets the channel appropriately in order to get to the databases for that
|
|
||||||
|
* 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
|
channel (handles whether or not a given channel uses channel-specific
|
||||||
databases and whatnot).
|
databases and whatnot).
|
||||||
|
|
||||||
"hostmask"
|
* hostmask
|
||||||
Returns the hostmask of any provided nick or hostmask argument.
|
|
||||||
|
|
||||||
"banmask"
|
- Returns the hostmask of any provided nick or hostmask argument.
|
||||||
Returns a generic banmask of the provided nick or hostmask argument.
|
|
||||||
|
|
||||||
"user"
|
* banmask
|
||||||
Requires that the caller be a registered user.
|
|
||||||
|
|
||||||
"matches", regexp, errmsg
|
- Returns a generic banmask of the provided nick or hostmask argument.
|
||||||
Searches the args with the given regexp and returns the matches. If no
|
|
||||||
|
* 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.
|
match is found, errmsg is given.
|
||||||
|
|
||||||
"public"
|
* public
|
||||||
Requires that the command be sent in a channel instead of a private
|
|
||||||
|
- Requires that the command be sent in a channel instead of a private
|
||||||
message.
|
message.
|
||||||
|
|
||||||
"private"
|
* private
|
||||||
Requires that the command be sent in a private message instead of a
|
|
||||||
|
- Requires that the command be sent in a private message instead of a
|
||||||
channel.
|
channel.
|
||||||
|
|
||||||
"otherUser"
|
* otherUser
|
||||||
Returns the user specified by the username or hostmask in the argument.
|
|
||||||
|
|
||||||
"regexpMatcher"
|
- Returns the user specified by the username or hostmask in the argument.
|
||||||
Gets a matching regexp argument (m// or //).
|
|
||||||
|
|
||||||
"validChannel"
|
* regexpMatcher
|
||||||
Gets a channel argument once it makes sure it's a valid channel.
|
|
||||||
|
|
||||||
"regexpReplacer"
|
- Gets a matching regexp argument (m// or //).
|
||||||
Gets a replacing regexp argument (s//).
|
|
||||||
|
|
||||||
"owner"
|
* validChannel
|
||||||
Requires that the command caller has the "owner" capability.
|
|
||||||
|
|
||||||
"admin"
|
- Gets a channel argument once it makes sure it's a valid channel.
|
||||||
Requires that the command caller has the "admin" capability.
|
|
||||||
|
|
||||||
"checkCapability", capability
|
* regexpReplacer
|
||||||
Checks to make sure that the caller has the specified capability.
|
|
||||||
|
- 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
|
"checkChannelCapability", capability
|
||||||
Checks to make sure that the caller has the specified capability on the
|
Checks to make sure that the caller has the specified capability on the
|
195
docs/conf.py
Normal file
195
docs/conf.py
Normal file
@ -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
|
||||||
|
# "<project> v<release> 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 <link> 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
|
24
docs/index.rst
Normal file
24
docs/index.rst
Normal file
@ -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`
|
||||||
|
|
Loading…
Reference in New Issue
Block a user