mirror of
https://github.com/Mikaela/Limnoria.git
synced 2024-11-26 12: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-logs
|
||||
*.pyc
|
||||
docs/_build
|
||||
docs/plugins
|
||||
|
@ -1,3 +1,7 @@
|
||||
============
|
||||
Capabilities
|
||||
============
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
@ -1,3 +1,7 @@
|
||||
=============
|
||||
Configuration
|
||||
=============
|
||||
|
||||
Introduction
|
||||
------------
|
||||
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?
|
||||
|
||||
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
|
||||
============
|
||||
First things first - what you need to do before writing a Supybot plugin.
|
||||
|
||||
Ok, so you want to write a plugin for Supybot. Good, then this is the place to
|
||||
be. We're going to start from the top (the highest level, where Supybot code
|
||||
does the most work for you) and move lower after that.
|
||||
@ -28,11 +28,11 @@ with just a few simple commands.
|
||||
a minimal plugin which we will enhance in later sections.
|
||||
|
||||
The recommended way to start writing a plugin is to use the wizard provided,
|
||||
'supybot-plugin-create'. Run this from within your local plugins directory, so
|
||||
we will be able to load the plugin and test it out.
|
||||
:command:`supybot-plugin-create`. Run this from within your local plugins
|
||||
directory, so we will be able to load the plugin and test it out.
|
||||
|
||||
It's very easy to follow, because basically all you have to do is answer three
|
||||
questions. Here's an example session:
|
||||
questions. Here's an example session::
|
||||
|
||||
[ddipaolo@quinn ../python/supybot]% supybot-plugin-create
|
||||
What should the name of the plugin be? Random
|
||||
@ -55,9 +55,8 @@ each of those files and see what they're used for.
|
||||
|
||||
README.txt
|
||||
==========
|
||||
Tell me about the plugin.
|
||||
|
||||
In README.txt you put exactly what the boilerplate text says to put in there:
|
||||
In :file:`README.txt` you put exactly what the boilerplate text says to put in
|
||||
there:
|
||||
|
||||
Insert a description of your plugin here, with any notes, etc. about
|
||||
using it.
|
||||
@ -69,7 +68,7 @@ describe individual commands or anything like that, as those are defined within
|
||||
the plugin code itself as you'll see later. You also don't need to acknowledge
|
||||
any of the developers of the plugin as those too are handled elsewhere.
|
||||
|
||||
For our Random plugin, let's make README.txt say this:
|
||||
For our Random plugin, let's make :file:`README.txt` say this:
|
||||
|
||||
This plugin contains commands relating to random numbers, and
|
||||
includes: a simple random number generator, the ability to pick a
|
||||
@ -82,73 +81,72 @@ simple it is!
|
||||
|
||||
__init__.py
|
||||
===========
|
||||
Plugin properties and a few other bits.
|
||||
|
||||
The next file we'll look at is __init__.py. If you're familiar with the Python
|
||||
import mechanism, you'll know what this file is for. If you're not, think of it
|
||||
as sort of the "glue" file that pulls all the files in this directory together
|
||||
when you load the plugin. It's also where there are a few administrative items
|
||||
live that you really need to maintain.
|
||||
The next file we'll look at is :file:`__init__.py`. If you're familiar with
|
||||
the Python import mechanism, you'll know what this file is for. If you're not,
|
||||
think of it as sort of the "glue" file that pulls all the files in this
|
||||
directory together when you load the plugin. It's also where there are a few
|
||||
administrative items live that you really need to maintain.
|
||||
|
||||
Let's go through the file. For the first 30 lines or so, you'll see the
|
||||
copyright notice that we use for our plugins, only with your name in place (as
|
||||
prompted in 'supybot-plugin-create'). Feel free to use whatever license you
|
||||
choose, we don't feel particularly attached to the boilerplate code so it's
|
||||
yours to license as you see fit even if you don't modify it. For our example,
|
||||
we'll leave it as is.
|
||||
prompted in :command:`supybot-plugin-create`). Feel free to use whatever
|
||||
license you choose, we don't feel particularly attached to the boilerplate
|
||||
code so it's yours to license as you see fit even if you don't modify it. For
|
||||
our example, we'll leave it as is.
|
||||
|
||||
The plugin docstring immediately follows the copyright notice and it (like
|
||||
README.txt) tells you precisely what it should contain:
|
||||
:file:`README.txt`) tells you precisely what it should contain:
|
||||
|
||||
Add a description of the plugin (to be presented to the user inside
|
||||
the wizard) here. This should describe *what* the plugin does.
|
||||
|
||||
The "wizard" that it speaks of is the 'supybot-wizard' script that is used to
|
||||
create working Supybot config file. I imagine that in meeting the prerequisite
|
||||
of "using a Supybot" first, most readers will have already encountered this
|
||||
script. Basically, if the user selects to look at this plugin from the list of
|
||||
plugins to load, it prints out that description to let the user know what it
|
||||
does, so make sure to be clear on what the purpose of the plugin is. This
|
||||
should be an abbreviated version of what we put in our README.txt, so let's put
|
||||
this:
|
||||
The "wizard" that it speaks of is the :command:`supybot-wizard` script that is
|
||||
used to create working Supybot config file. I imagine that in meeting the
|
||||
prerequisite of "using a Supybot" first, most readers will have already
|
||||
encountered this script. Basically, if the user selects to look at this plugin
|
||||
from the list of plugins to load, it prints out that description to let the
|
||||
user know what it does, so make sure to be clear on what the purpose of the
|
||||
plugin is. This should be an abbreviated version of what we put in our
|
||||
:file:`README.txt`, so let's put this::
|
||||
|
||||
Provides a number of commands for selecting random things.
|
||||
|
||||
Next in __init__.py you see a few imports which are necessary, and then four
|
||||
attributes that you need to modify for your bot and preferably keep up with as
|
||||
you develop it: __version__, __author__, __contributors__, __url__.
|
||||
Next in :file:`__init__.py` you see a few imports which are necessary, and
|
||||
then four attributes that you need to modify for your bot and preferably keep
|
||||
up with as you develop it: ``__version__``, ``__author__``,
|
||||
``__contributors__``, ``__url__``.
|
||||
|
||||
__version__ is just a version string representing the current working version
|
||||
of the plugin, and can be anything you want. If you use some sort of RCS, this
|
||||
would be a good place to have it automatically increment the version string for
|
||||
any time you edit any of the files in this directory. We'll just make ours
|
||||
"0.1".
|
||||
``__version__`` is just a version string representing the current working
|
||||
version of the plugin, and can be anything you want. If you use some sort of
|
||||
RCS, this would be a good place to have it automatically increment the version
|
||||
string for any time you edit any of the files in this directory. We'll just
|
||||
make ours "0.1".
|
||||
|
||||
__author__ should be an instance of the supybot.Author class. A supybot.Author
|
||||
is simply created by giving it a full name, a short name (preferably IRC nick),
|
||||
and an e-mail address (all of these are optional, though at least the second
|
||||
one is expected). So, for example, to create my Author user (though I get to
|
||||
cheat and use supybot.authors.strike since I'm a main dev, muahaha), I would
|
||||
do:
|
||||
``__author__`` should be an instance of the :class:`supybot.Author` class. A
|
||||
:class:`supybot.Author` is simply created by giving it a full name, a short
|
||||
name (preferably IRC nick), and an e-mail address (all of these are optional,
|
||||
though at least the second one is expected). So, for example, to create my
|
||||
Author user (though I get to cheat and use supybot.authors.strike since I'm a
|
||||
main dev, muahaha), I would do::
|
||||
|
||||
__author__ = supybot.Author('Daniel DiPaolo', 'Strike',
|
||||
'somewhere@someplace.xxx')
|
||||
|
||||
Keep this in mind as we get to the next item...
|
||||
|
||||
__contributors__ is a dictionary mapping supybot.Author instances to lists of
|
||||
things they contributed. If someone adds a command named foo to your plugin,
|
||||
the list for that author should be ["foo"], or perhaps even ["added foo
|
||||
command"]. The main author shouldn't be referenced here, as it is assumed that
|
||||
everything that wasn't contributed by someone else was done by the main author.
|
||||
For now we have no contributors, so we'll leave it blank.
|
||||
``__contributors__`` is a dictionary mapping supybot.Author instances to lists
|
||||
of things they contributed. If someone adds a command named foo to your
|
||||
plugin, the list for that author should be ``["foo"]``, or perhaps even
|
||||
``["added foo command"]``. The main author shouldn't be referenced here, as it
|
||||
is assumed that everything that wasn't contributed by someone else was done by
|
||||
the main author. For now we have no contributors, so we'll leave it blank.
|
||||
|
||||
Lastly, the __url__ attribute should just reference the download URL for the
|
||||
plugin. Since this is just an example, we'll leave this blank.
|
||||
Lastly, the ``__url__`` attribute should just reference the download URL for
|
||||
the plugin. Since this is just an example, we'll leave this blank.
|
||||
|
||||
The rest of __init__.py really shouldn't be touched unless you are using
|
||||
third-party modules in your plugin. If you are, then you need to take special
|
||||
note of the section that looks like this:
|
||||
The rest of :file:`__init__.py` really shouldn't be touched unless you are
|
||||
using third-party modules in your plugin. If you are, then you need to take
|
||||
special note of the section that looks like this::
|
||||
|
||||
import config
|
||||
import plugin
|
||||
@ -158,22 +156,20 @@ note of the section that looks like this:
|
||||
# import them as well!
|
||||
|
||||
As the comment says, this is one place where you need to make sure you import
|
||||
the third-party modules, and that you call reload() on them as well. That way,
|
||||
if we are reloading a plugin on a running bot it will actually reload the
|
||||
latest code. We aren't using any third-party modules, so we can just leave this
|
||||
bit alone.
|
||||
the third-party modules, and that you call :func:`reload` on them as well.
|
||||
That way, if we are reloading a plugin on a running bot it will actually
|
||||
reload the latest code. We aren't using any third-party modules, so we can
|
||||
just leave this bit alone.
|
||||
|
||||
We're almost through the "boring" part and into the guts of writing Supybot
|
||||
plugins, let's take a look at the next file.
|
||||
|
||||
config.py
|
||||
=========
|
||||
Making our plugin configurable
|
||||
|
||||
config.py is, unsurprisingly, where all the configuration stuff related to
|
||||
your plugin goes. If you're not familiar with Supybot's configuration system,
|
||||
I recommend reading the config tutorial before going any further with this
|
||||
section.
|
||||
:file:`config.py` is, unsurprisingly, where all the configuration stuff
|
||||
related to your plugin goes. If you're not familiar with Supybot's
|
||||
configuration system, I recommend reading the config tutorial before going any
|
||||
further with this section.
|
||||
|
||||
So, let's plow through config.py line-by-line like we did the other files.
|
||||
|
||||
@ -208,7 +204,7 @@ so we'll leave this as is.
|
||||
Next, you'll see a line that looks very similar to the one in the configure
|
||||
function. This line is used not only to register the plugin prior to being
|
||||
called in configure, but also to store a bit of an alias to the plugin's config
|
||||
group to make things shorter later on. So, this line should read:
|
||||
group to make things shorter later on. So, this line should read::
|
||||
|
||||
Random = conf.registerPlugin('Random')
|
||||
|
||||
@ -224,8 +220,6 @@ Tutorial
|
||||
|
||||
plugin.py
|
||||
=========
|
||||
The meat of the plugin
|
||||
|
||||
Here's the moment you've been waiting for, the overview of plugin.py and how to
|
||||
make our plugin actually do stuff.
|
||||
|
||||
@ -244,7 +238,7 @@ subclass of callbacks.Plugin for you to start with. The only real content it
|
||||
has is the boilerplate docstring, which you should modify to reflect what the
|
||||
boilerplate text says - it should be useful so that when someone uses the
|
||||
plugin help command to determine how to use this plugin, they'll know what they
|
||||
need to do. Ours will read something like:
|
||||
need to do. Ours will read something like::
|
||||
|
||||
"""This plugin provides a few random number commands and some
|
||||
commands for getting random samples. Use the "seed" command to seed
|
||||
@ -259,7 +253,7 @@ plugin do something. First of all, to get any random numbers we're going to
|
||||
need a random number generator (RNG). Pretty much everything in our plugin is
|
||||
going to use it, so we'll define it in the constructor of our plugin, __init__.
|
||||
Here we'll also seed it with the current time (standard practice for RNGs).
|
||||
Here's what our __init__ looks like:
|
||||
Here's what our __init__ looks like::
|
||||
|
||||
def __init__(self, irc):
|
||||
self.__parent = super(Random, self)
|
||||
@ -276,7 +270,8 @@ to use the plugin name that you are working on instead.
|
||||
|
||||
So, now we have a RNG in our plugin, let's write a command to get a random
|
||||
number. We'll start with a simple command named random that just returns a
|
||||
random number from our RNG and takes no arguments. Here's what that looks like:
|
||||
random number from our RNG and takes no arguments. Here's what that looks
|
||||
like::
|
||||
|
||||
def random(self, irc, msg, args):
|
||||
"""takes no arguments
|
||||
@ -337,7 +332,7 @@ Now let's create a command with some arguments and see how we use those in our
|
||||
plugin commands. Let's allow the user to seed our RNG with their own seed
|
||||
value. We'll call the command seed and take just the seed value as the argument
|
||||
(which we'll require be a floating point value of some sort, though technically
|
||||
it can be any hashable object). Here's what this command looks like:
|
||||
it can be any hashable object). Here's what this command looks like::
|
||||
|
||||
def seed(self, irc, msg, args, seed):
|
||||
"""<seed>
|
||||
@ -377,7 +372,7 @@ then assigns values to each argument in the arg list after the first four
|
||||
With this alone you'd be able to make some pretty usable plugin commands, but
|
||||
we'll go through two more commands to introduce a few more useful ideas. The
|
||||
next command we'll make is a sample command which gets a random sample of items
|
||||
from a list provided by the user:
|
||||
from a list provided by the user::
|
||||
|
||||
def sample(self, irc, msg, args, n, items):
|
||||
"""<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
|
||||
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):
|
||||
"""[<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
|
||||
|
||||
And now that we're done adding plugin commands you should see the boilerplate
|
||||
stuff at the bottom, which just consists of:
|
||||
stuff at the bottom, which just consists of::
|
||||
|
||||
Class = Random
|
||||
|
||||
@ -448,8 +443,6 @@ with plugin.py!
|
||||
|
||||
test.py
|
||||
=======
|
||||
Plugin tests go here.
|
||||
|
||||
Now that we've gotten our plugin written, we want to make sure it works. Sure,
|
||||
an easy way to do a somewhat quick check is to start up a bot, load the plugin,
|
||||
and run a few commands on it. If all goes well there, everything's probably
|
||||
@ -474,7 +467,7 @@ testRandom, testSeed, testSample, and testDiceRoll. Any other methods you want
|
||||
to add are more free-form and should describe what you're testing (don't be
|
||||
afraid to use long names).
|
||||
|
||||
First we'll write the testRandom method:
|
||||
First we'll write the testRandom method::
|
||||
|
||||
def testRandom(self):
|
||||
# difficult to test, let's just make sure it works
|
||||
@ -488,7 +481,7 @@ can do.
|
||||
Next, testSeed. In this method we're just going to check that the command
|
||||
itself functions. In another test method later on we will check and make sure
|
||||
that the seed produces reproducible random numbers like we would hope it would,
|
||||
but for now we just test it like we did random in 'testRandom':
|
||||
but for now we just test it like we did random in 'testRandom'::
|
||||
|
||||
def testSeed(self):
|
||||
# just make sure it works
|
||||
@ -496,7 +489,7 @@ but for now we just test it like we did random in 'testRandom':
|
||||
|
||||
Now for testSample. Since this one takes more arguments it makes sense that we
|
||||
test more scenarios in this one. Also this time we have to make sure that we
|
||||
hit the error that we coded in there given the right conditions:
|
||||
hit the error that we coded in there given the right conditions::
|
||||
|
||||
def testSample(self):
|
||||
self.assertError('sample 20 foo')
|
||||
@ -510,7 +503,7 @@ right number of elements and that they are formatted correctly when we give 1,
|
||||
2, or 3 element lists.
|
||||
|
||||
And for the last of our basic "check to see that it works" functions,
|
||||
testDiceRoll:
|
||||
testDiceRoll::
|
||||
|
||||
def testDiceRoll(self):
|
||||
self.assertActionRegexp('diceroll', 'rolls a \d')
|
||||
@ -520,7 +513,7 @@ should roll a single-digit number. And that's about all we can test reliably
|
||||
here, so that's all we do.
|
||||
|
||||
Lastly, we wanted to check and make sure that seeding the RNG with seed
|
||||
actually took effect like it's supposed to. So, we write another test method:
|
||||
actually took effect like it's supposed to. So, we write another test method::
|
||||
|
||||
def testSeedActuallySeeds(self):
|
||||
# now to make sure things work repeatably
|
||||
@ -539,28 +532,26 @@ another random number and make sure it is distinct from the prior one.
|
||||
|
||||
Conclusion
|
||||
==========
|
||||
Now you're ready to write Supybot plugins!
|
||||
|
||||
You are now very well-prepared to write Supybot plugins. Now for a few words of
|
||||
wisdom with regards to Supybot plugin-writing.
|
||||
|
||||
* Read other people's plugins, especially the included plugins and ones by
|
||||
the core developers. We (the Supybot dev team) can't possibly document
|
||||
all the awesome things that Supybot plugins can do, but we try.
|
||||
Nevertheless there are some really cool things that can be done that
|
||||
aren't very well-documented.
|
||||
* Read other people's plugins, especially the included plugins and ones by
|
||||
the core developers. We (the Supybot dev team) can't possibly document
|
||||
all the awesome things that Supybot plugins can do, but we try.
|
||||
Nevertheless there are some really cool things that can be done that
|
||||
aren't very well-documented.
|
||||
|
||||
* Hack new functionality into existing plugins first if writing a new
|
||||
plugin is too daunting.
|
||||
* Hack new functionality into existing plugins first if writing a new
|
||||
plugin is too daunting.
|
||||
|
||||
* Come ask us questions in #supybot on Freenode or OFTC. Going back to the
|
||||
first point above, the developers themselves can help you even more than
|
||||
the docs can (though we prefer you read the docs first).
|
||||
* Come ask us questions in #supybot on Freenode or OFTC. Going back to the
|
||||
first point above, the developers themselves can help you even more than
|
||||
the docs can (though we prefer you read the docs first).
|
||||
|
||||
* Share your plugins with the world and make Supybot all that more
|
||||
attractive for other users so they will want to write their plugins for
|
||||
Supybot as well.
|
||||
* Share your plugins with the world and make Supybot all that more
|
||||
attractive for other users so they will want to write their plugins for
|
||||
Supybot as well.
|
||||
|
||||
* Read, read, read all the documentation.
|
||||
* Read, read, read all the documentation.
|
||||
|
||||
* And of course, have fun writing your plugins.
|
||||
* And of course, have fun writing your plugins.
|
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
|
||||
0.80 to handle argument parsing and validation for your plugin's commands.
|
||||
|
||||
Introduction
|
||||
============
|
||||
What is 'wrap'?
|
||||
|
||||
To plugin developers for older (pre-0.80) versions of Supybot, one of the more
|
||||
annoying aspects of writing commands was handling the arguments that were
|
||||
passed in. In fact, many commands often had to duplicate parsing and
|
||||
@ -23,10 +21,8 @@ will massively ease the task of writing commands.
|
||||
|
||||
Using Wrap
|
||||
==========
|
||||
How can I use 'wrap'?
|
||||
|
||||
First off, to get the wrap function, it is recommended (strongly) that you use
|
||||
the following import line:
|
||||
the following import line::
|
||||
|
||||
from supybot.commands import *
|
||||
|
||||
@ -38,7 +34,7 @@ Let's write a quickie command that uses wrap to get a feel for how it makes our
|
||||
lives better. Let's write a command that repeats a string of text a given
|
||||
number of times. So you could say "repeat 3 foo" and it would say "foofoofoo".
|
||||
Not a very useful command, but it will serve our purpose just fine. Here's how
|
||||
it would be done without wrap:
|
||||
it would be done without wrap::
|
||||
|
||||
def repeat(self, irc, msg, args):
|
||||
"""<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
|
||||
(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):
|
||||
"""<num> <text>
|
||||
@ -76,11 +72,9 @@ to do to use it.
|
||||
|
||||
Syntax Changes
|
||||
==============
|
||||
How will I need to change my plugin commands to take advantage of 'wrap'?
|
||||
|
||||
There are two syntax changes to the old style that are implemented. First, the
|
||||
definition of the command function must be changed. The basic syntax for the
|
||||
new definition is:
|
||||
new definition is::
|
||||
|
||||
def commandname(self, irc, msg, args, <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
|
||||
from?" you ask. Well, that's where the second syntax change comes in. The
|
||||
second syntax change is the actual use of the wrap function itself to decorate
|
||||
our command names. The basic decoration syntax is:
|
||||
our command names. The basic decoration syntax is::
|
||||
|
||||
commandname = wrap(commandname, [converter1, converter2, ...])
|
||||
|
||||
NOTE: This should go on the line immediately following the body of the
|
||||
command's definition, so it can easily be located (and it obviously must go
|
||||
after the command's definition so that commandname is defined).
|
||||
.. note::
|
||||
|
||||
This should go on the line immediately following the body of the command's
|
||||
definition, so it can easily be located (and it obviously must go after the
|
||||
command's definition so that commandname is defined).
|
||||
|
||||
Each of the converters in the above listing should be one of the converters in
|
||||
commands.py (I will describe each of them in detail later.) The converters are
|
||||
@ -108,7 +104,7 @@ builtin namespace by overriding the builtin int.
|
||||
As you will find out when you look through the list of converters below, some
|
||||
of the converters actually take arguments. The syntax for supplying them (since
|
||||
we aren't actually calling the converters, but simply specifying them), is to
|
||||
wrap the converter name and args list into a tuple. For example:
|
||||
wrap the converter name and args list into a tuple. For example::
|
||||
|
||||
commandname = wrap(commandname, [(converterWithArgs, arg1, arg2),
|
||||
converterWithoutArgs1, converterWithoutArgs2])
|
||||
@ -119,8 +115,6 @@ any.
|
||||
|
||||
Customizing Wrap
|
||||
================
|
||||
Is there a way to affect how I apply my wraps? We call them contexts.
|
||||
|
||||
Converters alone are a pretty powerful tool, but for even more advanced (yet
|
||||
simpler!) argument handling you may want to use contexts. Contexts describe how
|
||||
the converters are applied to the arguments, while the converters themselves
|
||||
@ -142,7 +136,7 @@ Now, how do you use them? Well, they are in the global namespace of
|
||||
src/commands.py, so your previous import line will import them all; you can
|
||||
call them just as you call wrap. In fact, the way you use them is you simply
|
||||
call the context function you want to use, with the converter (and its
|
||||
arguments) as arguments. It's quite simple. Here's an example:
|
||||
arguments) as arguments. It's quite simple. Here's an example::
|
||||
|
||||
commandname = wrap(commandname, [optional('int'), many('something')])
|
||||
|
||||
@ -159,220 +153,269 @@ thing that just "something" would return, but rather a list of "something"s.
|
||||
|
||||
Converter List
|
||||
==============
|
||||
What converters are available for me to use?
|
||||
|
||||
Below is a list of all the available converters to use with wrap. If the
|
||||
converter accepts any arguments, they are listed after it and if they are
|
||||
optional, the default value is shown.
|
||||
|
||||
"id", kind="integer"
|
||||
Returns something that looks like an integer ID number. Takes an optional
|
||||
* id, kind="integer"
|
||||
|
||||
- Returns something that looks like an integer ID number. Takes an optional
|
||||
"kind" argument for you to state what kind of ID you are looking for,
|
||||
though this doesn't affect the integrity-checking. Basically requires that
|
||||
the argument be an integer, does no other integrity-checking, and provides
|
||||
a nice error message with the kind in it.
|
||||
|
||||
"ip"
|
||||
Checks and makes sure the argument looks like a valid IP and then returns
|
||||
* ip
|
||||
|
||||
- Checks and makes sure the argument looks like a valid IP and then returns
|
||||
it.
|
||||
|
||||
"int", type="integer", p=None
|
||||
Gets an integer. The "type" text can be used to customize the error message
|
||||
* int, type="integer", p=None
|
||||
|
||||
- Gets an integer. The "type" text can be used to customize the error message
|
||||
received when the argument is not an integer. "p" is an optional predicate
|
||||
to test the integer with. If p(i) fails (where i is the integer arg parsed
|
||||
out of the argument string), the arg will not be accepted.
|
||||
|
||||
"index"
|
||||
Basically ("int", "index"), but with a twist. This will take a 1-based
|
||||
* index
|
||||
|
||||
- Basically ("int", "index"), but with a twist. This will take a 1-based
|
||||
index and turn it into a 0-based index (which is more useful in code). It
|
||||
doesn't transform 0, and it maintains negative indices as is (note that it
|
||||
does allow them!).
|
||||
|
||||
"color"
|
||||
Accepts arguments that describe a text color code (e.g., "black", "light
|
||||
* color
|
||||
|
||||
- Accepts arguments that describe a text color code (e.g., "black", "light
|
||||
blue") and returns the mIRC color code for that color. (Note that many
|
||||
other IRC clients support the mIRC color code scheme, not just mIRC)
|
||||
|
||||
"now"
|
||||
Simply returns the current timestamp as an arg, does not reference or
|
||||
* now
|
||||
|
||||
- Simply returns the current timestamp as an arg, does not reference or
|
||||
modify the argument list.
|
||||
|
||||
"url"
|
||||
Checks for a valid URL.
|
||||
* url
|
||||
|
||||
"httpUrl"
|
||||
Checks for a valid HTTP URL.
|
||||
- Checks for a valid URL.
|
||||
|
||||
"long", type="long"
|
||||
Basically the same as int minus the predicate, except that it converts the
|
||||
* httpUrl
|
||||
|
||||
- Checks for a valid HTTP URL.
|
||||
|
||||
* long, type="long"
|
||||
|
||||
- Basically the same as int minus the predicate, except that it converts the
|
||||
argument to a long integer regardless of the size of the int.
|
||||
|
||||
"float", type="floating point number"
|
||||
Basically the same as int minus the predicate, except that it converts the
|
||||
* float, type="floating point number"
|
||||
|
||||
- Basically the same as int minus the predicate, except that it converts the
|
||||
argument to a float.
|
||||
|
||||
"nonInt", type="non-integer value"
|
||||
Accepts everything but integers, and returns them unchanged. The "type"
|
||||
* nonInt, type="non-integer value"
|
||||
|
||||
- Accepts everything but integers, and returns them unchanged. The "type"
|
||||
value, as always, can be used to customize the error message that is
|
||||
displayed upon failure.
|
||||
|
||||
"positiveInt"
|
||||
Accepts only positive integers.
|
||||
* positiveInt
|
||||
|
||||
"nonNegativeInt"
|
||||
Accepts only non-negative integers.
|
||||
- Accepts only positive integers.
|
||||
|
||||
"letter"
|
||||
Looks for a single letter. (Technically, it looks for any one-element
|
||||
* nonNegativeInt
|
||||
|
||||
- Accepts only non-negative integers.
|
||||
|
||||
* letter
|
||||
|
||||
- Looks for a single letter. (Technically, it looks for any one-element
|
||||
sequence).
|
||||
|
||||
"haveOp", action="do that"
|
||||
Simply requires that the bot have ops in the channel that the command is
|
||||
* haveOp, action="do that"
|
||||
|
||||
- Simply requires that the bot have ops in the channel that the command is
|
||||
called in. The action parameter completes the error message: "I need to be
|
||||
opped to ...".
|
||||
|
||||
"expiry"
|
||||
Takes a number of seconds and adds it to the current time to create an
|
||||
* expiry
|
||||
|
||||
- Takes a number of seconds and adds it to the current time to create an
|
||||
expiration timestamp.
|
||||
|
||||
"literal", literals, errmsg=None
|
||||
Takes a required sequence or string (literals) and any argument that
|
||||
* literal, literals, errmsg=None
|
||||
|
||||
- Takes a required sequence or string (literals) and any argument that
|
||||
uniquely matches the starting substring of one of the literals is
|
||||
transformed into the full literal. For example, with ("literal", ("bar",
|
||||
"baz", "qux")), you'd get "bar" for "bar", "baz" for "baz", and "qux" for
|
||||
any of "q", "qu", or "qux". "b" and "ba" would raise errors because they
|
||||
don't uniquely identify one of the literals in the list. You can override
|
||||
errmsg to provide a specific (full) error message, otherwise the default
|
||||
argument error message is displayed.
|
||||
transformed into the full literal. For example, with ``("literal", ("bar",
|
||||
"baz", "qux"))``, you'd get "bar" for "bar", "baz" for "baz", and "qux"
|
||||
for any of "q", "qu", or "qux". "b" and "ba" would raise errors because
|
||||
they don't uniquely identify one of the literals in the list. You can
|
||||
override errmsg to provide a specific (full) error message, otherwise the
|
||||
default argument error message is displayed.
|
||||
|
||||
"to"
|
||||
Returns the string "to" if the arg is any form of "to" (case-insensitive).
|
||||
* to
|
||||
|
||||
"nick"
|
||||
Checks that the arg is a valid nick on the current IRC server.
|
||||
- Returns the string "to" if the arg is any form of "to" (case-insensitive).
|
||||
|
||||
"seenNick"
|
||||
Checks that the arg is a nick that the bot has seen (NOTE: this is limited
|
||||
* nick
|
||||
|
||||
- Checks that the arg is a valid nick on the current IRC server.
|
||||
|
||||
* seenNick
|
||||
|
||||
- Checks that the arg is a nick that the bot has seen (NOTE: this is limited
|
||||
by the size of the history buffer that the bot has).
|
||||
|
||||
"channel"
|
||||
Gets a channel to use the command in. If the channel isn't supplied, uses
|
||||
* channel
|
||||
|
||||
- Gets a channel to use the command in. If the channel isn't supplied, uses
|
||||
the channel the message was sent in. If using a different channel, does
|
||||
sanity-checking to make sure the channel exists on the current IRC network.
|
||||
|
||||
"inChannel"
|
||||
Requires that the command be called from within any channel that the bot
|
||||
* inChannel
|
||||
|
||||
- Requires that the command be called from within any channel that the bot
|
||||
is currently in or with one of those channels used as an argument to the
|
||||
command.
|
||||
|
||||
"onlyInChannel"
|
||||
Requires that the command be called from within any channel that the bot
|
||||
* onlyInChannel
|
||||
|
||||
- Requires that the command be called from within any channel that the bot
|
||||
is currently in.
|
||||
|
||||
"nickInChannel"
|
||||
Requires that the argument be a nick that is in the current channel, and
|
||||
* nickInChannel
|
||||
|
||||
- Requires that the argument be a nick that is in the current channel, and
|
||||
returns that nick.
|
||||
|
||||
"networkIrc", errorIfNoMatch=False
|
||||
Returns the IRC object of the specified IRC network. If one isn't
|
||||
* networkIrc, errorIfNoMatch=False
|
||||
|
||||
- Returns the IRC object of the specified IRC network. If one isn't
|
||||
specified, the IRC object of the IRC network the command was called on is
|
||||
returned.
|
||||
|
||||
"callerInGivenChannel"
|
||||
Takes the given argument as a channel and makes sure that the caller is in
|
||||
* callerInGivenChannel
|
||||
|
||||
- Takes the given argument as a channel and makes sure that the caller is in
|
||||
that channel.
|
||||
|
||||
"plugin", require=True
|
||||
Returns the plugin specified by the arg or None. If require is True, an
|
||||
* plugin, require=True
|
||||
|
||||
- Returns the plugin specified by the arg or None. If require is True, an
|
||||
error is raised if the plugin cannot be retrieved.
|
||||
|
||||
"boolean"
|
||||
Converts the text string to a boolean value. Acceptable true values are:
|
||||
* boolean
|
||||
|
||||
- Converts the text string to a boolean value. Acceptable true values are:
|
||||
"1", "true", "on", "enable", or "enabled" (case-insensitive). Acceptable
|
||||
false values are: "0", false", "off", "disable", or "disabled"
|
||||
(case-insensitive).
|
||||
|
||||
"lowered"
|
||||
Returns the argument lowered (NOTE: it is lowered according to IRC
|
||||
* lowered
|
||||
|
||||
- Returns the argument lowered (NOTE: it is lowered according to IRC
|
||||
conventions, which does strange mapping with some punctuation characters).
|
||||
|
||||
"anything"
|
||||
Returns anything as is.
|
||||
* anything
|
||||
|
||||
"something", errorMsg=None, p=None
|
||||
Takes anything but the empty string. errorMsg can be used to customize the
|
||||
- Returns anything as is.
|
||||
|
||||
* something, errorMsg=None, p=None
|
||||
|
||||
- Takes anything but the empty string. errorMsg can be used to customize the
|
||||
error message. p is any predicate function that can be used to test the
|
||||
validity of the input.
|
||||
|
||||
"filename"
|
||||
Used to get a filename argument.
|
||||
* filename
|
||||
|
||||
"commandName"
|
||||
Returns the canonical command name version of the given string (ie, the
|
||||
- Used to get a filename argument.
|
||||
|
||||
* commandName
|
||||
|
||||
- Returns the canonical command name version of the given string (ie, the
|
||||
string is lowercased and dashes and underscores are removed).
|
||||
|
||||
"text"
|
||||
Takes the rest of the arguments as one big string. Note that this differs
|
||||
* text
|
||||
|
||||
- Takes the rest of the arguments as one big string. Note that this differs
|
||||
from the "anything" context in that it clobbers the arg string when it's
|
||||
done. Using any converters after this is most likely incorrect.
|
||||
|
||||
"glob"
|
||||
Gets a glob string. Basically, if there are no wildcards ('"*"', "?") in
|
||||
the argument, returns *string*, making a glob string that matches anything
|
||||
containing the given argument.
|
||||
* glob
|
||||
|
||||
"somethingWithoutSpaces"
|
||||
Same as something, only with the exception of disallowing spaces of course.
|
||||
- Gets a glob string. Basically, if there are no wildcards (``*``, ``?``) in
|
||||
the argument, returns ``*string*``, making a glob string that matches
|
||||
anything containing the given argument.
|
||||
|
||||
"capability"
|
||||
Used to retrieve an argument that describes a capability.
|
||||
* somethingWithoutSpaces
|
||||
|
||||
"channelDb"
|
||||
Sets the channel appropriately in order to get to the databases for that
|
||||
- Same as something, only with the exception of disallowing spaces of course.
|
||||
|
||||
* capability
|
||||
|
||||
- Used to retrieve an argument that describes a capability.
|
||||
|
||||
* channelDb
|
||||
|
||||
- Sets the channel appropriately in order to get to the databases for that
|
||||
channel (handles whether or not a given channel uses channel-specific
|
||||
databases and whatnot).
|
||||
|
||||
"hostmask"
|
||||
Returns the hostmask of any provided nick or hostmask argument.
|
||||
* hostmask
|
||||
|
||||
"banmask"
|
||||
Returns a generic banmask of the provided nick or hostmask argument.
|
||||
- Returns the hostmask of any provided nick or hostmask argument.
|
||||
|
||||
"user"
|
||||
Requires that the caller be a registered user.
|
||||
* banmask
|
||||
|
||||
"matches", regexp, errmsg
|
||||
Searches the args with the given regexp and returns the matches. If no
|
||||
- Returns a generic banmask of the provided nick or hostmask argument.
|
||||
|
||||
* user
|
||||
|
||||
- Requires that the caller be a registered user.
|
||||
|
||||
* matches, regexp, errmsg
|
||||
|
||||
- Searches the args with the given regexp and returns the matches. If no
|
||||
match is found, errmsg is given.
|
||||
|
||||
"public"
|
||||
Requires that the command be sent in a channel instead of a private
|
||||
* public
|
||||
|
||||
- Requires that the command be sent in a channel instead of a private
|
||||
message.
|
||||
|
||||
"private"
|
||||
Requires that the command be sent in a private message instead of a
|
||||
* private
|
||||
|
||||
- Requires that the command be sent in a private message instead of a
|
||||
channel.
|
||||
|
||||
"otherUser"
|
||||
Returns the user specified by the username or hostmask in the argument.
|
||||
* otherUser
|
||||
|
||||
"regexpMatcher"
|
||||
Gets a matching regexp argument (m// or //).
|
||||
- Returns the user specified by the username or hostmask in the argument.
|
||||
|
||||
"validChannel"
|
||||
Gets a channel argument once it makes sure it's a valid channel.
|
||||
* regexpMatcher
|
||||
|
||||
"regexpReplacer"
|
||||
Gets a replacing regexp argument (s//).
|
||||
- Gets a matching regexp argument (m// or //).
|
||||
|
||||
"owner"
|
||||
Requires that the command caller has the "owner" capability.
|
||||
* validChannel
|
||||
|
||||
"admin"
|
||||
Requires that the command caller has the "admin" capability.
|
||||
- Gets a channel argument once it makes sure it's a valid channel.
|
||||
|
||||
"checkCapability", capability
|
||||
Checks to make sure that the caller has the specified capability.
|
||||
* regexpReplacer
|
||||
|
||||
- Gets a replacing regexp argument (s//).
|
||||
|
||||
* owner
|
||||
|
||||
- Requires that the command caller has the "owner" capability.
|
||||
|
||||
* admin
|
||||
|
||||
- Requires that the command caller has the "admin" capability.
|
||||
|
||||
* checkCapability, capability
|
||||
|
||||
- Checks to make sure that the caller has the specified capability.
|
||||
|
||||
"checkChannelCapability", capability
|
||||
Checks to make sure that the caller has the specified capability on the
|
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