mirror of
https://github.com/Mikaela/Limnoria.git
synced 2024-12-24 11:42:52 +01:00
Add supybot module
This commit is contained in:
parent
da614f5561
commit
dbfec8afb9
@ -1,3 +0,0 @@
|
||||
*.pyc
|
||||
*.pyo
|
||||
*~
|
7
ACKS
7
ACKS
@ -1,7 +0,0 @@
|
||||
johhnyace, who gave me the modem that helped me tremendously in development.
|
||||
bwp, who rewrote the Http.weather command, and also hosted the example
|
||||
"supybot" in #supybot on OFTC and Freenode for quite some time.
|
||||
sweede, for hosting the "main" supybot for awhile.
|
||||
HostPC.com, for hosting the current example "supybot" and for graciously
|
||||
providing DNS services and email.
|
||||
|
10
BUGS
10
BUGS
@ -1,10 +0,0 @@
|
||||
We're sure there are tons of them. When you find them, send them to us and
|
||||
we'll fix them ASAP. We'd love to have a bugless bot someday...
|
||||
|
||||
Incidentally, the way to "send the bugs to us" is via SourceForge:
|
||||
<http://sourceforge.net/tracker/?atid=489447&group_id=58965&func=browse>
|
||||
|
||||
Known bugs that probably won't get fixed:
|
||||
|
||||
BadWords' outFilter filters colors. It's not a high priority to get
|
||||
that fixed.
|
67
DEVS
67
DEVS
@ -1,67 +0,0 @@
|
||||
These are the developers of Supybot, in approximate order of ____.
|
||||
|
||||
Jeremy Fincher (jemfinch) is a Computer Science (and possibly
|
||||
philosophy) student at The Ohio State University. He spends most of
|
||||
his free time with his girlfriend Meg, but also...well, there's not
|
||||
much also :) He hopes to graduate with good enough grades to go to
|
||||
law school or seminary at some point in the future. He initially
|
||||
wrote the majority of the Supybot framework and standard plugins,
|
||||
though he's been trying to slowly phase himself out of plugin-writing
|
||||
and more into framework-enhancement. Rather than list the specific
|
||||
things he's done, you can just assume that if someone else isn't
|
||||
claiming it, it was probably done by him.
|
||||
|
||||
Daniel DiPaolo (Strike/ddipaolo) is a lazy Texan punk with a job as an IT
|
||||
monkey who spends his free time coding, playing ultimate frisbee, and arguing
|
||||
pointless things on the internet. As far as the bot goes, he's mainly a
|
||||
plugin developer but he has helped here and there with various under-the-hood
|
||||
things and is one of the few people (other than jemfinch) who understands the
|
||||
inner workings of Supybot. His biggest plugin contribution (in terms of sheer
|
||||
lines of code) has been the MoobotFactoids plugin and all the workd involved
|
||||
in getting that plugin to work, but he has also helped with a lot of testing,
|
||||
debugging, and brainstorming. He also wrote the Dunno, News, and Todo plugins
|
||||
and is responsible for a significant amount of code in the Poll, Debian,
|
||||
QuoteGrabs, Karma, and ChannelDB plugins.
|
||||
|
||||
James Vega (jamessan) is an Electrical Engineering/Computer Science student at
|
||||
Northeastern University. He wrote the Sourceforge and Ebay plugins as well as
|
||||
the first incarnation of the Babelfish commands and most of Amazon. He has
|
||||
also performed a significant amount of maintenance and refactoring of plugins
|
||||
in general. Some of the plugins that were affected the most are Debian,
|
||||
FunDB, Gameknot, Http, Note, and Quote. All of the link snarfers, save
|
||||
Bugzilla's, were also written by jamessan. His meddlings have prompted the
|
||||
implementation of Toggleables, which eventually evolved to Configurables and
|
||||
then to the current registry system. As well as being the current webmaster,
|
||||
he also overhauled the tool which is used to generate the site's HTML
|
||||
documentation for Supybot and setup the weekly creation of CVS snapshots.
|
||||
|
||||
Brett Kelly (inkedmn) is a hobbyist (soon to be professional :)) coder
|
||||
from southern California who enjoys collecting tattoos (on his body) and
|
||||
drinking coffee with his wife. He initially wrote the Note plugin as well
|
||||
as several commands in the Http plugin.
|
||||
|
||||
Vincent Foley-Bourgon is a recently-graduated student from Quebec who
|
||||
enjoys anything pointless, unprofitable, and generally useless. Recently
|
||||
returning to Supybot development (after writing the original freshmeat
|
||||
command for the Http plugin) he wrote the entire Hangman infrastructure
|
||||
for the Words plugin.
|
||||
|
||||
Daniel Berlin is a soon to be lawyer with a background in computer science
|
||||
and compilers. He enjoys selling crack to young homeless orphans, and works
|
||||
on Supybot when he's not lawyering or hacking on gcc.
|
||||
|
||||
Keith Jones (kmj) dislikes talking about himself in the third person. He
|
||||
has an MS in Computer Science, and has decided to see how long he can go
|
||||
without using that in any kind of professional capacity. To that end he
|
||||
is currently taking some math classes and applying to math Ph.D programs
|
||||
so some day he can be a professor at a college near a snowy mountain where
|
||||
he will ski every morning. So far, he hasn't done much for the project
|
||||
except squeeze Doug Bell's GPL'd unit conversion code into a supybot plugin.
|
||||
|
||||
Stéphan Kochen (G-LiTe) is a lazy (soon to be) computer science student.
|
||||
He's usually just freelancing and submitting patches here and there when he
|
||||
bumps into a bug that bothers him, but Supybot is one of the first projects
|
||||
he semi-actively tries to work on. ;) His biggest contribution has been the
|
||||
refactoring of the supybot-wizard script to use the registry, though he also
|
||||
likes to track down those nasty obscure bugs which haunt many of our fine
|
||||
applications these days.
|
@ -1,8 +0,0 @@
|
||||
No, really. Darcs is way cooler and the Darcs repository has all the new
|
||||
code (like our new supybot-newplugin and supybot-test).
|
||||
|
||||
"darcs get http://source.supybot.com/supybot/" for the new Supybot code.
|
||||
"darcs get http://source.supybot.com/supybot-plugins/" for plugins that aren't
|
||||
included in the official Supybot.
|
||||
|
||||
You can get Darcs at http://darcs.net/
|
110
INSTALL
110
INSTALL
@ -1,110 +0,0 @@
|
||||
So what do you do? That depends on which operating system you're
|
||||
running. We've split this document up to address the different
|
||||
methods, so find the section for your operating system and continue
|
||||
from there. First let's start with the parts that are common to all
|
||||
OSes.
|
||||
|
||||
|
||||
###
|
||||
# COMMON:
|
||||
###
|
||||
|
||||
First things first: Supybot *requires* at least Python 2.3. There ain't
|
||||
no getting around it. We do not require any version greater than 2.3,
|
||||
but we will be compatible with any version of Python >= 2.3. If you're
|
||||
a Python developer, you probably know how superior 2.3 is to previous
|
||||
incarnations. If you're not, just think about the difference between a
|
||||
bowl of plain vanilla ice cream and a banana split. Or something like
|
||||
that. Either way, *We're* Python developers and we like banana splits.
|
||||
So, be sure to install python2.3 or greater before continuing. You can
|
||||
get it from http://www.python.org/
|
||||
|
||||
For more information and help on how to use Supybot, checkout
|
||||
the documents under docs/ (especially GETTING_STARTED and CONFIGURATION).
|
||||
Our forums (http://forums.supybot.org/) may also be of use, especially
|
||||
the "Tips and Tricks" topic under "Supybot User Discussion".
|
||||
|
||||
|
||||
###
|
||||
# UNIX/Linux/*BSD:
|
||||
###
|
||||
|
||||
If you're installing Python using your distributor's packages, you may
|
||||
need a python-dev package installed, too. If you don't have a
|
||||
/usr/lib/python2.3/distutils directory (assuming /usr/lib/python2.3 is
|
||||
where your Python libs are installed), then you will need a python-dev
|
||||
package.
|
||||
|
||||
After you extract Supybot and cd into the supybot directory just
|
||||
created, you'll want to run (as root) "python setup.py install". This
|
||||
will install Supybot globally. If you need to install locally for
|
||||
whatever reason, see the addendum near the end of this document.
|
||||
You'll then have several new programs installed where Python scripts
|
||||
are normally installed on your system (/usr/bin or /usr/local/bin are
|
||||
common on UNIX systems). The two that might be of particular interest
|
||||
to you, the new user, are "supybot" and "supybot-wizard". The former
|
||||
("supybot") is the script to run an actual bot; the latter
|
||||
("supybot-wizard") is an in-depth wizard that provides a nice user
|
||||
interface for creating a registry file for your bot.
|
||||
|
||||
So after running supybot-wizard, you've got a nice registry file
|
||||
handy. If you're not satisfied with your answers to any of the
|
||||
questions you were asked, feel free to run the program again until
|
||||
you're satisfied with all your answers. Once you're satisfied,
|
||||
though, run the "supybot" program with the registry file you created
|
||||
as an argument. This will start the bot; unless you turned off
|
||||
logging to stdout, you'll see some nice log messages describing what
|
||||
the bot is doing at any particular moment; it may pause for a
|
||||
significant amount of time after saying "Connecting to ..." while the
|
||||
server tries to check its ident.
|
||||
|
||||
|
||||
###
|
||||
# Windows:
|
||||
###
|
||||
|
||||
*** If you are using an IPV6 connection, you will not be able to run
|
||||
Supybot under Windows (unless Python has fixed things). Current
|
||||
versions of Python for Windows are *not* built with IPV6 support. This
|
||||
isn't expected to be fixed until Python 2.4, at the earliest.
|
||||
|
||||
Now that you have Python installed, open up a command prompt. The
|
||||
easiest way to do this is to open the run dialog (Programs -> run) and
|
||||
type "cmd" (for Windows 2000/XP/2003) or "command" (for Windows 9x).
|
||||
In order to reduce the amount of typing you need to do, I suggest
|
||||
adding Python's directory to your path. If you installed Python using
|
||||
the default settings, you would then do the following in the command
|
||||
prompt (otherwise change the path to match your settings):
|
||||
|
||||
set PATH=%PATH%;C:\Python23\
|
||||
|
||||
You should now be able to type "python" to start the Python
|
||||
interpreter (CTRL-Z and Return to exit). Now that that's setup,
|
||||
you'll want to cd into the directory that was created when you
|
||||
unzipped Supybot; I'll assume you unzipped it to C:\Supybot for these
|
||||
instructions. From C:\Supybot, run "python setup.py install". This
|
||||
will install Supybot under C:\Python23\. If you want to install
|
||||
Supybot to a non-default location, see the addendum near the end of
|
||||
this document. You will now have several new programs installed in
|
||||
C:\Python23\Scripts\. The two that might be of particular interest to
|
||||
you, the new user, are "supybot" and "supybot-wizard". The former
|
||||
("supybot") is the script to run an actual bot; the latter
|
||||
("supybot-wizard") is an in-depth wizard that provides a nice user
|
||||
interface for creating a registry file for your bot.
|
||||
|
||||
Now you will want to run "python C:\Python23\Scripts\supybot-wizard"
|
||||
to generate a registry file for your bot. So after running
|
||||
supybot-wizard, you've got a nice registry file handy. If you're not
|
||||
satisfied with your answers to any of the questions you were asked,
|
||||
feel free to run the program again until you're satisfied with all
|
||||
your answers. Once you're satisfied, though, run "python
|
||||
C:\Python23\Scripts\supybot botname.conf". This will start the bot;
|
||||
unless you turned off logging to stdout, you'll see some nice log
|
||||
messages describing what the bot is doing at any particular moment; it
|
||||
may pause for a significant amount of time after saying "Connecting
|
||||
to ..." while the server tries to check its ident.
|
||||
|
||||
###
|
||||
# Addenda
|
||||
###
|
||||
Local installs: See this forum post: http://tinyurl.com/2tb37
|
28
LICENSE
28
LICENSE
@ -1,28 +0,0 @@
|
||||
Copyright (c) 2002-2004 Jeremiah Fincher and others
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions, and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions, and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the author of this software nor the name of
|
||||
contributors to this software may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
Portions of the included source code are copyright by its original author(s)
|
||||
and remain subject to its associated license.
|
38
README
38
README
@ -1,38 +0,0 @@
|
||||
EVERYONE:
|
||||
---------
|
||||
Read LICENSE. It's a 2-clause BSD license, but you should read it anyway.
|
||||
|
||||
|
||||
USERS:
|
||||
------
|
||||
If you're upgrading, read RELNOTES. If you're new to Supybot,
|
||||
read docs/GETTING_STARTED for an introduction to the bot, and read
|
||||
docs/CAPABILITIES to see how to use capabilities to your greater
|
||||
benefit.
|
||||
|
||||
If you have any trouble, feel free to swing by #supybot on
|
||||
irc.freenode.net or irc.oftc.net (we have a Supybot there relaying,
|
||||
so either network works) and ask questions. We'll be happy to help
|
||||
wherever we can. And by all means, if you find anything hard to
|
||||
understand or think you know of a better way to do something,
|
||||
*please* post it on Sourceforge.net so we can improve the bot!
|
||||
|
||||
WINDOWS USERS:
|
||||
--------------
|
||||
The wizards (supybot-wizard, supybot-newplugin, and
|
||||
supybot-adduser) are all installed to your Python directory's
|
||||
\Scripts. What that *probably* means is that you'll run them like
|
||||
this: C:\Python23\python C:\Python23\Scripts\supybot-wizard
|
||||
|
||||
|
||||
DEVELOPERS:
|
||||
-----------
|
||||
Read OVERVIEW to see what the modules are used for. Read PLUGIN-EXAMPLE
|
||||
to see some examples of callbacks and commands written for the bot.
|
||||
Read INTERFACES to see what kinds of objects you'll be dealing with.
|
||||
Read STYLE if you wish to contribute; all contributed code must meet
|
||||
the guidelines set forth there.
|
||||
|
||||
Be sure to run "test/test.py --help" to see what options are available
|
||||
to you when testing. Windows users in particular should be sure to
|
||||
exclude test_Debian.py and test_Unix.py.
|
262
RELNOTES
262
RELNOTES
@ -1,262 +0,0 @@
|
||||
Version 0.80.1
|
||||
|
||||
Bugfix release. No incompatibilities.
|
||||
|
||||
|
||||
Version 0.80.0
|
||||
|
||||
We *finally* hit 0.80.0! This release is completely compatible with
|
||||
the last release candidate.
|
||||
|
||||
An update to Babelfish may cause an error message to be displayed in
|
||||
the console when the bot is first run. The error message should be
|
||||
gone when the bot is restarted.
|
||||
|
||||
We also have a new community website at http://www.supybot.com/ where
|
||||
our users can submit their own plugins, view/download other people's
|
||||
plugins and discuss all things Supybot-related.
|
||||
|
||||
|
||||
Version 0.80.0rc3
|
||||
|
||||
Another bugfix release. This one was pretty important as it actually
|
||||
makes supybot.database.plugins.channelSpecific work properly.
|
||||
|
||||
|
||||
Version 0.80.0rc2
|
||||
|
||||
supybot.databases.plugins.channelSpecific.channel was renamed to
|
||||
supybot.databases.plugins.channelSpecific.link.
|
||||
|
||||
supybot.databases.plugins.channelSpecific.link.allow was added, which
|
||||
determines whether a channel will allow other channels to link to its
|
||||
database.
|
||||
|
||||
Infobot is no longer deprecated and the following changes were made to
|
||||
its config variables:
|
||||
supybot.plugins.Infobot.answerUnaddressedQuestions was renamed to
|
||||
supybot.plugins.Infobot.unaddressed.answerQuestions.
|
||||
supybot.plugins.Infobot.snarfUnaddressedDefinitions was renamed to
|
||||
supybot.plugins.Infobot.unaddressed.snarfDefinitions.
|
||||
supybot.plugins.Infobot.unaddressed.replyExistingFactoid was added to
|
||||
determine whether the bot will reply when someone attempts to create a
|
||||
duplicate factoid.
|
||||
|
||||
|
||||
Version 0.80.0pre6
|
||||
|
||||
Another bugfix release. No incompatibilities known. The only
|
||||
registry change is that supybot.databases.users.hash has been
|
||||
removed.
|
||||
|
||||
|
||||
Version 0.80.0pre5
|
||||
|
||||
Completely bugfix release. No incompatibilies known.
|
||||
|
||||
|
||||
Version 0.80.0pre4
|
||||
|
||||
Mainly a bug fix release. This will likely be the last release before
|
||||
0.80.0final, but we're gonna let it stew for a couple weeks to attempt
|
||||
to catch any lingering bugs.
|
||||
|
||||
ansycoreDrivers is now deprecated in favor of socketDrivers or
|
||||
twistedDrivers.
|
||||
|
||||
supybot.databases.plugins.channelSpecific.channel is now a channelValue
|
||||
so that you can link specific channels together (instead of all channels
|
||||
being linked together).
|
||||
|
||||
For those of you that use eval and/or exec, they have been removed from
|
||||
the Owner plugin and are now in sandbox/Debug.py (which you'll have to
|
||||
grab from CVS).
|
||||
|
||||
|
||||
Version 0.80.0pre3
|
||||
|
||||
The database format for the Note plugin has changed to a flatfile
|
||||
format; use tools/noteConvert.py to convert it to the new format.
|
||||
|
||||
Ditto that for the URL database.
|
||||
|
||||
FunDB is deprecated and will be removed at the next major release;
|
||||
use tools/fundbConvert.py to convert your old FunDB databases to Lart
|
||||
and Praise databases.
|
||||
|
||||
If you had turned off supybot.databases.plugins.channelSpecific, your
|
||||
non-channel-specific database files had gone directly into your data/
|
||||
directory. We had some problems with poor interactions between that
|
||||
configuration variable and channel capabilities, though, so we
|
||||
changed the implementation so that non-channel-specific databases are
|
||||
considered databases of a single (configurable) channel (defaulting
|
||||
to "#"). This will also help others who are converting from
|
||||
channel-specific to non-channel-specific databases, but for you
|
||||
who've already made the switch, you'll need to move your database
|
||||
files again, from data/ to data/# (or whatever channel you might
|
||||
change that variable to).
|
||||
|
||||
supybot.channels doesn't exist anymore; now the only list of channels
|
||||
to join is per-network, in supybot.networks.<network>.channels.
|
||||
|
||||
We weren't serializing supybot.replies.* properly in older versions.
|
||||
Now we are, but the old, improperly serialized versions won't work
|
||||
properly. Remove from your configuration file all variables
|
||||
beginning with "supybot.replies" before you start the bot.
|
||||
|
||||
The URL database has been changed again, but it will use a different
|
||||
filename so you shouldn't run into conflicts, just a newly-empty
|
||||
database.
|
||||
|
||||
We upgraded the SOAP stuff in others; you may do well to do a
|
||||
setup.py install --clean this time around.
|
||||
|
||||
|
||||
Version 0.80.0pre2
|
||||
|
||||
Many more bugs have been fixed. A few more plugins have been updated
|
||||
to use our new-style database abstraction. If it seems like your
|
||||
databases are suddenly empty, look for a new database file named
|
||||
Plugin.dbtype.db. We've also added a few more configuration variables.
|
||||
|
||||
|
||||
Version 0.80.0pre1
|
||||
|
||||
Tons of bugs fixed, many features and plugins added. Everything
|
||||
should be entirely compatible; many more configuration variables have
|
||||
been added.
|
||||
|
||||
|
||||
Version 0.79.9999
|
||||
|
||||
Some more bugs fixed, added a few features and a couple configuration
|
||||
variabless. This should hopefully be the last release before 0.80.0,
|
||||
which will finally bring us to pure Beta status.
|
||||
|
||||
|
||||
Version 0.79.999
|
||||
|
||||
Some bugs fixed, but the ones that were fixed were pretty big. This
|
||||
is, of course, completely compatible with the last release.
|
||||
|
||||
|
||||
Version 0.79.99
|
||||
|
||||
Many bugs fixed, thanks to the users who reported them. We're
|
||||
getting asymptotically closer to 0.80.0 -- maybe this'll be the last
|
||||
one, maybe we'll have to release an 0.79.999 -- either way, we're
|
||||
getting close :) Check out the ChangeLog for the fixes and a few new
|
||||
features.
|
||||
|
||||
|
||||
Version 0.79.9
|
||||
|
||||
We've changed so much stuff in this release that we've given up on
|
||||
users upgrading their configuration files for the new release. So
|
||||
do a clean install (python2.3 setup.py install --clean), run the
|
||||
wizard again, and kick some butt.
|
||||
|
||||
(It's rumored that you can save most of your old configuration by
|
||||
appending your new configuration at the end of your old configuration
|
||||
and running supybot with that new configuration file. This, of
|
||||
course, comes with no warranty or guarantee of utility -- try it if
|
||||
you want, but backup your original configuration file!)
|
||||
|
||||
|
||||
Version 0.77.2
|
||||
|
||||
This is a drop-in replacement for 0.77.1, with two exceptions. The
|
||||
configuration variable formerly known as
|
||||
"supybot.plugins.Services.password" is now known as
|
||||
"supybot.plugins.Services.NickServ.password", due to the fact that
|
||||
there might be different passwords for NickServ and ChanServ (and
|
||||
ChanServ passwords are per-channel, whereas NickServ passwords are
|
||||
global). If you're using the Services plugin, you'll need to make
|
||||
this change in order to continue identifying with services. The
|
||||
configuration variable formerly known as
|
||||
"supybot.plugins.Babelfish.disabledLanguages" is now known as
|
||||
"supybot.plugins.Babelfish.languages". The configuration variable now
|
||||
accepts the languages that *will* be translated as opposed to ones
|
||||
that are *not* translated.
|
||||
|
||||
Tests and the developer sandbox are not longer delivered with our
|
||||
release tarballs. If you're a developer and you want these, you
|
||||
should either check out CVS or download one of our weekly CVS
|
||||
snapshots, available at http://supybot.sourceforge.net/snapshots/ .
|
||||
|
||||
|
||||
Version 0.77.1
|
||||
|
||||
This is a drop-in replacement for 0.77.0 -- no incompatibilities, to
|
||||
out knowledge. Simply install over your old installation and restart
|
||||
your bot :)
|
||||
|
||||
|
||||
Version 0.77.0
|
||||
|
||||
Setup.py will automatically remove your old installations for you, no
|
||||
need to worry about that yourself.
|
||||
|
||||
Configuration has been *entirely* redone. Read the new
|
||||
GETTING_STARTED document to see how to work with configuration
|
||||
variables now. Your old botscripts from earlier versions *will not*
|
||||
work with the new configuration method. We'd appreciate it if you'd
|
||||
rerun the wizard in order for us to find any bugs that remain in it
|
||||
before we officially declare ourselves Beta. Note also that because
|
||||
of the new configuration method, the interface for plugins' configure
|
||||
function has changed: there are no longer any onStart or afterConnect
|
||||
arguments, so all configuration should be performed via the registry.
|
||||
|
||||
Channel capabilities have been changed; rather than being
|
||||
#channel.capability, they're now #channel,capability. It's a bit
|
||||
uglier, we know, but dots can be valid in channel names, and we
|
||||
needed the dot for handling plugin.command capabilities.
|
||||
tools/ircdbConvert.py should update this for you.
|
||||
|
||||
The on-disk format of the user/channel databases has changed to be far
|
||||
more readable. A conversion utility is included, as mentioned before:
|
||||
tools/ircdbConvert.py. Run this with no arguments to see the
|
||||
directions for using it.
|
||||
|
||||
Uh, we were just kidding about the upgrade script in 0.76.0 :) It'll
|
||||
be a little while longer. We do have several little upgrade scripts,
|
||||
though.
|
||||
|
||||
|
||||
Version 0.76.1
|
||||
|
||||
Almost entirely bugfixes, just some minor (and some less minor) bugs
|
||||
that need to get in before we really start hacking on the next
|
||||
version. Should be *entirely* compatible with 0.76.0.
|
||||
|
||||
|
||||
Version 0.76.0
|
||||
|
||||
Major bugfix release. A great number of bugs fixed. This is the last
|
||||
release without an upgrade script.
|
||||
|
||||
The only hiccup in the upgrade from 0.75.0 should be that you'll need
|
||||
to update your botscript to reflect the removal of the debug module.
|
||||
We'd rather you use supybot-wizard to generate a new botscript, of
|
||||
course, but if you insist on modifying your existing botscript, take a
|
||||
look at
|
||||
<http://cvs.sourceforge.net/viewcvs.py/supybot/supybot/src/template.py?r1=1.20&r2=1.21>
|
||||
to see what you need to do.
|
||||
|
||||
|
||||
Version 0.75.0
|
||||
|
||||
Don't forget to reinstall (i.e., run "python setup.py install" as
|
||||
root). Sometimes it even does good to remove the old installation;
|
||||
$PYTHON/site-packages/supybot can be removed with no problems
|
||||
whatsoever.
|
||||
|
||||
You will need to re-run supybot-wizard and generate a new botscript.
|
||||
|
||||
The Infobot plugin has been removed from this release; it's not ready
|
||||
for prime time. If you're interested in getting it running (i.e., you
|
||||
want full Infobot compatibility and aren't satisfied with either
|
||||
MoobotFactoids or Factoids) then swing over to #supybot and we can
|
||||
discuss the tests. We simply don't know enough about Infobot to make
|
||||
sure our Infobot plugin is an exact replica, and need someone's help
|
||||
with making the changes necessary for that.
|
40
TODO
40
TODO
@ -1,40 +0,0 @@
|
||||
Roughly in order of precedence (the closer to the front of the file,
|
||||
the more likely it'll be done before next release):
|
||||
|
||||
* We should have a channel-global value in ircdb.IrcChannel for
|
||||
ignoring unregistered users.
|
||||
|
||||
* We need to note when bans expire on a channel and send the unban,
|
||||
that way plugins can use the ircdb ban stuff and not worry about
|
||||
sending or scheduling unbans.
|
||||
|
||||
* We should probably add a "hello" command to make things more
|
||||
compatible with Eggdrop, since we've been replacing many eggdrop
|
||||
bots lately.
|
||||
|
||||
* We should be able to set the log level for plugins individually.
|
||||
|
||||
* Rbot has a "remind" plugin that seems pretty cool, we should get
|
||||
one as well. It might do well as a command in the Later plugin.
|
||||
|
||||
* MozBot has a "list" plugin that seems pretty cool, we should get
|
||||
one as well.
|
||||
|
||||
Problems that involve a lot of tedious minutiae, but really need to
|
||||
be done at some point:
|
||||
|
||||
Hard problems that won't get done until someone really wants to have
|
||||
some fun:
|
||||
|
||||
* Redundant relaying -- having more than one bot handle relaying,
|
||||
where a secondary one will automatically take over if the first
|
||||
becomes incapacitated.
|
||||
|
||||
* Have the bot detect when other bots are in the channel responding
|
||||
to the same prefix character, and do something based on that.
|
||||
|
||||
* Unicode support. Basically, we'd have to have a way to set the
|
||||
encoding for the server, and use unicode throughout internally.
|
||||
The real issue is how much it would affect the current code to
|
||||
switch it over to unicode, and what kind of burden it would put on
|
||||
plugin authors to deal with that issue.
|
63
debian/changelog
vendored
63
debian/changelog
vendored
@ -1,63 +0,0 @@
|
||||
supybot (0.79.9999+0.80.0pre2-1) unstable; urgency=high
|
||||
|
||||
* New upstream
|
||||
* Urgency high because it still fixes previous bugs.
|
||||
|
||||
-- James Vega <vega.james@gmail.com> Fri, 17 Sep 2004 11:18:43 -0400
|
||||
|
||||
supybot (0.79.9999-1) unstable; urgency=high
|
||||
|
||||
* New upstream
|
||||
* Urgency high because it still fixes previous bugs.
|
||||
|
||||
-- James Vega <vega.james@gmail.com> Mon, 6 Sep 2004 15:40:13 -0400
|
||||
|
||||
supybot (0.79.999-1) unstable; urgency=high
|
||||
|
||||
* New upstream
|
||||
* Urgency high because of a bug in the Ebay plugin that would hang
|
||||
the bot.
|
||||
|
||||
-- James Vega <vega.james@gmail.com> Tue, 31 Aug 2004 21:58:59 -0400
|
||||
|
||||
supybot (0.79.99-1) unstable; urgency=high
|
||||
|
||||
* New upstream
|
||||
* Urgency high as 0.79.9 had a serious bug in src/callbacks.py
|
||||
* Added a watch file
|
||||
|
||||
-- James Vega <vega.james@gmail.com> Mon, 30 Aug 2004 09:20:48 -0400
|
||||
|
||||
supybot (0.79.9-1) unstable; urgency=high
|
||||
|
||||
* New upstream (Closes: #268311)
|
||||
* New maintainer: James Vega <vega.james@gmail.com>
|
||||
* Urgency high to get the RC fix into Sarge (hopefully)
|
||||
* debian/control:
|
||||
+ Downgrade Recommends: python-sqlite to Suggests: python-sqlite
|
||||
+ Update Build-Depends: cdbs, debhelper (>= 4.1.67)
|
||||
+ Bump Standard-Version to 3.6.1.0, no changes necessary
|
||||
* Convert to cdbs
|
||||
|
||||
-- James Vega <vega.james@gmail.com> Thu, 26 Aug 2004 17:28:03 -0400
|
||||
|
||||
supybot (0.77.2-1) unstable; urgency=low
|
||||
|
||||
* New upstream
|
||||
|
||||
-- Jonathan Hseu <vomjom@debian.org> Sun, 18 Apr 2004 02:58:25 -0500
|
||||
|
||||
supybot (0.77.1-1) unstable; urgency=low
|
||||
|
||||
* New upstream
|
||||
* Add python-dev as a build-dep (closes: Bug#242953)
|
||||
|
||||
-- Jonathan Hseu <vomjom@debian.org> Sat, 10 Apr 2004 13:06:38 -0500
|
||||
|
||||
supybot (0.77.0-1) unstable; urgency=low
|
||||
|
||||
* Initial Release.
|
||||
* Paste LICENSE file into copyright
|
||||
|
||||
-- Jonathan Hseu <vomjom@debian.org> Tue, 10 Mar 2004 00:59:46 -0600
|
||||
|
1
debian/compat
vendored
1
debian/compat
vendored
@ -1 +0,0 @@
|
||||
4
|
21
debian/control
vendored
21
debian/control
vendored
@ -1,21 +0,0 @@
|
||||
Source: supybot
|
||||
Section: net
|
||||
Priority: optional
|
||||
Maintainer: James Vega <vega.james@gmail.com>
|
||||
Build-Depends: debhelper (>= 4.1.67), cdbs, python (>= 2.3), python-dev (>= 2.3)
|
||||
Standards-Version: 3.6.1.0
|
||||
|
||||
Package: supybot
|
||||
Architecture: all
|
||||
Depends: ${python:Depends}
|
||||
Suggests: python-twisted, python-sqlite
|
||||
Description: robust and user friendly Python IRC bot
|
||||
Supybot is a robust (it doesn't crash), user friendly (it's easy
|
||||
to configure) and programmer friendly (plugins are *extremely*
|
||||
easy to write) Python IRC bot. It aims to be an adequate
|
||||
replacement for most existing IRC bots. It includes a very
|
||||
flexible and powerful ACL system for controlling access to
|
||||
commands, as well as more than 50 builtin plugins providing
|
||||
around 400 actual commands.
|
||||
.
|
||||
Homepage: http://supybot.sourceforge.net/
|
53
debian/copyright
vendored
53
debian/copyright
vendored
@ -1,53 +0,0 @@
|
||||
This package was debianized by Jonathan Hseu <vomjom@debian.org> on
|
||||
Tue, 3 Feb 2004 21:45:46 -0600.
|
||||
|
||||
It was downloaded from http://supybot.sourceforge.net/
|
||||
|
||||
Upstream Authors:
|
||||
|
||||
/usr/share/doc/supybot/DEVS includes the names and descriptions of the
|
||||
developers.
|
||||
|
||||
Copyright:
|
||||
|
||||
Files located in /usr/lib/python2.3/site-packages/supybot/others/ have their
|
||||
own respective licenses, which are either commented at the top of the file, or
|
||||
are held in the __license__ variable.
|
||||
|
||||
The licenses in that directory are either:
|
||||
BSD-style, GPL, or the Python License
|
||||
The first two can be found in /usr/share/common-licenses/
|
||||
|
||||
The Python License can be found in:
|
||||
/usr/share/doc/python/copyright
|
||||
|
||||
The copyright for all other files are as follows (BSD-style):
|
||||
|
||||
Copyright (c) 2002, 2003, 2004 Jeremiah Fincher
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions, and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions, and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the author of this software nor the name of
|
||||
contributors to this software may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
Portions of the included source code are copyright by its original author(s)
|
||||
and remains subject to its associated license.
|
15
debian/docs
vendored
15
debian/docs
vendored
@ -1,15 +0,0 @@
|
||||
ACKS
|
||||
BUGS
|
||||
DEVS
|
||||
README
|
||||
RELNOTES
|
||||
TODO
|
||||
docs/CAPABILITIES
|
||||
docs/CONFIGURATION
|
||||
docs/FAQ
|
||||
docs/GETTING_STARTED
|
||||
docs/HACKING
|
||||
docs/INTERFACES
|
||||
docs/OVERVIEW
|
||||
docs/PLUGIN-EXAMPLE
|
||||
docs/STYLE
|
6
debian/rules
vendored
6
debian/rules
vendored
@ -1,6 +0,0 @@
|
||||
#!/usr/bin/make -f
|
||||
include /usr/share/cdbs/1/rules/debhelper.mk
|
||||
include /usr/share/cdbs/1/class/python-distutils.mk
|
||||
|
||||
DEB_INSTALL_MANPAGES_supybot := docs/man/supybot-adduser.1 docs/man/supybot-newplugin.1\
|
||||
docs/man/supybot.1 docs/man/supybot-wizard.1
|
6
debian/watch
vendored
6
debian/watch
vendored
@ -1,6 +0,0 @@
|
||||
# Example watch control file for uscan
|
||||
# Rename this file to "watch" and then you can run the "uscan" command
|
||||
# to check for upstream updates and more.
|
||||
# Site Directory Pattern Version Script
|
||||
version=2
|
||||
http://osdn.dl.sourceforge.net/supybot/Supybot-(\d.\d\d.\d+(.\d+)?)\.tar\.gz
|
@ -1,118 +0,0 @@
|
||||
Ok, some explanation of the capabilities system is probably in
|
||||
order. With most IRC bots (including the ones I've written myself
|
||||
prior to this one) "what a user can do" is set in one of two ways. On
|
||||
the *really* simple bots, each user has a numeric "level" and commands
|
||||
check to see if a user has a "high enough level" to perform some
|
||||
operation. On bots that are slightly more complicated, users have a
|
||||
list of "flags" whose meanings are hardcoded, and the bot checks to
|
||||
see if a user possesses the necessary flag before performing some
|
||||
operation. Both methods, IMO, are rather arbitrary, and force the
|
||||
user and the programmer to be unduly confined to less expressive
|
||||
constructs.
|
||||
|
||||
This bot is different. Every user has a set of "capabilities" that is
|
||||
consulted every time they give the bot a command. Commands, rather
|
||||
than checking for a user level of 100, or checking if the user has an
|
||||
"o" flag, are instead able to check if a user has the "owner"
|
||||
capability. At this point such a difference might not seem
|
||||
revolutionary, but at least we can already tell that this method is
|
||||
self-documenting, and easier for users and developers to understand
|
||||
what's truly going on.
|
||||
|
||||
If that was all, well, the capability system would be "cool", but not
|
||||
many people would say it was "awesome". But it *is* awesome! Several
|
||||
things are happening behind the scene that make it awesome, and these
|
||||
are things that couldn't happen if the bot was using numeric
|
||||
userlevels or single-character flags. First, whenever a user issues
|
||||
the bot a command, the command dispatcher checks to make sure the user
|
||||
doesn't have the "anticapability" for that command. An anticapability
|
||||
is a capability that, instead of saying "what a user can do", says
|
||||
what a user *cannot* do. It's formed rather simply by adding a dash
|
||||
("-") to the beginning of a capability; "rot13" is a capability, and
|
||||
"-rot13" is an anticapability. Anyway, when a user issues the bot a
|
||||
command, perhaps "calc" or "help", the bot first checks to make sure
|
||||
the user doesn't have the "-calc" or the "-help" capabilities before
|
||||
even considering responding to the user. So commands can be turned on
|
||||
or off on a *per user* basis, offering fine-grained control not often
|
||||
(if at all!) seen in other bots.
|
||||
|
||||
But that's not all! The capabilities system also supports *Channel*
|
||||
capabilities, which are capabilities that only apply to a specific
|
||||
channel; they're of the form "#channel,capability". Whenever a user
|
||||
issues a command to the bot in a channel, the command dispatcher also
|
||||
checks to make sure the user doesn't have the anticapability for that
|
||||
command *in that channel*, and if the user does, the bot won't respond
|
||||
to the user in the channel. Thus now, in addition to having the
|
||||
ability to turn individual commands on or off for an individual user,
|
||||
we can now turn commands on or off for an individual user on an
|
||||
individual channel!
|
||||
|
||||
So when a user "foo" sends a command "bar" to the bot on channel
|
||||
"#baz", first the bot checks to see if the user has the anticapability
|
||||
for the command by itself, "-bar". If so, it returns right then and
|
||||
there, completely ignoring the fact that the user issued that command
|
||||
to it. If the user doesn't have that anticapability, then the bot
|
||||
checks to see if the user issued the command over a channel, and if
|
||||
so, checks to see if the user has the antichannelcapability for that
|
||||
command, "#baz,-bar". If so, again, he returns right then and there
|
||||
and doesn't even think about responding to the bot. If neither of
|
||||
these anticapabilities are present, then the bot just responds to the
|
||||
user like normal.
|
||||
|
||||
From a programming perspective, capabilties are easy to use and
|
||||
flexible. Any command can check if a user has any capability, even
|
||||
ones not thought of when the bot was originally written.
|
||||
Commands/Callbacks can add their own capabilities -- it's as easy as
|
||||
just checking for a capability and documenting somewhere that a user
|
||||
needs that capability to do something.
|
||||
|
||||
From an end-user perspective, capabilities remove a lot of the mystery
|
||||
and esotery of bot control, in addition to giving the user absolutely
|
||||
finegrained control over what users are allowed to do with the bot.
|
||||
Additionally, defaults can be set by the end-user for both individual
|
||||
channels and for the bot as a whole, letting an end-user set the
|
||||
policy he wants the bot to follow for users that haven't yet
|
||||
registered in his user database. It's really a revolution!
|
||||
|
||||
There are several default capabilities the bot uses. The most
|
||||
important of these is the "owner" capability. This capability allows
|
||||
the person having it to use *any* command. It's best to keep this
|
||||
capability reserved to people who actually have access to the shell
|
||||
the bot is running on.
|
||||
|
||||
There is also the "admin" capability for non-owners that are highly
|
||||
trusted to administer the bot appropriately. They can do things such
|
||||
as change the bot's nick, globally enable/disable commands, cause the
|
||||
bot to ignore a given user, set the prefixchar, report bugs, etc.
|
||||
They generally cannot do administration related to channels, which is
|
||||
reserved for people with the next capability.
|
||||
|
||||
People who are to administer channels with the bot should have the
|
||||
#channel,op capability -- whatever channel they are to administrate,
|
||||
they should have that channel capability for "op". For example, since
|
||||
I want inkedmn to be an administrator in #supybot, I'll give him the
|
||||
#supybot,op capability. This is in addition to his admin capability,
|
||||
since the admin capability doesn't give the person having it control
|
||||
over channels. #channel,op is used for such things as
|
||||
giving/receiving ops, kickbanning people, lobotomizing the bot,
|
||||
ignoring users in the channel, and managing the channel capabilities.
|
||||
The #channel,op capability is also basically the equivalent of the
|
||||
owner capability for capabilities involving #channel -- basically
|
||||
anyone with the #channel,op capability is considered to have all
|
||||
positive capabilities and no negative capabilities for #channel.
|
||||
|
||||
One other globally important capability exists: "trusted". This is a
|
||||
command that basically says "This user can be trusted not to try and
|
||||
crash the bot." It allows users to call commands like Math.icalc,
|
||||
which potentially could cause the bot to begin a calculation that
|
||||
could potentially never return (a calculation like 10**10**10**10).
|
||||
Another command that requires the trusted capability is Utilties.re,
|
||||
which (due to the regular expression implementation in Python (and any
|
||||
other language that uses NFA regular expressions, like Perl or Ruby or
|
||||
Lua or ...) which can allow a regular expression to take exponential
|
||||
time to process). Consider what would happen if someone gave the
|
||||
bot the command 're [format join "" s/./ [dict go] /] [dict go]'
|
||||
|
||||
Other plugins may require different capabilities; the Factoids plugin
|
||||
requires #channel,factoids, the Topic plugin requires #channel,topic,
|
||||
etc.
|
@ -1,174 +0,0 @@
|
||||
So you've got your Supybot up and running and there are some things
|
||||
you don't like about it. Fortunately for you, chances are that these
|
||||
things are configurable, and this document is here to tell you how to
|
||||
configure them.
|
||||
|
||||
Configuration of Supybot is handled via the Config plugin, which
|
||||
controls runtime access to Supybot's registry (the configuration file
|
||||
generated by the supybot-wizard program you ran). The Config plugin
|
||||
provides a way to get or set variables, to list the available
|
||||
variables, and even to get help for certain variables. Take a moment
|
||||
now to read the help for each of those commands: config, list, and
|
||||
help. If you don't know how to get help on those commands, go ahead
|
||||
and read our GETTING_STARTED document before this one.
|
||||
|
||||
Now, if you're used to the Windows registry, don't worry, Supybot's
|
||||
registry is completely different. For one, it's completely plain
|
||||
text. There's no binary database sensitive to corruption, it's not
|
||||
necessary to use another program to edit it -- all you need is a
|
||||
simple text editor. But there is at least one good idea in Windows'
|
||||
registry: hierarchical configuration. Supybot's configuration
|
||||
variables are organized in a hierarchy: variables having to do with
|
||||
the way Supybot makes replies all start with supybot.reply; variables
|
||||
having to do with the way a plugin works all start with
|
||||
supybot.plugins.Plugin (where Plugin is the name of the plugin in
|
||||
question). This hierarchy is nice because it means the user isn't
|
||||
inundated with hundreds of unrelated and unsorted configuration
|
||||
variables.
|
||||
|
||||
Some of the more important configuration values are located directly
|
||||
under the base group, supybot. Things like the bot's nick, its ident,
|
||||
etc. Along with these config values are a few subgroups that contain
|
||||
other values. Some of the more prominent subgroups are: plugins
|
||||
(where all the plugin-specific configuration is held), reply (where
|
||||
variables affecting the way a Supybot makes its replies resides),
|
||||
replies (where all the specific standard replies are kept), and
|
||||
directories (where all the directories a Supybot uses are defined).
|
||||
There are other subgroups as well, but these are the ones we'll use in
|
||||
our example.
|
||||
|
||||
Using the Config plugin, you can list the values in a subgroup and get
|
||||
or set any of the values anywhere in the configuration hierarchy. For
|
||||
example, let's say you wanted to see what configuration values were
|
||||
under the "supybot" (the base group) hierarchy. You would simply
|
||||
issue this command:
|
||||
|
||||
<jemfinch|lambda> @config list supybot
|
||||
<supybot> @capabilities, @commands, @databases, @debug, @directories, @drivers,
|
||||
@log, @networks, @nick, @plugins, @protocols, @replies, @reply,
|
||||
alwaysJoinOnInvite, channels, defaultIgnore, defaultSocketTimeout,
|
||||
externalIP, flush, followIdentificationThroughNickChanges,
|
||||
humanTimestampFormat, ident, pidFile, snarfThrottle, upkeepInterval,
|
||||
and user
|
||||
|
||||
These are all the configuration groups and values which are under the
|
||||
base "supybot" group. Actually, their full names would each have a
|
||||
"supybot." appended on to the front of them, but it is omitted in the
|
||||
listing in order to shorten the output. The first entries in the output are
|
||||
the groups (distinguished by the @ symbol in front of them), and the rest are
|
||||
the configuration values.
|
||||
|
||||
Okay, now that you've used the Config plugin to list configuration
|
||||
variables, it's time that we start looking at individual variables and
|
||||
their values.
|
||||
|
||||
The first (and perhaps most important) thing you should know about
|
||||
each configuration variable is that they all have an associated help
|
||||
string to tell you what they represent. So the first command we'll
|
||||
cover is "config help". To see the help string for any value or
|
||||
group, simply use the "config help" command. For example, to see what
|
||||
this "supybot.snarfThrottle" configuration variable is all about, we'd
|
||||
do this:
|
||||
|
||||
<jemfinch|lambda> @config help supybot.snarfThrottle
|
||||
<supybot> jemfinch|lambda: A floating point number of seconds to throttle snarfed
|
||||
URLs, in order to prevent loops between two bots snarfing the same URLs and
|
||||
having the snarfed URL in the output of the snarf message. (Current value:
|
||||
10.0)
|
||||
|
||||
Pretty simple, eh?
|
||||
|
||||
Now, if you're curious what the current value of a configuration
|
||||
variable is, you'll use the "config" command with one argument, the
|
||||
name of the variable you want to see the value of:
|
||||
|
||||
<jemfinch|lambda> @config supybot.reply.whenAddressedBy.chars
|
||||
<supybot> jemfinch|lambda: '@'
|
||||
|
||||
To set this value, just stick an extra argument after the name:
|
||||
|
||||
<jemfinch|lambda> @config supybot.reply.whenAddressedBy.chars @$
|
||||
<supybot> jemfinch|lambda: The operation succeeded.
|
||||
|
||||
Now, check this out:
|
||||
|
||||
<jemfinch|lambda> $config supybot.reply.whenAddressedBy.chars
|
||||
<supybot> jemfinch|lambda: '@$'
|
||||
|
||||
Note that we used $ as our prefix character, and that the value of the
|
||||
configuration variable changed. If I were to use the "flush" command
|
||||
now, this change would be flushed to the registry file on disk (this
|
||||
would also happen if I made the bot quit, or pressed Ctrl-C in the
|
||||
terminal the bot was running in). Instead, I'll revert the change:
|
||||
|
||||
<jemfinch|lambda> $config supybot.reply.whenAddressedBy.chars @
|
||||
<supybot> jemfinch|lambda: The operation succeeded.
|
||||
<jemfinch|lambda> $note that this makes no response.
|
||||
|
||||
If you're ever curious what the default for a given configuration
|
||||
variable is, use the "config default" command:
|
||||
|
||||
<jemfinch|lambda> @config default supybot.reply.whenAddressedBy.chars
|
||||
<supybot> jemfinch|lambda: ''
|
||||
|
||||
Thus, to reset a configuration variable to its default value, you can
|
||||
simply say:
|
||||
|
||||
<jemfinch|lambda> @config supybot.reply.whenAddressedBy.chars [config
|
||||
default supybot.reply.whenAddressedBy.chars]
|
||||
<supybot> jemfinch|lambda: The operation succeeded.
|
||||
<jemfinch|lambda> @note that this does nothing
|
||||
|
||||
Simple, eh?
|
||||
|
||||
Now, let's say you want to find all configuration variables that might
|
||||
be even remotely related to opping. For that, you'll want the "config
|
||||
search" command. Check this out:
|
||||
|
||||
<jemfinch|lambda> @config search op
|
||||
<supybot> jemfinch|lambda:
|
||||
supybot.plugins.Enforcer.autoOp,
|
||||
supybot.plugins.Enforcer.autoHalfop,
|
||||
supybot.plugins.Enforcer.takeRevenge.onOps,
|
||||
supybot.plugins.Enforcer.cycleToGetOps,
|
||||
supybot.plugins.Topic, supybot.plugins.Topic.public,
|
||||
supybot.plugins.Topic.separator,
|
||||
supybot.plugins.Topic.format,
|
||||
supybot.plugins.Topic.recognizeTopiclen,
|
||||
supybot.plugins.Topic.default,
|
||||
supybot.plugins.Topic.undo.maz, and
|
||||
supybot.plugins.Relay.topicSync
|
||||
|
||||
Sure, it showed up all the topic-related stuff in there, but it also
|
||||
showed you all the op-related stuff, too. Do note, however, that you
|
||||
can only see configuration variables for plugins that you have loaded
|
||||
or that you loaded in the past; if you've never loaded a plugin,
|
||||
there's no way for the bot to know what configuration variables it
|
||||
registers.
|
||||
|
||||
Some people might like editing their registry file directly rather
|
||||
than manipulating all these things through the bot. For those people,
|
||||
we offer the "config reload" command, which reloads both registry
|
||||
configuration and user/channel/ignore database configuration. Just
|
||||
edit the interesting files and then give the bot the "config reload"
|
||||
command and it'll work as expected. Do note, however, that Supybot
|
||||
flushes his configuration files and databases to disk every hour or
|
||||
so, and if this happens after you've edited your configuration files
|
||||
but before you reload your changes, you could lose the changes you
|
||||
made. To prevent this, set the supybot.flush value to Off, and no
|
||||
automatic flushing will occur.
|
||||
|
||||
Many configuration variables can be specific to individual channels.
|
||||
The Config plugin provides an easy way to configure something for a
|
||||
specific channel; for instance, in order to set the prefix chars for a
|
||||
specific channel, do this in that channel:
|
||||
|
||||
config channel supybot.reply.whenAddressedBy.chars !
|
||||
|
||||
That'll set the prefix chars in the channel that message is sent in to
|
||||
!. Voila, channel-specific values! Also, note that when using the
|
||||
Config plugin's list command, channel-specific values are preceded by
|
||||
a '#' character to indicate such.
|
||||
|
||||
Anyway, that's about it for configuration. Have fun, and enjoy your
|
||||
configurable bot!
|
@ -1,25 +0,0 @@
|
||||
JADE=/usr/bin/jade
|
||||
JADETEX=/usr/bin/jadetex
|
||||
DVIPDF=/usr/bin/dvipdfm
|
||||
HTMLSTYLESHEET=/usr/share/sgml/docbook/stylesheet/dsssl/modular/html/docbook.dsl
|
||||
PRINTSTYLESHEET=/usr/share/sgml/docbook/stylesheet/dsssl/modular/print/docbook.dsl
|
||||
|
||||
html: example.sgml capabilities.sgml
|
||||
$(JADE) -t xml -d $(HTMLSTYLESHEET) $<
|
||||
|
||||
example.dvi: example.sgml
|
||||
$(JADE) -t tex -d $(PRINTSTYLESHEET) $<
|
||||
$(JADETEX) $(addsuffix .tex, $(basename $<))
|
||||
|
||||
example.pdf: example.dvi
|
||||
$(DVIPDF) -o $(addsuffix .pdf, $(basename $<)) $<
|
||||
|
||||
capabilities.dvi: capabilities.sgml
|
||||
$(JADE) -t tex -d $(PRINTSTYLESHEET) $<
|
||||
$(JADETEX) $(addsuffix .tex, $(basename $<))
|
||||
|
||||
capabilities.pdf: capabilities.dvi
|
||||
$(DVIPDF) -o $(addsuffix .pdf, $(basename $<)) $<
|
||||
|
||||
clean:
|
||||
rm -f *.html
|
@ -1,177 +0,0 @@
|
||||
The Official Supybot DocBook Metadocumentation
|
||||
(or, How Does One SGML File Turn Into All Those Document Formats?)
|
||||
|
||||
Okay, though this isn't the case yet, ideally all of Supybot's documentation
|
||||
can and will be done using DocBook and DocBook-related tools as well as a few
|
||||
custom extensions that I've written.
|
||||
|
||||
- How does DocBook work?
|
||||
First things first, you have to understand sort of how DocBook works in order
|
||||
to figure out how our documentation gets generated from just one file. What
|
||||
DocBook is, basically, is just a DTD (Document Type Definition). What that
|
||||
means is that it simply specifies how a document can be structured and still be
|
||||
considered a valid document by placing restrictions on what elements go where.
|
||||
It's a popular DTD because it is structured very well and it's not only fairly
|
||||
generic, but it also has nice elements that make documenting things (such as
|
||||
supybot) rather easy. It focuses on structure and content instead of
|
||||
presentation which is what makes it nice for writing things which are output
|
||||
format agnostic.
|
||||
|
||||
So, let's say we've written a proper DocBook document, now what? Well, using
|
||||
an output formatting tool and a stylesheet, you create whatever form of output
|
||||
you want. What we use for producing the outptut is jade, and DocBook comes
|
||||
with a few stylesheets that work with that tool to create output formats like
|
||||
HTML and TeX. From the TeX file we produce a DVI (device independent) file
|
||||
with latex, and from that we produce the print formats of our documents, like
|
||||
PDF and Postscript using tools like dvips and dvipdfm.
|
||||
|
||||
- What extra stuff do we do?
|
||||
Well, since our documents all have to do with an IRC bot, there are some very
|
||||
common things that we talk about a lot that we might like to format specially.
|
||||
For example, when we discuss a particular command for the bot we might like to
|
||||
have that text appear slightly different to emphasize the fact that it is
|
||||
special. So, for the commonly used items that weren't already covered by
|
||||
DocBook's DTD, I added elements into a new DTD which just extends DocBook's
|
||||
DTD. So now we have elements like <nick> and <channel> and <botcommand> that
|
||||
we can use for our documentation.
|
||||
|
||||
Of course, now that we have used a DTD with more stuff in it (than DocBook),
|
||||
the stylesheets that DocBook provides won't do any special formatting for those
|
||||
new elements so we have to write new stylesheets as well. Once again I just
|
||||
extended the existing ones with formatting instructions on how to treat the new
|
||||
elements. So with this done, now our HTML and TeX (and whatever else) output
|
||||
will be properly formatted.
|
||||
|
||||
- How do I make my own changes to the DTD and stylesheets?
|
||||
Primarily, you don't :) Ask me (Strike) first about it, and I will generally
|
||||
write them for you or explain a better way of doing things. This is especially
|
||||
true for the DTD, because that must remain consistent everywhere we write/read
|
||||
supybot docs based on it. The stylesheets are more lax and can be modified to
|
||||
produce whatever kind of output you wish.
|
||||
|
||||
So, with that warning/reminder out of the way, here's how to modify each
|
||||
anyway. This doesn't really assume any knowledge of how to write a DTD, nor is
|
||||
it an exhaustive reference on writing one, so don't treat it as such. I'm
|
||||
basically just going to explain how to add extra elements that will play well
|
||||
with the DocBook DTD.
|
||||
|
||||
-- Adding an element to the DTD
|
||||
If you've decided that there's a certain "thing" that's mentioned a lot in the
|
||||
documentation and deserves classification (for potential special formatting),
|
||||
you'll probably want to create a new element (or set of elements) for it. I'll
|
||||
walk you through how I added the "nick" element to our DTD (though many/most of
|
||||
the elements I added follow an identical process).
|
||||
|
||||
The very first thing you need to figure out is: where in my document does this
|
||||
element "fit". That is to say, what elements should/can rightly contain this
|
||||
particular one? In the case of the "nick" element, it's basically always an
|
||||
inline-formatted deal that belongs in paragraphs for the most part. For those
|
||||
of you scratching your heads at that last sentence, perhaps thinking "okay, so
|
||||
how are we supposed to know what is relevant?" I say, "don't worry, I learned
|
||||
by example as well." Basically, I just looked through the DocBook DTD and
|
||||
figured out where things belong. Now, even if you don't know the DocBook DTD
|
||||
front-to-back, you can still peruse it to figure out where your new element
|
||||
belongs. Obviously, you should probably know *some* DocBook to figure out what
|
||||
each element means, but luckily all of our docs have been converted to DocBook
|
||||
and serve as nice examples of the usage of many elements :)
|
||||
|
||||
Now, to figure out where something like "nick" belongs. In many ways, a nick
|
||||
is sort of like a variable name (at least in documentation usage). So, the
|
||||
element I chose to base it off of was "varname". If you have the DocBook DTD
|
||||
installed (as you should if you intend on making extensions to it), the varname
|
||||
element definition is contained in the dbpoolx.mod filename (in Debian, it's
|
||||
under /usr/share/sgml/docbook/dtd/4.2). How did I know this? Well, grep is
|
||||
your friend and mine too, and dbpoolx is the only filename that shows up when
|
||||
grepping for "varname" in the DocBook DTD directory. So, we open up dbpoolx.mod and search for varname. The first thing we find it in looks like this:
|
||||
|
||||
<!ENTITY % tech.char.class
|
||||
"action|application
|
||||
|classname|methodname|interfacename|exceptionname
|
||||
|ooclass|oointerface|ooexception
|
||||
|command|computeroutput
|
||||
|database|email|envar|errorcode|errorname|errortype|errortext|filename
|
||||
|function|guibutton|guiicon|guilabel|guimenu|guimenuitem
|
||||
|guisubmenu|hardware|interface|keycap
|
||||
|keycode|keycombo|keysym|literal|constant|markup|medialabel
|
||||
|menuchoice|mousebutton|option|optional|parameter
|
||||
|prompt|property|replaceable|returnvalue|sgmltag|structfield
|
||||
|structname|symbol|systemitem|token|type|userinput|varname
|
||||
%ebnf.inline.hook;
|
||||
%local.tech.char.class;">
|
||||
|
||||
Hmm, this doesn't look like a definition of varname (to me, but I sort of
|
||||
cheated by having read about DocBook before-hand ;)), but it will be important
|
||||
to remember for later. Let's try and find the element definition for varname
|
||||
(so, basically, let's look for the first line that starts with "<!ELEMENT ").
|
||||
The first line I come up with when I search is:
|
||||
|
||||
<!ELEMENT varname %ho; (%smallcptr.char.mix;)*>
|
||||
|
||||
Rather than write a separate tutorial for interpreting DTDs, I found a good
|
||||
SGML tutorial online that explains everything necessary to help you parse the
|
||||
DocBook DTD to figure out what the varname element really is, as well as to
|
||||
help you learn all the stuff necessary for what we will cover in creating our
|
||||
new nick element. That tutorial is at
|
||||
http://www.w3.org/TR/WD-html40-970708/intro/sgmltut.html#howtodtd (it's for
|
||||
reading the HTML DTD, but it applies to any DTD).
|
||||
|
||||
So, now that we understand how to write/read things for a DTD, we arrive at the
|
||||
time where we can write the actual definition of our "nick" element:
|
||||
|
||||
<!ELEMENT Nick - - ((%smallcptr.char.mix;)+)>
|
||||
|
||||
As we learned in the above tutorial, this means that we are creating an element
|
||||
named "nick", which must have start and end tags, and is defined to contain one
|
||||
or more of whatever is in "smallcptr.char.mix". And rather than hunt through
|
||||
the DocBook DTD to figure out what that is, for now we'll just live with the
|
||||
fact that whatever can go into a DocBook varname can go into our new nick
|
||||
element. If you feel so inclined, feel free to try and define the content
|
||||
model for nick to only include valid nick characters. It's perfectly doable,
|
||||
and I'll probably do it at some point but I haven't yet.
|
||||
|
||||
Since we're extending the DocBook DTD, I also decided that it'd be nice to
|
||||
follow the element creation conventions observed in their DTD, so there are a
|
||||
few more lines associated with our new nick element. All of them are related
|
||||
to the attributes of the element, and allowing for them to be extended by
|
||||
external DTDs (much like we are doing, only we aren't changing attributes of
|
||||
existing elements, just adding our own). The first one is:
|
||||
|
||||
<!ENTITY % local.nick.attrib "">
|
||||
|
||||
This basically defines an empty entity named local.nick.attrib which we will
|
||||
include so that if anyone chooses to extend the nick attributes, all they have
|
||||
to do is redefine local.nick.attrib.
|
||||
|
||||
<!ENTITY % nick.role.attrib "%role.attrib;">
|
||||
|
||||
To tell you the truth, I'm not entirely sure what this is for, but it follows the DocBook convention :)
|
||||
|
||||
<!ATTLIST Nick
|
||||
%common.attrib;
|
||||
%local.nick.attrib;
|
||||
%nick.role.attrib;
|
||||
>
|
||||
|
||||
This is, of course, our attribute list for our nick element. It consists of
|
||||
the two things we just defined as well as common.attrib which contains things
|
||||
like "id" and whatnot which all DocBook elements are expected to have.
|
||||
|
||||
-- Extending the DocBook DTD to recognize new elements
|
||||
So, that's all you need to define your new element. But, we're not done just
|
||||
yet! We're almost there, we just need to make it so that it works with the
|
||||
existing DocBook elements, otherwise it's no good to us. Since we defined our
|
||||
element to esentially be the same as varname, it probably belongs at the same
|
||||
place within the DocBook schema as varname. Do you remember when we had that
|
||||
large entity definition that wasn't what we were looking for at the time though
|
||||
I said it'd be important later? Well, later is now. So, what that line tells
|
||||
us is what class of elements DocBook has varname in, which is
|
||||
"tech.char.class". And thanks to the DocBook convention of defining a
|
||||
local.<classname> entity that we can extend, all we have to do is redefine
|
||||
local.tech.char.class to contain "nick", and we are done.
|
||||
|
||||
You may notice, however, that we don't actually put varname right into the
|
||||
local.tech.char.class entity, but instead we create our own
|
||||
supybot.tech.char.class class of elements that are supybot-specific (and are
|
||||
the equivalent of DocBook's tech.char.class elements) and instead, put all of
|
||||
those into the local.tech.char.class entity. Basically, we just go through one
|
||||
more level of indirection.
|
@ -1,221 +0,0 @@
|
||||
<!DOCTYPE article SYSTEM "supybot.dtd">
|
||||
|
||||
<article>
|
||||
<articleinfo>
|
||||
<authorgroup>
|
||||
<author>
|
||||
<firstname>Jeremiah</firstname>
|
||||
<surname>Fincher</surname>
|
||||
</author>
|
||||
<editor>
|
||||
<firstname>Daniel</firstname>
|
||||
<surname>DiPaolo</surname>
|
||||
<contrib>DocBook translator</contrib>
|
||||
</editor>
|
||||
</authorgroup>
|
||||
<title>Supybot capabilities system explanation</title>
|
||||
<revhistory>
|
||||
<revision>
|
||||
<revnumber>0.1</revnumber>
|
||||
<date>18 Feb 2004</date>
|
||||
<revremark>Initial Docbook translation</revremark>
|
||||
</revision>
|
||||
<revision>
|
||||
<revnumber>0.2</revnumber>
|
||||
<date>04 Sep 2004</date>
|
||||
<revremark>Update Docbook translation</revremark>
|
||||
</revision>
|
||||
</revhistory>
|
||||
</articleinfo>
|
||||
<sect1>
|
||||
<title>Introduction</title>
|
||||
<subtitle>
|
||||
Supybot's capabilities overview and comparisons to other bots
|
||||
</subtitle>
|
||||
<para>
|
||||
Ok, some some explanation of the capabilities system is probably
|
||||
in order. With most IRC bots (including the ones I've written
|
||||
myself prior to this one) “what a user can do” is set
|
||||
in one of two ways. On the <emphasis>really</emphasis> simple
|
||||
bots, each user has a numeric “level” and commands
|
||||
check to see if a user has a “high enough level” to
|
||||
perform some operation. On bots that are slightly more
|
||||
complicated, users have a list of “flags” whose
|
||||
meanings are hardcoded, and the bot checks to see if a user
|
||||
possesses the necessary flag before performing some operation.
|
||||
Both methods, IMO, are rather arbitrary, and force the user and
|
||||
the programmer to be unduly confined to less expressive
|
||||
constructs.
|
||||
</para>
|
||||
<para>
|
||||
This bot is different. Every user has a set of
|
||||
“capabilities” that is consulted every time they give
|
||||
the bot a command. Commands, rather than checking for a user
|
||||
level of 100, or checking if the user has an <varname>o</varname>
|
||||
flag, are instead able to check if a user has the
|
||||
<capability>owner</capability> capability. At this point such a
|
||||
difference might not seem revolutionary, but at least we can
|
||||
already tell that this method is self-documenting, and easier for
|
||||
users and developers to understand what's truly going on.
|
||||
</para>
|
||||
</sect1>
|
||||
<sect1>
|
||||
<title>What sets supybot's capabilities apart</title>
|
||||
<para>
|
||||
If that was all, well, the capability system would be
|
||||
“cool”, but not many people would say it was
|
||||
“awesome”. But it <emphasis>is</emphasis> awesome!
|
||||
Several things are happening behind the scene that make it
|
||||
awesome, and these are things that couldn't happen if the bot was
|
||||
using numeric userlevels or single-character flags. First,
|
||||
whenever a user issues the bot a command, the command dispatcher
|
||||
checks to make sure the user doesn't have the
|
||||
“anticapability” for that command. An anticapability is
|
||||
a capability that, instead of saying “what a user can
|
||||
do”, says what a user <emphasis>cannot</emphasis> do. It's
|
||||
formed rather simply by adding a dash (“-”) to the
|
||||
beginning of a capability; <botcommand>rot13</botcommand> is a
|
||||
capability, and <botcommand>-rot13</botcommand> is an
|
||||
anticapability. Anyway, when a user issues the bot a command,
|
||||
perhaps <botcommand>calc</botcommand> or
|
||||
<botcommand>help</botcommand>, the bot first checks to make sure
|
||||
the user doesn't have the <capability>-calc</capability> or the
|
||||
<capability>-help</capability> capabilities before even
|
||||
considering responding to the user. So commands can be turned on
|
||||
or off on a <emphasis>per user</emphasis> basis, offering
|
||||
finegrained control not often (if at all!) seen in other bots.
|
||||
</para>
|
||||
<sect2>
|
||||
<title>Channel capabilities</title>
|
||||
<para>
|
||||
But that's not all! The capabilities system also supports
|
||||
<emphasis>Channel</emphasis> capabilities, which are
|
||||
capabilities that only apply to a specific channel; they're of
|
||||
the form <capability>#channel,capability</capability>.
|
||||
Whenever a user issues a command to the bot in a channel, the
|
||||
command dispatcher also checks to make sure the user doesn't
|
||||
have the anticapability for that command <emphasis>in that
|
||||
channel</emphasis> and if the user does, the bot won't respond
|
||||
to the user in the channel. Thus now, in addition to having
|
||||
the ability to turn individual commands on or off for an
|
||||
individual user, we can now turn commands on or off for an
|
||||
individual user on an individual channel!
|
||||
</para>
|
||||
<para>
|
||||
So when a user <nick>foo</nick> sends a command
|
||||
<botcommand>bar</botcommand> to the bot on channel
|
||||
<channel>#baz</channel>, first the bot checks to see if the
|
||||
user has the anticapability for the command by itself,
|
||||
<capability>-bar</capability>. If so, it returns right then
|
||||
and there, compltely ignoring the fact that the user issued
|
||||
that command to it. If the user doesn't have that
|
||||
anticapability, then the bot checks to see if the user issued
|
||||
the command over a channel, and if so, checks to see if the
|
||||
user has the antichannelcapability for that command,
|
||||
<capability>#baz,-bar</capability>. If so, again, he returns
|
||||
right then and there and doesn't even think about responding
|
||||
to the bot. If neither of these anticapabilities are present,
|
||||
then the bot just responds to the user like normal.
|
||||
</para>
|
||||
</sect2>
|
||||
</sect1>
|
||||
<sect1>
|
||||
<title>Motivations behind the capabilities system</title>
|
||||
<sect2>
|
||||
<title>A programmer's perspective</title>
|
||||
<para>
|
||||
From a programming perspective, capabilties are easy to use
|
||||
and flexible. Any command can check if a user has any
|
||||
capability, even ones not thought of when the bot was
|
||||
originally written. Commands/Callbacks can add their own
|
||||
capabilities – it's as easy as just checking for a
|
||||
capability and documenting somewhere that a user needs that
|
||||
capability to do something.
|
||||
</para>
|
||||
</sect2>
|
||||
<sect2>
|
||||
<title>An end-user's perspective</title>
|
||||
<para>
|
||||
From an end-user perspective, capabilities remove a lot of the
|
||||
mystery and esotery of bot control, in addition to giving the
|
||||
user absolutely finegrained control over what users are
|
||||
allowed to do with the bot. Additionally, defaults can be set
|
||||
by the end-user for both individual channels and for the bot
|
||||
as a whole, letting an end-user set the policy he wants the
|
||||
bot to follow for users that haven't yet registered in his
|
||||
user database.
|
||||
</para>
|
||||
</sect2>
|
||||
<para>
|
||||
It's really a revolution!
|
||||
</para>
|
||||
</sect1>
|
||||
<sect1>
|
||||
<title>Hard-coded supybot capabilities</title>
|
||||
<para>
|
||||
There are several default capabilities the bot uses. The most
|
||||
important of these is the <capability>owner</capability>
|
||||
capability. This capability allows the person having it to use
|
||||
<emphasis>any</emphasis> command. It's best to keep this
|
||||
capability reserved to people who actually have access to the
|
||||
shell the bot is running on.
|
||||
</para>
|
||||
<para>
|
||||
There is also the <capability>admin</capability> capability for
|
||||
non-owners that are highly trusted to administer the bot
|
||||
appropriately. They can do things such as change the bot's nick,
|
||||
globally enable/disable commands, cause the bot to ignore a given
|
||||
user, set the prefixchar, report bugs, etc. They generally cannot
|
||||
do administration related to channels, which is reserved for
|
||||
people with the next capability.
|
||||
</para>
|
||||
<para>
|
||||
People who are to administer channels with the bot should have the
|
||||
<capability>#channel,op</capability> capability – whatever
|
||||
channel they are to administrate, they should have that channel
|
||||
capability for <capability>op</capability>. For example, since I
|
||||
want <nick>inkedmn</nick> to be an administrator in
|
||||
<channel>#supybot</channel>, I'll give him the
|
||||
<capability>#supybot,op</capability> capability. This is in
|
||||
addition to his <capability>admin</capability> capability, since
|
||||
the <capability>admin</capability> capability doesn't give the
|
||||
person having it control over channels.
|
||||
<capability>#channel.op</capability> is used for such things as
|
||||
giving/receiving ops, kickbanning people, lobotomizing the bot,
|
||||
ignoring users in the channel, and managing the channel
|
||||
capabilities. The <capability>#channel,op</capability> capability
|
||||
is also basically the equivalent of the owner capability for
|
||||
capabilities involving <channel>#channel</channel> –
|
||||
basically anyone with the <capability>#channel,op</capability>
|
||||
capability is considered to have all positive capabilities and no
|
||||
negative capabilities for <channel>#channel</channel>.
|
||||
</para>
|
||||
<para>
|
||||
One other globally important capability exists:
|
||||
<capability>trusted</capability>. This is a command that
|
||||
basically says “This user can be trusted not to try and
|
||||
crash the bot.” It allows users to call commands like
|
||||
<botcommand>Math.icalc</botcommand>, which potentially could cause the
|
||||
bot to begin a calculation that could potentially never return (a
|
||||
calculation like 10**10**10**10). Another command that requires
|
||||
the trusted capability is <botcommand>Utilties.re</botcommand>, which
|
||||
(due to the regular expression implementation in Python (and any
|
||||
other language that uses NFA regular expressions, like Perl or
|
||||
Ruby or Lua or …) which can allow a regular expression to
|
||||
take exponential time to process). Consider what would happen if
|
||||
the someone gave the bot the command <literal>re [strjoin "" s/./
|
||||
[dict go] /] [dict go]</literal>.
|
||||
</para>
|
||||
</sect1>
|
||||
<sect1>
|
||||
<title>Other capabilities</title>
|
||||
<para>
|
||||
Other plugins may require different capabilities; the
|
||||
<plugin>Factoids</plugin> plugin requires
|
||||
<capability>#channel,factoids</capability>, the <plugin>Topic</plugin>
|
||||
plugin requires <capability>#channel,topic</capability>, etc.
|
||||
</para>
|
||||
</sect1>
|
||||
</article>
|
||||
|
||||
|
@ -1,316 +0,0 @@
|
||||
<!DOCTYPE article SYSTEM "supybot.dtd">
|
||||
|
||||
<article>
|
||||
<articleinfo>
|
||||
<authorgroup>
|
||||
<author>
|
||||
<firstname>Jeremiah</firstname>
|
||||
<surname>Fincher</surname>
|
||||
</author>
|
||||
<author>
|
||||
<firstname>Daniel</firstname>
|
||||
<surname>DiPaolo</surname>
|
||||
</author>
|
||||
<editor>
|
||||
<firstname>Daniel</firstname>
|
||||
<surname>DiPaolo</surname>
|
||||
<contrib>DocBook translator</contrib>
|
||||
</editor>
|
||||
</authorgroup>
|
||||
<title>Supybot configuration system explanation</title>
|
||||
<revhistory>
|
||||
<revision>
|
||||
<revnumber>0.1</revnumber>
|
||||
<date>18 Feb 2004</date>
|
||||
<revremark>Initial Docbook translation</revremark>
|
||||
</revision>
|
||||
<revision>
|
||||
<revnumber>0.2</revnumber>
|
||||
<date>26 Feb 2004</date>
|
||||
<revremark>Conversion to Supybot DTD</revremark>
|
||||
</revision>
|
||||
<revision>
|
||||
<revnumber>0.3</revnumber>
|
||||
<date>4 Sep 2004</date>
|
||||
<revremark>Update Docbook translation</revremark>
|
||||
</revision>
|
||||
</revhistory>
|
||||
</articleinfo>
|
||||
<sect1>
|
||||
<title>Introduction</title>
|
||||
<para>
|
||||
So you've got your Supybot up and running and there are some
|
||||
things you don't like about it. Fortunately for you, chances are
|
||||
that these things are configurable, and this document is here to
|
||||
tell you how to configure them.
|
||||
</para>
|
||||
<para>
|
||||
Configuration of Supybot is handled via the
|
||||
<plugin>Config</plugin> plugin, which controls runtime access to
|
||||
Supybot's registry (the configuration file generated by the
|
||||
<script>supybot-wizard</script> program you ran). The
|
||||
<plugin>Config</plugin> plugin provides a way to get or set
|
||||
variables, to list the available variables, and even to get help
|
||||
for certain variables. Take a moment now to read the help for
|
||||
each of those commands: <botcommand>config</botcommand>,
|
||||
<botcommand>list</botcommand>, and
|
||||
<botcommand>help</botcommand>. If you don't know how to get help on
|
||||
those commands, go ahead and read our
|
||||
<filename>GETTING_STARTED</filename> document before this one.
|
||||
</para>
|
||||
</sect1>
|
||||
<sect1>
|
||||
<title>Supybot's registry</title>
|
||||
<para>
|
||||
Now, if you're used to the Windows registry, don't worry,
|
||||
Supybot's registry is completely different. For one, it's
|
||||
completely plain text. There's no binary database sensitive to
|
||||
corruption, it's not necessary to use another program to edit it
|
||||
– all you need is a simple text editor. But there is at
|
||||
least one good idea in Windows' registry: hierarchical
|
||||
configuration. Supybot's configuration variables are organized in
|
||||
a hierarchy: variables having to do with the way Supybot makes
|
||||
replies all start with
|
||||
<registrygroup>supybot.reply</registrygroup>; variables having to
|
||||
do with the way a plugin works all start with
|
||||
<registrygroup>supybot.plugins.Plugin</registrygroup> (where
|
||||
<plugin>Plugin</plugin> is the name of the plugin in question).
|
||||
This hierarchy is nice because it means the user isn't inundated
|
||||
with hundreds of unrelated and unsorted configuration variables.
|
||||
</para>
|
||||
<para>
|
||||
Some of the more important configuration values are located
|
||||
directly under the base group,
|
||||
<registrygroup>supybot</registrygroup>. Things like the bot's
|
||||
nick, its ident, etc. Along with these config values are a few
|
||||
subgroups that contain other values. Some of the more prominent
|
||||
subgroups are: <registrygroup>plugins</registrygroup> (where all
|
||||
the plugin-specific configuration is held),
|
||||
<registrygroup>reply</registrygroup> (where variables affecting
|
||||
the way a Supybot makes its replies resides),
|
||||
<registrygroup>replies</registrygroup> (where all the specific
|
||||
standard replies are kept), and
|
||||
<registrygroup>directories</registrygroup> (where all the
|
||||
directories a Supybot uses are defined). There are other
|
||||
subgroups as well, but these are the ones we'll use in our
|
||||
example.
|
||||
</para>
|
||||
<sect2>
|
||||
<title>Config plugin commands</title>
|
||||
<sect3>
|
||||
<title>Listing registry contents</title>
|
||||
<para>
|
||||
Using the <plugin>Config</plugin> plugin, you can list
|
||||
the values in a subgroup and get or set any of the values
|
||||
anywhere in the configuration hierarchy. For example,
|
||||
let's say you wanted to see what configuration values were
|
||||
under the <registrygroup>supybot</registrygroup> (the base
|
||||
group) hierarchy. You would simply issue this command:
|
||||
</para>
|
||||
<ircsession>
|
||||
<jemfinch|lambda> @config list supybot
|
||||
<supybot> @capabilities, @commands, @databases, @debug, @directories, @drivers,
|
||||
@log, @networks, @nick, @plugins, @protocols, @replies, @reply,
|
||||
alwaysJoinOnInvite, channels, defaultIgnore, defaultSocketTimeout,
|
||||
externalIP, flush, followIdentificationThroughNickChanges,
|
||||
humanTimestampFormat, ident, pidFile, snarfThrottle, upkeepInterval,
|
||||
and user
|
||||
</ircsession>
|
||||
<para>
|
||||
These are all the configuration groups and values which
|
||||
are under the base <registrygroup>supybot</registrygroup>
|
||||
group. Actually, their full names would each have a
|
||||
“supybot.” appended on to the front of them,
|
||||
but it is omitted in the listing in order to shorten the
|
||||
output. The first entries in the output are the groups
|
||||
(distinguished by the @ symbol in front of them), and the
|
||||
rest are the configuration values.
|
||||
</para>
|
||||
</sect3>
|
||||
<sect2>
|
||||
<title>Supybot's registry</title>
|
||||
<sect3>
|
||||
<title>Dealing with registry values</title>
|
||||
<para>
|
||||
Okay, now that you've used the <plugin>Config</plugin>
|
||||
plugin to list configuration variables, it's time that we
|
||||
start looking at individual variables and their values.
|
||||
</para>
|
||||
<sect4>
|
||||
<title>Built-in help for registry values</title>
|
||||
<para>
|
||||
The first (and perhaps most important) thing you
|
||||
should know about each configuration variable is that
|
||||
they all have an associated help string to tell you
|
||||
what they represent. So the first command we'll cover
|
||||
is <botcommand>config help</botcommand>. To see the
|
||||
help string for any value or group, simply use the
|
||||
<botcommand>config help</botcommand> command. For
|
||||
example, to see what this
|
||||
<registrygroup>supybot.snarfThrottle</registrygroup>
|
||||
configuration variable is all about, we'd do this:
|
||||
</para>
|
||||
<ircsession>
|
||||
<jemfinch|lambda> @config help supybot.snarfThrottle
|
||||
<supybot> jemfinch|lambda: A floating point number of seconds to throttle snarfed
|
||||
URLs, in order to prevent loops between two bots snarfing the same URLs and
|
||||
having the snarfed URL in the output of the snarf message. (Current value:
|
||||
10.0)
|
||||
</ircsession>
|
||||
<para>
|
||||
Pretty simple, eh?
|
||||
</para>
|
||||
</sect4>
|
||||
<sect4>
|
||||
<title>Getting/setting registry values</title>
|
||||
<para>
|
||||
Now, if you're curious what the current value of a
|
||||
configuration variable is, you'll use the
|
||||
<botcommand>config</botcommand> command with one
|
||||
argument, the name of the variable you want to see the
|
||||
value of:
|
||||
</para>
|
||||
<ircsession>
|
||||
<jemfinch|lambda> @config supybot.reply.whenAddressedBy.chars
|
||||
<supybot> jemfinch|lambda: '@'
|
||||
</ircsession>
|
||||
<para>
|
||||
To set this value, just stick an extra argument after
|
||||
the name:
|
||||
</para>
|
||||
<ircsession>
|
||||
<jemfinch|lambda> @config supybot.reply.whenAddressedBy.chars @$
|
||||
<supybot> jemfinch|lambda: The operation succeeded.
|
||||
</ircsession>
|
||||
<para>
|
||||
Now, check this out:
|
||||
</para>
|
||||
<ircsession>
|
||||
<jemfinch|lambda> $config supybot.reply.whenAddressedBy.chars
|
||||
<supybot> jemfinch|lambda: '@$'
|
||||
</ircsession>
|
||||
<para>
|
||||
Note that we used <literal>$</literal> as our prefix
|
||||
character, and that the value of the configuration
|
||||
variable changed. If I were to use the
|
||||
<botcommand>flush</botcommand> command now, this
|
||||
change would be flushed to the registry file on disk
|
||||
(this would also happen if I made the bot quit, or
|
||||
pressed
|
||||
<keycombo>
|
||||
<keycap>Ctrl</keycap>
|
||||
<keycap>C</keycap>
|
||||
</keycombo>
|
||||
in the terminal the bot was running in). Instead,
|
||||
I'll revert the change:
|
||||
</para>
|
||||
<ircsession>
|
||||
<jemfinch|lambda> $config supybot.reply.whenAddressedBy.chars @
|
||||
<supybot> jemfinch|lambda: The operation succeeded.
|
||||
<jemfinch|lambda> $note that this makes no response.
|
||||
</ircsession>
|
||||
<para>
|
||||
If you're ever curious what the default for a given
|
||||
configuration variable is, use the <botcommand>config
|
||||
default</botcommand> command:
|
||||
</para>
|
||||
<ircsession>
|
||||
<jemfinch|lambda> @config default supybot.reply.whenAddressedBy.chars
|
||||
<supybot> jemfinch|lambda: ''
|
||||
</ircsession>
|
||||
<para>
|
||||
Thus, to reset a configuration variable to its default
|
||||
value, you can simply say:
|
||||
</para>
|
||||
<ircsession>
|
||||
<jemfinch|lambda> @config supybot.reply.whenAddressedBy.chars [config default
|
||||
supybot.reply.whenAddressedBy.chars]
|
||||
<supybot> jemfinch|lambda: The operation succeeded.
|
||||
<jemfinch|lambda> @note that this does nothing
|
||||
</ircsession>
|
||||
<para>
|
||||
Simple, eh?
|
||||
</para>
|
||||
</sect4>
|
||||
</sect3>
|
||||
<sect3>
|
||||
<title>Searching the registry</title>
|
||||
<para>
|
||||
Now, let's say you want to find all configuration
|
||||
variables that might be even remotely related to opping.
|
||||
For that, you'll want the <botcommand>config
|
||||
search</botcommand> command. Check this out:
|
||||
</para>
|
||||
<ircsession>
|
||||
<jemfinch|lambda> @config search op
|
||||
<supybot> jemfinch|lambda:
|
||||
supybot.plugins.Enforcer.autoOp,
|
||||
supybot.plugins.Enforcer.autoHalfop,
|
||||
supybot.plugins.Enforcer.takeRevenge.onOps,
|
||||
supybot.plugins.Enforcer.cycleToGetOps,
|
||||
supybot.plugins.Topic, supybot.plugins.Topic.public,
|
||||
supybot.plugins.Topic.separator,
|
||||
supybot.plugins.Topic.format,
|
||||
supybot.plugins.Topic.recognizeTopiclen,
|
||||
supybot.plugins.Topic.default,
|
||||
supybot.plugins.Topic.undo.maz, and
|
||||
supybot.plugins.Relay.topicSync
|
||||
</ircsession>
|
||||
<para>
|
||||
Sure, it showed up all the topic-related stuff in there,
|
||||
but it also showed you all the op-related stuff, too. Do
|
||||
note, however, that you can only see configuration
|
||||
variables for plugins that you have loaded or that you
|
||||
loaded in the past; if you've never loaded a plugin,
|
||||
there's no way for the bot to know what configuration
|
||||
variables it registers.
|
||||
</para>
|
||||
<para>
|
||||
Some people might like editing their registry file
|
||||
directly rather than manipulating all these things through
|
||||
the bot. For those people, we offer the
|
||||
<botcommand>config reload</botcommand> command, which
|
||||
reloads both registry configuration and
|
||||
user/channel/ignore database configuration. Just edit the
|
||||
interesting files and then give the bot the
|
||||
<botcommand>config reload</botcommand> command and it'll
|
||||
work as expected. Do note, however, that Supybot flushes
|
||||
his configuration files and databases to disk every hour
|
||||
or so, and if this happens after you've edited your
|
||||
configuration files but before you reload your changes,
|
||||
you could lose the changes you made. To prevent this, set
|
||||
the <registrygroup>supybot.flush</registrygroup> value to
|
||||
<literal>Off</literal>, and no automatic flushing will
|
||||
occur.
|
||||
</para>
|
||||
</sect3>
|
||||
<sect3>
|
||||
<title>Channel-specific configuration</title>
|
||||
<para>
|
||||
Many configuration variables can be specific to individual
|
||||
channels. The <plugin>Config</plugin> plugin provides an
|
||||
easy way to configure something for a specific channel;
|
||||
for instance, in order to set the prefix chars for a
|
||||
specific channel, do this in that channel:
|
||||
</para>
|
||||
<ircsession>
|
||||
config channel supybot.reply.whenAddressedBy.chars !
|
||||
</ircsession>
|
||||
<para>
|
||||
That'll set the prefix chars in the channel that message
|
||||
is sent in to <literal>!</literal>. Voila,
|
||||
channel-specific values!
|
||||
</para>
|
||||
</sect3>
|
||||
</sect2>
|
||||
</sect1>
|
||||
<sect1>
|
||||
<title>All done!</title>
|
||||
<para>
|
||||
Anyway, that's about it for configuration. Have fun, and enjoy
|
||||
your configurable bot!
|
||||
</para>
|
||||
</sect1>
|
||||
</article>
|
||||
|
||||
|
@ -1,359 +0,0 @@
|
||||
<!DOCTYPE article SYSTEM "supybot.dtd">
|
||||
<article class="faq">
|
||||
<articleinfo>
|
||||
<authorgroup>
|
||||
<author>
|
||||
<firstname>Jeremiah</firstname>
|
||||
<surname>Fincher</surname>
|
||||
</author>
|
||||
<editor>
|
||||
<firstname>Daniel</firstname>
|
||||
<surname>DiPaolo</surname>
|
||||
<contrib>DocBook translator</contrib>
|
||||
</editor>
|
||||
</authorgroup>
|
||||
<title>Supybot Frequently Asked Questions</title>
|
||||
<revhistory>
|
||||
<revision>
|
||||
<revnumber>0.1</revnumber>
|
||||
<date>18 Feb 2004</date>
|
||||
<revremark>Initial Docbook translation</revremark>
|
||||
</revision>
|
||||
<revision>
|
||||
<revnumber>0.2</revnumber>
|
||||
<date>26 Feb 2004</date>
|
||||
<revremark>Changed to Supybot DTD</revremark>
|
||||
</revision>
|
||||
</revhistory>
|
||||
</articleinfo>
|
||||
<qandaset defaultlabel="qanda">
|
||||
<qandaentry>
|
||||
<question>
|
||||
<para>
|
||||
Why does my bot not recognize me or tell me that I don't
|
||||
have the <capability>owner</capability> capability?
|
||||
</para>
|
||||
</question>
|
||||
<answer>
|
||||
<para>
|
||||
Because you've not given it anything to recognize you
|
||||
from! You'll need to identify with the bot
|
||||
(<botcommand>help identify</botcommand> to see how that
|
||||
works) or add your hostmask to your user record
|
||||
(<botcommand>help addhostmask</botcommand> to see how that
|
||||
works) for it to know that you're you. You may wish to
|
||||
note that <botcommand>addhostmask</botcommand> can accept
|
||||
a password; rather than identify, you can send the command
|
||||
<botcommand>addhostmask myOwnerUser [hostmask]
|
||||
myOwnerUserPassword</botcommand> and the bot will add your
|
||||
current hostmask to your owner user (of course, you should
|
||||
change <literal>myOwnerUser</literal> and
|
||||
<literal>myOwnerUserPassword</literal> appropriately for
|
||||
your bot).
|
||||
</para>
|
||||
</answer>
|
||||
</qandaentry>
|
||||
<qandaentry>
|
||||
<question>
|
||||
<para>
|
||||
How do I make Supybot op my users?
|
||||
</para>
|
||||
</question>
|
||||
<answer>
|
||||
<para>
|
||||
First, you'll have to make sure that your users register
|
||||
with the bot. They can do this with the
|
||||
<botcommand>register</botcommand> command. After they do
|
||||
so, you'll want to add the
|
||||
<capability>#channel,op</capability> capability to their
|
||||
user. Use the <botcommand>channel
|
||||
addcapability</botcommand> command to do this. After
|
||||
that, your users should be able to use the
|
||||
<botcommand>op</botcommand> command to get ops.
|
||||
</para>
|
||||
<para>
|
||||
If you want your users to be auto-opped when they join the
|
||||
channel, you'll need to load the <plugin>Enforcer</plugin>
|
||||
plugin and turn its <registrygroup>autoOp</registrygroup>
|
||||
configuration variable on. Use the
|
||||
<botcommand>config</botcommand> command to do so. Here's
|
||||
an example of how to do these steps:
|
||||
</para>
|
||||
<ircsession>
|
||||
<jemfinch|lambda> I'm going to make an example session for giving
|
||||
you auto-ops, for our FAQ.
|
||||
<dunk1> ah ok ;]
|
||||
<jemfinch|lambda> First, I need you to register with supybot, using
|
||||
the "register" command (remember to send it in private).
|
||||
<dunk1> done
|
||||
<jemfinch|lambda> what name are you registered under?
|
||||
<dunk1> dunk1
|
||||
<jemfinch|lambda> ok, cool.
|
||||
<jemfinch|lambda> @channel addcapability dunk1 op
|
||||
<supybot> jemfinch|lambda: The operation succeeded.
|
||||
<jemfinch|lambda> now use the "op" command to get ops.
|
||||
<dunk1> @op
|
||||
— supybot gives channel operator status to dunk1
|
||||
<dunk1> works!
|
||||
<dunk1> ;]
|
||||
<jemfinch|lambda> @load Enforcer
|
||||
<supybot> jemfinch|lambda: The operation succeeded.
|
||||
<jemfinch|lambda> @config channel supybot.plugins.Enforcer.autoOp On
|
||||
<supybot> jemfinch|lambda: The operation succeeded.
|
||||
<jemfinch|lambda> ok, now cycle the channel (part and then rejoin)
|
||||
<– dunk1 (dunker@freebsd.nl) has left #supybot
|
||||
–> dunk1 (dunker@freebsd.nl) has joined #supybot
|
||||
— supybot gives channel operator status to dunk1
|
||||
<jemfinch|lambda> cool, thanks :)
|
||||
</ircsession>
|
||||
</answer>
|
||||
</qandaentry>
|
||||
<qandaentry>
|
||||
<question>
|
||||
<para>
|
||||
Can users with the <capability>admin</capability>
|
||||
capability change configuration variables?
|
||||
</para>
|
||||
</question>
|
||||
<answer>
|
||||
<para>
|
||||
Currently, no. Since this is the first release of Supybot
|
||||
that uses the registry, we wanted to stay on the
|
||||
conservative side and require the
|
||||
<capability>owner</capability> capability for changing all
|
||||
non-channel-related configuration variables. Feel free to
|
||||
make your case to us as to why a certain configuration
|
||||
variable should only require the
|
||||
<capability>admin</capability> capability instead of the
|
||||
<capability>owner</capability> capability, and if we agree
|
||||
with you, we'll change it for the next release.
|
||||
</para>
|
||||
</answer>
|
||||
</qandaentry>
|
||||
<qandaentry>
|
||||
<question>
|
||||
<para>
|
||||
Can Supybot do factoids?
|
||||
</para>
|
||||
</question>
|
||||
<answer>
|
||||
<para>
|
||||
Supybot most certainly can! In fact, we offer three
|
||||
full-fledged factoids-related plugins!
|
||||
</para>
|
||||
<para>
|
||||
<plugin>Factoids</plugin> (written by
|
||||
<nick>jemfinch</nick>) is Supybot's original
|
||||
factoids-related plugin. It offers full integration with
|
||||
Supybot's nested commands as well as a complete 1:n key to
|
||||
factoid ratio, with lookup by individual number. Factoids
|
||||
also uses a channel-specific database instead of a global
|
||||
database though that's configurable with the
|
||||
<registrygroup>supybot.databases.plugins.channelSpecific</registrygroup>
|
||||
configuration variable.
|
||||
</para>
|
||||
<para>
|
||||
<plugin>MoobotFactoids</plugin> (written by
|
||||
<nick>Strike</nick>) is much more full-featured, offering
|
||||
users the ability to define factoids in a slightly more
|
||||
user-friendly way, as well as parsing factoids to handle
|
||||
<reply>, <action>, and alternations (defining
|
||||
a factoid “test” as
|
||||
“<reply>(foo|bar|baz)” will make the bot
|
||||
send “foo” or “bar” or
|
||||
“baz” to the channel (without the normal
|
||||
“test is ” at the beginning)). If you're
|
||||
accustomed to Moobot's factoids or Blootbot's factoids,
|
||||
then this is the Factoids plugin for you. Unfortunately,
|
||||
due to the more natural definition syntax (required to be
|
||||
comaptible with Moobot) you can't define Factoids with
|
||||
nested commands; you'll have to evaluate the command first
|
||||
and then copy the result into your factoid definition.
|
||||
</para>
|
||||
<para>
|
||||
<plugin>Infobot</plugin> (written by
|
||||
<nick>jamessan</nick>) is used for Infobot compatibility;
|
||||
if you still want the basic functionality of Infobot, this
|
||||
is the plugin to use.
|
||||
</para>
|
||||
</answer>
|
||||
</qandaentry>
|
||||
<qandaentry>
|
||||
<question>
|
||||
<para>
|
||||
Can I import my Infobot/Blootbot/Moobot factoids into
|
||||
Supybot?
|
||||
</para>
|
||||
</question>
|
||||
<answer>
|
||||
<para>
|
||||
As of present, we have no automated way to do so.
|
||||
<nick>Strike</nick> has written a few scripts for
|
||||
importing a Moobot database into MoobotFactoids, however,
|
||||
so you'll want to talk to him about helping you with that.
|
||||
We're certainly happy to help you convert such databases;
|
||||
if you can provide us with such a database exported to a
|
||||
flat file, we can probably do the rest of the work to
|
||||
write a script that imports it into a database for one of
|
||||
our factoids-related plugins.
|
||||
</para>
|
||||
</answer>
|
||||
</qandaentry>
|
||||
<qandaentry>
|
||||
<question>
|
||||
<para>
|
||||
Do I really have to use separate databases for each
|
||||
channel?
|
||||
</para>
|
||||
</question>
|
||||
<answer>
|
||||
<para>
|
||||
Of course not! We default to separate databases for each
|
||||
channel because, well, that's what <nick>jemfinch</nick>
|
||||
always thought was reasonable. Anyway, if you change the
|
||||
configuration variable
|
||||
<registrygroup>supybot.databases.plugins.channelSpecific</registrygroup>
|
||||
to <literal>False</literal> instead of
|
||||
<literal>True</literal>, for <emphasis>most</emphasis>
|
||||
databases, each channel will share the same database (the
|
||||
exceptions are <plugin>ChannelStats</plugin>,
|
||||
<plugin>Herald</plugin>, <plugin>Seen</plugin>, and
|
||||
<plugin>WordStats</plugin>, which are inherently rather
|
||||
channel-based).
|
||||
</para>
|
||||
</answer>
|
||||
</qandaentry>
|
||||
<qandaentry>
|
||||
<question>
|
||||
<para>
|
||||
Karma doesn't seem to work for me.
|
||||
</para>
|
||||
</question>
|
||||
<answer>
|
||||
<para>
|
||||
<plugin>Karma</plugin> by default doesn't acknowledge
|
||||
karma updates. If you check the karma of whatever you
|
||||
increased/decreased, you'll note that your increment or
|
||||
decrement still took place. If you'd rather
|
||||
<plugin>Karma</plugin> acknowledge karma updates, change
|
||||
the
|
||||
<registrygroup>supybot.plugins.Karma.response</registrygroup>
|
||||
configuration variable to <literal>On</literal>.
|
||||
</para>
|
||||
</answer>
|
||||
</qandaentry>
|
||||
<qandaentry>
|
||||
<question>
|
||||
<para>
|
||||
I added an alias, but it doesn't work!
|
||||
</para>
|
||||
</question>
|
||||
<answer>
|
||||
<para>
|
||||
Take a look at <botcommand>help <alias you
|
||||
added></botcommand>. If the alias the bot has listed
|
||||
doesn't match what you're giving it, chances re you need
|
||||
to quote your alias in order for the brackets not to be
|
||||
evaluated. For instance, if you're adding an alias to
|
||||
give you a link to your homepage, you need to say:
|
||||
<para>
|
||||
<ircsession>
|
||||
alias add mylink "format concat http://myhost.com/
|
||||
[urlquote $1]"
|
||||
</ircsession>
|
||||
<para>
|
||||
and not:
|
||||
</para>
|
||||
<ircsession>
|
||||
alias add mylink format concat http://myhost.com/
|
||||
[urlquote $1]
|
||||
</ircsession>
|
||||
<para>
|
||||
The first version works; the second version will always
|
||||
return the same url.
|
||||
</para>
|
||||
</answer>
|
||||
</qandaentry>
|
||||
<qandaentry>
|
||||
<question>
|
||||
<para>
|
||||
Is there a command that can tell me what capability
|
||||
another command requires?
|
||||
</para>
|
||||
</question>
|
||||
<answer>
|
||||
<para>
|
||||
No, there isn't, and there probably never will be.
|
||||
Commands have the flexibility to check any capabilities
|
||||
they wish to check; while this flexibility is useful, it
|
||||
also makes it hard to guess what capability a certain
|
||||
command requires. We could make a solution that would
|
||||
work in a large majority of cases, but it wouldn't (and
|
||||
couldn't!) be absolutely correct in all circumstances, and
|
||||
since we're anal and we hate doing things halfway, we
|
||||
probably won't ever add this partial solution.
|
||||
</para>
|
||||
<para>
|
||||
Besides, is the error message so bad? If we did have such
|
||||
a command, many users would call the command, see that
|
||||
they could perform it, and then run the command, thus
|
||||
doubling the activity in the channel. Is that something
|
||||
you want?
|
||||
</para>
|
||||
</answer>
|
||||
</qandaentry>
|
||||
<qandaentry>
|
||||
<question>
|
||||
<para>
|
||||
How do I make my Supybot connect to multiple servers?
|
||||
</para>
|
||||
</question>
|
||||
<answer>
|
||||
<para>
|
||||
Just use the <botcommand>connect</botcommand> command in
|
||||
the <plugin>Owner</plugin> plugin. Easy as pie!
|
||||
</para>
|
||||
</answer>
|
||||
</qandaentry>
|
||||
<qandaentry>
|
||||
<question>
|
||||
<para>
|
||||
I found a bug, what do I do?
|
||||
</para>
|
||||
</question>
|
||||
<answer>
|
||||
<para>
|
||||
Submit it on Sourceforge through our Sourceforge project
|
||||
page:
|
||||
<ulink
|
||||
url="http://sourceforge.net/tracker/?group_id=58965&atid=489447">
|
||||
http://sourceforge.net/tracker/?group_id=58965&atid=489447
|
||||
</ulink>. If Sourceforge happens to be down when you try
|
||||
to submit your bug, then post it in the "Supybot Developer
|
||||
Discussion" forum at our forums at
|
||||
<ulink url="http://forums.supybot.org">
|
||||
http://forums.supybot.org/
|
||||
</ulink>. If that doesn't work, email
|
||||
<email>supybot-bugs@lists.sourceforge.net</email>. If
|
||||
that doesn't work, email
|
||||
<email>jemfinch@supybot.org</email>. If that doesn't
|
||||
work, find yourself some carrier pigeons and … hah!
|
||||
You thought I was serious!
|
||||
</para>
|
||||
<para>
|
||||
Anyway, when you submit your bug, we'll need several
|
||||
things. If the bug involved an uncaught exception, we
|
||||
need the traceback (basically the stuff from
|
||||
“Uncaught exception in …” to the next
|
||||
log entry). We'd also like to see the commands that
|
||||
caused the bug, or happened around the time you saw the
|
||||
bug. If the bug involved a database, we'd love to see the
|
||||
database. Remember, it's always worse to send us too
|
||||
little information in a bug report than too much.
|
||||
</para>
|
||||
</answer>
|
||||
</qandaentry>
|
||||
</qandaset>
|
||||
</article>
|
||||
|
||||
|
@ -1,297 +0,0 @@
|
||||
<!DOCTYPE article SYSTEM "supybot.dtd">
|
||||
|
||||
<article>
|
||||
<articleinfo>
|
||||
<authorgroup>
|
||||
<author>
|
||||
<firstname>Jeremiah</firstname>
|
||||
<surname>Fincher</surname>
|
||||
</author>
|
||||
<editor>
|
||||
<firstname>Daniel</firstname>
|
||||
<surname>DiPaolo</surname>
|
||||
<contrib>DocBook translator</contrib>
|
||||
</editor>
|
||||
</authorgroup>
|
||||
<title>Getting started with Supybot</title>
|
||||
<revhistory>
|
||||
<revision>
|
||||
<revnumber>0.1</revnumber>
|
||||
<date>18 Feb 2004</date>
|
||||
<revremark>Initial Docbook translation</revremark>
|
||||
</revision>
|
||||
</revhistory>
|
||||
</articleinfo>
|
||||
<sect1>
|
||||
<title>Introduction</title>
|
||||
<para>
|
||||
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 :)
|
||||
</para>
|
||||
<para>
|
||||
First things first: Supybot <emphasis>requires</emphasis> Python
|
||||
2.3. There ain't no getting around it. If you're a Python
|
||||
developer, you probably know how superior 2.3 is to previous
|
||||
incarnations. If you're not, just think about the difference
|
||||
between a bowl of plain vanilla ice cream and a banana split. Or
|
||||
something like that. Either way, <emphasis>we're</emphasis>
|
||||
Python developers and we like banana splits.
|
||||
</para>
|
||||
</sect1>
|
||||
<sect1>
|
||||
<title>Installing the bot and its utilities</title>
|
||||
<para>
|
||||
So what do you do? First thing you'll want to do is run (with
|
||||
root/admin privileges) <application>python setup.py
|
||||
install</application>. This will install Supybot globally. If
|
||||
you need to install locally for whatever reason, see this <ulink
|
||||
url="http://tinyurl.com/2tb37">forum post</ulink> on how to do so.
|
||||
You'll then have several new programs installed where Python
|
||||
scripts are normally installed on your system
|
||||
(<filename>/usr/bin</filename> or
|
||||
<filename>/usr/local/bin</filename> are common on UNIX systems;
|
||||
<filename>C:\Python23\Scripts</filename> is a common place on
|
||||
Windows; and (watch out, this is a long one :))
|
||||
<filename>/System/Library/Frameworks/Python.framework/Versions/2.3/bin</filename>
|
||||
is a common place on MacOS X.). The two that might be of
|
||||
particular interest to you, the new user, are
|
||||
<script>supybot</script> and
|
||||
<script>supybot-wizard</script> The former
|
||||
(<script>supybot</script> is the script to run an actual
|
||||
bot; the latter (<script>supybot-wizard</script> is an
|
||||
in-depth wizard that provides a nice user interface for creating
|
||||
configuration files for your bot. We'd prefer you to the use
|
||||
<script>supybot-wizard</script>, but if you're in a
|
||||
hurry or don't feel like being asked many questions, just run
|
||||
supybot with no arguments and it'll ask you only the questions
|
||||
necessary ")to run a bot.
|
||||
</para>
|
||||
</sect1>
|
||||
<sect1>
|
||||
<title>Firing up the bot for the first time</title>
|
||||
<para>
|
||||
So after running either of those two programs, you've got a nice
|
||||
registry file handy. If you're not satisfied with your answers
|
||||
to any of the questions you were asked, feel free to run the
|
||||
program again until you're satisfied with all your answers. Once
|
||||
you're satisfied, though, run the
|
||||
<script>supybot</script> program with the
|
||||
registry file you created as an argument. This will start the
|
||||
bot; unless you turned off logging to stdout, you'll see some nice
|
||||
log messages describing what the bot is doing at any particular
|
||||
moment; it may pause for a significant amount of time after saying
|
||||
"Connecting to ..." while the server tries to check its ident.
|
||||
</para>
|
||||
</sect1>
|
||||
<sect1>
|
||||
<title>Your first interactions with the bot</title>
|
||||
<para>
|
||||
Ok, so let's assume your bot connected to the server fine and
|
||||
joined the channels you told it to join. For now we'll assume you
|
||||
named your bot <nick>supybot</nick> (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>#channel</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:
|
||||
</para>
|
||||
<ircsession>
|
||||
supybot: list
|
||||
</ircsession>
|
||||
<para>
|
||||
Replacing <nick>supybot</nick> 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
|
||||
<plugin>Admin</plugin>, <plugin>Channel</plugin>,
|
||||
<plugin>Config</plugin>, <plugin>Misc</plugin>,
|
||||
<plugin>Owner</plugin>, and <plugin>User</plugin> should be
|
||||
there; if you used <script>supybot-wizard</script> to
|
||||
create your configuration file you may have many more plugins
|
||||
loaded. The <botcommand>list</botcommand> command can also be used to
|
||||
list the commands in a given plugin:
|
||||
</para>
|
||||
<ircsession>
|
||||
supybot: list Misc
|
||||
</ircsession>
|
||||
<para>
|
||||
Will list all the commands in the <plugin>Misc</plugin> plugin.
|
||||
</para>
|
||||
<sect2>
|
||||
<title>Accessing the bot's online help</title>
|
||||
<para>
|
||||
If you want to see the help for any command, just use
|
||||
the <botcommand>help</botcommand> command:
|
||||
</para>
|
||||
<ircsession>
|
||||
supybot: help help
|
||||
supybot: help list
|
||||
supybot: help load
|
||||
</ircsession>
|
||||
</sect2>
|
||||
<sect2>
|
||||
<title>Dealing with ambiguous commands</title>
|
||||
<para>
|
||||
Sometimes more than one plugin will have a given command; for
|
||||
instance, the <botcommand>list</botcommand> command exists in both
|
||||
the <plugin>Misc</plugin> and <plugin>Config</plugin>
|
||||
plugins (both loaded by default). <plugin>List</plugin>, in
|
||||
this case, defaults to the <plugin>Misc</plugin> plugin, but
|
||||
you may want to get the help for the
|
||||
<botcommand>list</botcommand>
|
||||
command in the <plugin>Config</plugin> plugin. In that
|
||||
case, you'll want to give your command like this:
|
||||
</para>
|
||||
<ircsession>
|
||||
supybot: help config list
|
||||
</ircsession>
|
||||
<para>
|
||||
Anytime your bot tells you that a given command is defined in
|
||||
several plugins, you'll want to use this syntax
|
||||
(<botcommand>plugin command</botcommand>) to disambiguate which
|
||||
plugin's command you wish to call. For instance, if you
|
||||
wanted to call the <plugin>Config</plugin> plugin's
|
||||
<botcommand>list</botcommand> command, then you'd need to say:
|
||||
</para>
|
||||
<ircsession>
|
||||
supybot: config list
|
||||
</ircsession>
|
||||
<para>
|
||||
Rather than just <botcommand>list</botcommand>.
|
||||
</para>
|
||||
</sect2>
|
||||
<sect2>
|
||||
<title>Loading plugins</title>
|
||||
<para>
|
||||
Now that you know how to deal with plugins having commands
|
||||
with the same name, let's take a look at loading other
|
||||
plugins. If you didn't use
|
||||
<script>supybot-wizard</script>, though, you might
|
||||
do well to try it before playing around with loading plugins
|
||||
yourself: each plugin has its own
|
||||
<function>configure</function> function that the wizard uses
|
||||
to setup the appropriate registry entries if the plugin
|
||||
requires any.
|
||||
</para>
|
||||
<sect3>
|
||||
<title>Identifying yourself as the bot owner</title>
|
||||
<para>
|
||||
Now, if you do want to play around with loading plugins,
|
||||
you're going to need to have the
|
||||
<capability>owner</capability>
|
||||
capability. If you ran the wizard, then chances are you
|
||||
already added an owner user for yourself. If not,
|
||||
however, you can add one via the handy-dandy
|
||||
<script>supybot-adduser</script> script. You'll
|
||||
want to run it while the bot is not running (otherwise it
|
||||
could overwrite
|
||||
<script>supybot-adduser</script>'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 <capability>owner</capability> capability
|
||||
(without the quotes), restart the bot and you'll be ready
|
||||
to load some plugins!
|
||||
</para>
|
||||
<para>
|
||||
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:
|
||||
</para>
|
||||
<ircsession>
|
||||
help identify
|
||||
</ircsession>
|
||||
<para>
|
||||
And follow the instructions; the command you send will
|
||||
probably look like this, with your owner user and password
|
||||
replaced:
|
||||
</para>
|
||||
<ircsession>
|
||||
identify myowneruser myuserpassword
|
||||
</ircsession>
|
||||
<para>
|
||||
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 <plugin>Owner</plugin> and <plugin>Admin</plugin>
|
||||
plugins, which you may want to take a look at (using the
|
||||
<botcommand>list</botcommand> and
|
||||
<botcommand>help</botcommand>
|
||||
commands, of course). One command in particular that you
|
||||
might want to use (it's from the <plugin>User</plugin>
|
||||
plugin) is the <botcommand>addhostmask</botcommand> command: it
|
||||
lets you add a hostmask to your user record so the bot
|
||||
recognizes you by your hostmask instead of requiring you
|
||||
to always identify with it before it recognizes you. Use
|
||||
the <botcommand>help</botcommand> command to see how this
|
||||
command works. Here's how I often use it:
|
||||
</para>
|
||||
<ircsession>
|
||||
addhostmask myuser [hostmask] mypassword
|
||||
</ircsession>
|
||||
<para>
|
||||
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
|
||||
<plugin>Misc</plugin> 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
|
||||
<literal>mypassword</literal> if I'm not already
|
||||
identified with the bot.
|
||||
</para>
|
||||
</sect3>
|
||||
</sect2>
|
||||
<sect2>
|
||||
<title>The <botcommand>more</botcommand> command</title>
|
||||
<para>
|
||||
Another command you might find yourself needing somewhat often
|
||||
is the <botcommand>more</botcommand> 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 listing of
|
||||
configuration groups for the bot (more on this in the
|
||||
CONFIGURATION document) by giving the command "config list
|
||||
supybot". Last I checked, it'll overflow into a second chunk.
|
||||
When you invoke this command, you should see output like:
|
||||
</para>
|
||||
<ircsession>
|
||||
<supybot> nick, ident, user, server, password, channels, prefixChars,
|
||||
defaultCapabilities, defaultAllow, defaultIgnore,
|
||||
humanTimestampFormat, externalIP, bracketSyntax, pipeSyntax,
|
||||
followIdentificationThroughNickChanges, alwaysJoinOnInvite,
|
||||
showSimpleSyntax, maxHistoryLength, nickmods, throttleTime,
|
||||
snarfThrottle, threadAllCommands, pingServer, pingInterval,
|
||||
upkeepInterval, flush, (1 more message)
|
||||
</ircsession>
|
||||
<para>
|
||||
Now, to see the rest of the output, simply give the command
|
||||
<botcommand>more</botcommand>, and it will show you the rest:
|
||||
</para>
|
||||
<ircsession>
|
||||
<jemfinch> more
|
||||
<supybot> httpPeekSize, and defaultSocketTimeout
|
||||
</ircsession>
|
||||
</sect2>
|
||||
</sect1>
|
||||
<sect1>
|
||||
<title>You're ready!</title>
|
||||
<para>
|
||||
You should now have a solid foundation for using Supybot. Be sure
|
||||
to check the help that is built-in to the bot itself if you have
|
||||
any questions, and enjoy using Supybot!
|
||||
</para>
|
||||
</sect1>
|
||||
</article>
|
||||
|
||||
|
@ -1,585 +0,0 @@
|
||||
<!DOCTYPE article SYSTEM "supybot.dtd">
|
||||
|
||||
<article>
|
||||
<articleinfo>
|
||||
<authorgroup>
|
||||
<author>
|
||||
<firstname>Jeremiah</firstname>
|
||||
<surname>Fincher</surname>
|
||||
</author>
|
||||
<editor>
|
||||
<firstname>Daniel</firstname>
|
||||
<surname>DiPaolo</surname>
|
||||
<contrib>DocBook translator</contrib>
|
||||
</editor>
|
||||
</authorgroup>
|
||||
<title>Supybot developer interfaces</title>
|
||||
<revhistory>
|
||||
<revision>
|
||||
<revnumber>0.1</revnumber>
|
||||
<date>19 Feb 2004</date>
|
||||
<revremark>Initial Docbook translation</revremark>
|
||||
</revision>
|
||||
<revision>
|
||||
<revnumber>0.2</revnumber>
|
||||
<date>26 Feb 2004</date>
|
||||
<revremark>Converted to Supybot DTD</revremark>
|
||||
</revision>
|
||||
</revhistory>
|
||||
</articleinfo>
|
||||
<sect1>
|
||||
<title>Available interfaces</title>
|
||||
<para>
|
||||
These are the interfaces for some of the objects you'll deal with
|
||||
if you code for Supybot.
|
||||
</para>
|
||||
<sect2>
|
||||
<title><classname>ircmsgs.IrcMsg</classname>
|
||||
<para>
|
||||
This is the object that represents an IRC message. It has
|
||||
several methods and attributes. The most important thing
|
||||
about this class, however, is that it <emphasis>is</emphasis>
|
||||
hashable, and thus <emphasis>cannot</emphasis> be modified.
|
||||
Do not change any attributes; any code that modifies an IRC
|
||||
message is <emphasis>broken</emphasis> and should not exist.
|
||||
</para>
|
||||
<variablelist>
|
||||
<title>Interesting methods</title>
|
||||
<varlistentry>
|
||||
<term>__init__</term>
|
||||
<listitem>
|
||||
<para>
|
||||
One of the more complex initializers in a class.
|
||||
It can be used in three different ways:
|
||||
</para>
|
||||
<orderedlist numeration="arabic" spacing="normal">
|
||||
<listitem>
|
||||
<para>
|
||||
It can be given a string, as one received
|
||||
from the server, which it will then parse
|
||||
into its separate components and
|
||||
instantiate the class with those
|
||||
components as attributes.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
It can be given a command, some (optional)
|
||||
arguments, and a (optional) prefix, and
|
||||
will instantiate the class with those
|
||||
components as attributes.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
It can be given, in addition to any of the
|
||||
above arguments, a <varname>msg</varname>
|
||||
keyword argument that will use the
|
||||
attributes of msg as defaults. This
|
||||
exists to make it easier to copy messages,
|
||||
since the class is immutable.
|
||||
</para>
|
||||
</listitem>
|
||||
</orderedlist>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>__str__</term>
|
||||
<listitem>
|
||||
<para>
|
||||
This returns the message in a string form suitable
|
||||
for sending to a server.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>__repr__</term>
|
||||
<listitem>
|
||||
<para>
|
||||
This returns the message in a form suitable for
|
||||
<function>eval()</function>, assuming the name
|
||||
<varname>IrcMsg</varname> is in your namespace and
|
||||
is bound to this class.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
<para>
|
||||
The following attributes are the meat of this class. These
|
||||
are generally what you'll be looking at with
|
||||
<varname>IrcMsg</varname>s.
|
||||
</para>
|
||||
<variablelist>
|
||||
<title>Interesting attributes</title>
|
||||
<varlistentry>
|
||||
<term>command</term>
|
||||
<listitem>
|
||||
<para>
|
||||
This is the command of the
|
||||
<varname>IrcMsg</varname> –
|
||||
<literal>PRIVMSG</literal>,
|
||||
<literal>NOTICE</literal>,
|
||||
<literal>WHOIS</literal>,
|
||||
etc.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>args</term>
|
||||
<listitem>
|
||||
<para>
|
||||
This is a tuple of the arguments to the
|
||||
<varname>IrcMsg</varname>. Some messages have
|
||||
arguments, some don't, depending on what command
|
||||
they are. You are, of course, always assured that
|
||||
<varname>args</varname> exists and is a tuple,
|
||||
though it might be empty.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>prefix</term>
|
||||
<listitem>
|
||||
<para>
|
||||
This is the hostmask of the person/server the
|
||||
message is from. In general, you won't be setting
|
||||
this on your outgoing messages, but incoming
|
||||
messages will always have one. This is the whole
|
||||
hostmask; if the message was received from a
|
||||
server, it'll be the server's hostmask; if the
|
||||
message was received from a user, it'll be the
|
||||
whole user hostmask. In that case, however, it's
|
||||
also parsed out into the
|
||||
<varname>nick</varname>/<varname>user</varname>/<varname>host</varname>
|
||||
attributes, which are probably more useful to
|
||||
check for many purposes.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>nick</term>
|
||||
<listitem>
|
||||
<para>
|
||||
If the message was sent by a user, this will be
|
||||
the nick of the user. If it was sent by a server,
|
||||
this will be the server's name (something like
|
||||
<literal>calvino.freenode.net</literal> or
|
||||
similar).
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>user</term>
|
||||
<listitem>
|
||||
<para>
|
||||
If the message was sent by a user, this will be
|
||||
the user string of the user – what they put
|
||||
into their IRC client for their "full name." If
|
||||
it was sent by a server, it'll be the server's
|
||||
name, again.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>host</term>
|
||||
<listitem>
|
||||
<para>
|
||||
If the message was sent by a user, this will be
|
||||
the host portion of their hostmask. If it was
|
||||
sent by a server, it'll be the server's name (yet
|
||||
again :))
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</sect2>
|
||||
<sect2>
|
||||
<title><classname>irclib.Irc</classname>
|
||||
<para>
|
||||
This is the object to handle everything about IRC except the
|
||||
actual connection to the server itself.
|
||||
(<emphasis>NOTE</emphasis> that the object actually received
|
||||
by commands in subclasses of
|
||||
<classname>callbacks.Privmsg</classname> is an
|
||||
<classname>IrcObjectProxy</classname>, which is described
|
||||
later. It augments the following interface with several
|
||||
methods of its own to help plugin authors.)
|
||||
</para>
|
||||
<variablelist>
|
||||
<title>Interesting methods</title>
|
||||
<varlistentry>
|
||||
<term>queueMsg</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Queues a message for sending to the server. The
|
||||
queue is generally FIFO, but it does prioritize
|
||||
messages based on their command.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>sendMsg</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Queues a message for sending to the server prior
|
||||
to any messages in the normal queue. This is
|
||||
exactly a FIFO queue, no reordering is done at
|
||||
all.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<!--<note>
|
||||
<para>
|
||||
The following two methods are the most important for
|
||||
people writing new <varname>IrcDriver</varname>s.
|
||||
Otherwise, you really don't need to pay attention to
|
||||
them.
|
||||
</para>
|
||||
</note>-->
|
||||
<varlistentry>
|
||||
<term>feedMsg</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Feeds the <varname>Irc</varname> object a message
|
||||
for it handle appropriately, as well as passing it
|
||||
on to callbacks.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>takeMsg</term>
|
||||
<listitem>
|
||||
<para>
|
||||
If the <varname>Irc</varname> object has a message
|
||||
it's ready to send to the server, this will return
|
||||
it. Otherwise, it will return
|
||||
<literal>None</literal>.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<!--<note>
|
||||
<para>
|
||||
The next several methods are of far more marginal
|
||||
utility. But someone may need them, so they're
|
||||
documented here.
|
||||
</para>
|
||||
</note>-->
|
||||
<varlistentry>
|
||||
<term>addCallback</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Takes a callback to add to the list of callbacks
|
||||
in the <varname>Irc</varname> object. See the
|
||||
interface for <varname>IrcCallback</varname> for
|
||||
more information.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>getCallback</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Gets a callback by name, if it is in the
|
||||
<varname>Irc</varname> object's list of callbacks.
|
||||
If it it isn't, returns <literal>None</literal>.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>removeCallback</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Removes a callback by name. Returns a list of the
|
||||
callbacks removed (since it is technically
|
||||
possible to have multiple callbacks with the same
|
||||
name. This list may be empty.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>__init__</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Requires a <varname>nick</varname>. Optional
|
||||
arguments include <varname>user</varname> and
|
||||
<varname>ident</varname>, which default to the
|
||||
nick given, <varname>password</varname>, which
|
||||
defaults to the empty password, and
|
||||
<varname>callbacks</varname>, a list of callbacks
|
||||
(which defaults to nothing, an empty list).
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>reset</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Resets the <varname>Irc</varname> object to its
|
||||
original state, as well as sends a
|
||||
<function>reset()</function> to every callbacks.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>die</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Kills the IRC object and all its callbacks.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
<variablelist>
|
||||
<title>Interesting attributes</title>
|
||||
<varlistentry>
|
||||
<term>nick</term>
|
||||
<listitem>
|
||||
<para>
|
||||
The current nick of the bot.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>prefix</term>
|
||||
<listitem>
|
||||
<para>
|
||||
The current prefix of the bot.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>server</term>
|
||||
<listitem>
|
||||
<para>
|
||||
The current server the bot is connected to.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>network</term>
|
||||
<listitem>
|
||||
<para>
|
||||
The current network name the bot is connected to.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>afterConnect</term>
|
||||
<listitem>
|
||||
<para>
|
||||
<literal>False</literal> until the bot has
|
||||
received a command sent after the connection is
|
||||
finished – 376, 377, or 422.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>state</term>
|
||||
<listitem>
|
||||
<para>
|
||||
An <varname>IrcState</varname> object for this
|
||||
particular connection. See the interface for the
|
||||
<varname>IrcState</varname> object for more
|
||||
information.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</sect2>
|
||||
<sect2>
|
||||
<title><classname>irclib.IrcCallback</classname></title>
|
||||
<variablelist>
|
||||
<title>Interesting Methods</title>
|
||||
<varlistentry>
|
||||
<term>name</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Returns the name of the callback. The default
|
||||
implementation simply returns the name of the
|
||||
class.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>__call__</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Called by the <varname>Irc</varname> object with
|
||||
itself and the message whenever a message is fed
|
||||
to the <varname>Irc</varname> object. Nothing is
|
||||
done with the return value.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>inFilter</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Called by the <varname>Irc</varname> object with
|
||||
itself and the message whenever a message is fed
|
||||
to the <varname>Irc</varname> object. The return
|
||||
value should be an <varname>IrcMsg</varname>
|
||||
object to be passed to the next callback in the
|
||||
<varname>Irc</varname>'s list of callbacks. If
|
||||
<literal>None</literal> is returned, all
|
||||
processing stops. This gives callbacks an
|
||||
oppurtunity to "filter" incoming messages before
|
||||
general callbacks are given them.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>outFilter</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Basically equivalent to
|
||||
<varname>inFilter</varname>, except instead of
|
||||
being called on messages as they enter the
|
||||
<varname>Irc</varname> object, it's called on
|
||||
messages as they leave the <varname>Irc</varname>
|
||||
object.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>die</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Called when the parent <varname>Irc</varname> is
|
||||
told to die. This gives callbacks an oppurtunity
|
||||
to close open files, network connections, or
|
||||
databases before they're deleted.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>reset</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Called when the parent <varname>Irc</varname> is
|
||||
told to reset (which is generally when
|
||||
reconnecting to the server). Most callbacks don't
|
||||
need to define this.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
<variablelist>
|
||||
<title>Interesting attributes</title>
|
||||
<varlistentry>
|
||||
<term>priority</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Determines the priority of the callback in the
|
||||
<varname>Irc</varname> object's list of callbacks.
|
||||
Defaults to <literal>99</literal>; the valid range
|
||||
includes <literal>0</literal> through
|
||||
<literal>sys.maxint-1</literal> (don't use
|
||||
<literal>sys.maxint</literal> itself, that's
|
||||
reserved for the <varname>Misc</varname> plugin).
|
||||
The lower the number, the higher the priority.
|
||||
High priority callbacks are called earlier in the
|
||||
<varname>inFilter</varname> cycle, earlier in the
|
||||
<varname>__call__</varname> cycle, and later in
|
||||
the <varname>outFilter</varname> cycle –
|
||||
basically, they're given the first chances on the
|
||||
way in and the last chances on the way out.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</sect2>
|
||||
<sect2>
|
||||
<title><classname>callbacks.IrcObjectProxy</classname></title>
|
||||
<para>
|
||||
<classname>IrcObjectProxy</classname> is a proxy for an
|
||||
<classname>irclib.Irc</classname> instance that serves to
|
||||
provide a much fuller interface for handling replies and
|
||||
errors as well as to handle the nesting of commands. This is
|
||||
what you'll be dealing with almost all the time when writing
|
||||
commands; when writing <function>doCommand</function> methods
|
||||
(the kind you read about in the interface description of
|
||||
<classname>irclib.IrcCallback</classname>) you'll be dealing
|
||||
with plain old <classname>irclib.Irc</classname> objects.
|
||||
</para>
|
||||
<variablelist>
|
||||
<title>Interesting methods</title>
|
||||
<varlistentry>
|
||||
<term>reply</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Called to reply to the current message with a
|
||||
string that is to be the reply. Uses the
|
||||
<function>queueMsg</function> command discussed in
|
||||
the <classname>irclib.Irc</classname> section.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>replySuccess</term>
|
||||
<term>replyError</term>
|
||||
<listitem>
|
||||
<para>
|
||||
These reply with the configured responses for
|
||||
success and generic error, respectively. If an
|
||||
additional argument is given, it's (intelligently)
|
||||
appended to the generic message to be more
|
||||
specific.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>error</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Called to send an error reply to the current
|
||||
message; not only does the response indicate an
|
||||
error, but commands that error out break the
|
||||
nested-command chain, which is generally useful
|
||||
for not confusing the user :)
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>errorNoCapability</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Like <function>error</function>, except it accepts
|
||||
the capability that's missing and integrates it
|
||||
into the configured error message for such things.
|
||||
Also accepts an additional string for a more
|
||||
descriptive message, if that's what you want.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>errorPossibleBug</term>
|
||||
<term>errorNotRegistered</term>
|
||||
<term>errorNoUser</term>
|
||||
<term>errorRequiresPrivacy</term>
|
||||
<listitem>
|
||||
<para>
|
||||
These methods reply with the appropriate
|
||||
configured error message for the conditions in
|
||||
their names; they all take an additional arguments
|
||||
to be more specific about the conditions they
|
||||
indicate, but this argument is very rarely
|
||||
necessary.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>getRealIrc</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Returns the actual <classname>Irc</classname>
|
||||
object being proxied for.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</sect2>
|
||||
</sect1>
|
||||
</article>
|
||||
|
||||
|
@ -1,633 +0,0 @@
|
||||
<!DOCTYPE article SYSTEM "supybot.dtd">
|
||||
|
||||
<article>
|
||||
<articleinfo>
|
||||
<authorgroup>
|
||||
<author>
|
||||
<firstname>Jeremiah</firstname>
|
||||
<surname>Fincher</surname>
|
||||
</author>
|
||||
<editor>
|
||||
<firstname>Daniel</firstname>
|
||||
<surname>DiPaolo</surname>
|
||||
<contrib>DocBook translator</contrib>
|
||||
</editor>
|
||||
</authorgroup>
|
||||
<title>Supybot plugin author example</title>
|
||||
<revhistory>
|
||||
<revision>
|
||||
<revnumber>0.1</revnumber>
|
||||
<date>13 Sep 2003</date>
|
||||
<revremark>Initial revision</revremark>
|
||||
</revision>
|
||||
<revision>
|
||||
<revnumber>0.2</revnumber>
|
||||
<date>14 Sep 2003</date>
|
||||
<revremark>Converted to DocBook</revremark>
|
||||
</revision>
|
||||
<revision>
|
||||
<revnumber>0.3</revnumber>
|
||||
<date>24 Nov 2003</date>
|
||||
<revremark>
|
||||
Updated to match EXAMPLE included with 0.75.0
|
||||
</revremark>
|
||||
</revision>
|
||||
<revision>
|
||||
<revnumber>0.4</revnumber>
|
||||
<date>26 Feb 2004</date>
|
||||
<revremark>Converted to use Supybot DTD</revremark>
|
||||
</revision>
|
||||
<revision>
|
||||
<revnumber>0.5</revnumber>
|
||||
<date>4 Sep 2004</date>
|
||||
<revremark>Updated Docbook translation</revremark>
|
||||
</revision>
|
||||
</revhistory>
|
||||
</articleinfo>
|
||||
<sect1>
|
||||
<title>Introduction</title>
|
||||
<para>
|
||||
Ok, so you want to write a callback for supybot. Good, then this
|
||||
is the place to be. We're going to start from the top (the
|
||||
highest level, where supybot code does the most work for you) and
|
||||
move lower after that.
|
||||
</para>
|
||||
<para>
|
||||
So have you used supybot? If not, you need to go use it, get a
|
||||
feel for it, see how the various commands work and such.
|
||||
</para>
|
||||
<para>
|
||||
So now that we know you've used supybot, we'll start getting into
|
||||
details.
|
||||
</para>
|
||||
</sect1>
|
||||
<sect1>
|
||||
<title>Creating your own plugin</title>
|
||||
<sect2>
|
||||
<title>
|
||||
Using <script>scripts/newplugin.py</script>
|
||||
</title>
|
||||
<para>
|
||||
First, the easiest way to start writing a module is to use the
|
||||
wizard provided, <script>scripts/newplugin.py</script>.
|
||||
Here's an example session:
|
||||
</para>
|
||||
<screen>
|
||||
functor% scripts/newplugin.py
|
||||
What should the name of the plugin be? Random
|
||||
Supybot offers two major types of plugins: command-based and regexp-
|
||||
based. Command-based plugins are the kind of plugins you've seen most
|
||||
when you've used supybot. They're also the most featureful and
|
||||
easiest to write. Commands can be nested, for instance, whereas
|
||||
regexp-based callbacks can't do nesting. That doesn't mean that
|
||||
you'll never want regexp-based callbacks. They offer a flexibility
|
||||
that command-based callbacks don't offer; however, they don't tie into
|
||||
the whole system as well. If you need to combine a command-based
|
||||
callback with some regexp-based methods, you can do so by subclassing
|
||||
callbacks.PrivmsgCommandAndRegexp and then adding a class-level
|
||||
attribute "regexps" that is a sets.Set of methods that are regexp-
|
||||
based. But you'll have to do that yourself after this wizard is
|
||||
finished :)
|
||||
Do you want a command-based plugin or a regexp-based plugin? [command/
|
||||
regexp] command
|
||||
Sometimes you'll want a callback to be threaded. If its methods
|
||||
(command or regexp-based, either one) will take a signficant amount
|
||||
of time to run, you'll want to thread them so they don't block
|
||||
the entire bot.
|
||||
|
||||
Does your plugin need to be threaded? [y/n] n
|
||||
Your new plugin template is in plugins/Random.py
|
||||
functor%
|
||||
</screen>
|
||||
<para>
|
||||
So that's what it looks like. Now let's look at the source
|
||||
code (if you'd like to look at it in your programming editor,
|
||||
the whole plugin is available as
|
||||
<filename>examples/Random.py</filename>):
|
||||
</para>
|
||||
<programlisting>
|
||||
#!/usr/bin/env python
|
||||
|
||||
###
|
||||
# Copyright (c) 2004, Jeremiah Fincher
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions, and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions, and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
# * Neither the name of the author of this software nor the name of
|
||||
# contributors to this software may be used to endorse or promote products
|
||||
# derived from this software without specific prior written consent.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
###
|
||||
|
||||
"""
|
||||
Add the module docstring here. This will be used by the setup.py script.
|
||||
"""
|
||||
|
||||
__revision__ = "$Id$"
|
||||
__author__ = ''
|
||||
|
||||
import supybot.plugins as plugins
|
||||
|
||||
import supybot.conf as conf
|
||||
import supybot.utils as utils
|
||||
import supybot.privmsgs as privmsgs
|
||||
import supybot.callbacks as callbacks
|
||||
|
||||
|
||||
def configure(onStart, afterConnect, advanced):
|
||||
# This will be called by setup.py to configure this module. Advanced is
|
||||
# a bool that specifies whether the user identified himself as an advanced
|
||||
# user or not. You should effect your configuration by manipulating the
|
||||
# registry as appropriate.
|
||||
from questions import expect, anything, something, yn
|
||||
conf.registerPlugin('Random', True)
|
||||
|
||||
class Random(callbacks.Privmsg):
|
||||
pass
|
||||
|
||||
|
||||
Class = Random
|
||||
|
||||
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78:
|
||||
</programlisting>
|
||||
</sect2>
|
||||
<sect2>
|
||||
<title>Customizing the boilerplate code</title>
|
||||
<para>
|
||||
So a few notes, before we customize it.
|
||||
</para>
|
||||
<para>
|
||||
You'll probably want to change the copyright notice to be your
|
||||
name. It wouldn't stick even if you kept my name, so you
|
||||
might as well :)
|
||||
</para>
|
||||
<para>
|
||||
Describe what you want the plugin to do in the docstring.
|
||||
This is used in <script>scripts/setup.py</script> in
|
||||
order to explain to the user the purpose of the module. It's
|
||||
also returned when someone asks the bot for help for a given
|
||||
module (instead of help for a certain command). We'll change
|
||||
this one to <literal>"Lots of stuff relating to random
|
||||
numbers."</literal>
|
||||
</para>
|
||||
<para>
|
||||
Then there are the imports. The
|
||||
<module>callbacks</module>
|
||||
module is used (the class you're given subclasses
|
||||
<classname>callbacks.Privmsg</classname>) but the
|
||||
<module>privmsgs</module> module isn't used. That's
|
||||
alright; we can almost guarantee you'll use it, so we go ahead
|
||||
and add the import to the template.
|
||||
</para>
|
||||
<para>
|
||||
Then you see a <function>configure</function> function. This
|
||||
the function that's called when users decide to add your
|
||||
module in <script>scripts/setup.py</script>. You'll
|
||||
note that by default it simply registers the plugin to be
|
||||
automatically loaded on startup. For many
|
||||
plugins this is all you need; for more complex plugins, you
|
||||
might need to ask questions and add commands based on the
|
||||
answers.
|
||||
</para>
|
||||
</sect2>
|
||||
<sect2>
|
||||
<title>Digging in: customizing the plugin class</title>
|
||||
<para>
|
||||
Now comes the meat of the plugin: the plugin class.
|
||||
</para>
|
||||
<para>
|
||||
What you're given is a skeleton: a simple subclass of
|
||||
<classname>callbacks.Privmsg</classname> for you to start
|
||||
with. Now let's add a command.
|
||||
</para>
|
||||
<para>
|
||||
I don't know what you know about random number generators, but
|
||||
the short of it is that they start at a certain number (a
|
||||
seed) and they continue (via some somewhat
|
||||
complicated/unpredictable algorithm) from there. This seed
|
||||
(and the rest of the sequence, really) is all nice and
|
||||
packaged up in Python's <module>random</module> module, the
|
||||
<varname>Random</varname> object. So the first thing we're
|
||||
going to have to do is give our plugin a
|
||||
<varname>Random</varname> object.
|
||||
</para>
|
||||
<para>
|
||||
Normally, when we want to give instances of a class an object,
|
||||
we'll do so in the <function>__init__</function> method. And
|
||||
that works great for plugins, too. The one thing you have to
|
||||
be careful of is that you call the superclass
|
||||
<function>__init__</function> method at the end of your own
|
||||
<function>__init__</function>. So to add this
|
||||
<classname>random.Random</classname> object to our plugin, we
|
||||
can replace the <keyword>pass</keyword> statement with
|
||||
this:
|
||||
</para>
|
||||
<programlisting>
|
||||
def __init__(self):
|
||||
self.rng = random.Random()
|
||||
callbacks.Privmsg.__init__(self)
|
||||
</programlisting>
|
||||
<para>
|
||||
(<varname>rng</varname>is an abbreviation for "random number
|
||||
generator," in case you were curious)
|
||||
</para>
|
||||
<para>
|
||||
Do be careful not to give your <function>__init__</function>
|
||||
any arguments (other than <varname>self</varname>, of course).
|
||||
There's no way anything will ever get to them! If you have
|
||||
some sort of initial values you need to get to your plugin
|
||||
before it can do anything interesting, you should get those
|
||||
values from the registry.
|
||||
</para>
|
||||
<para>
|
||||
There's an easier way to get our plugin to have its own rng
|
||||
than to define an <function>__init__</function>. Plugins are
|
||||
unique among classes because we're always certain that there
|
||||
will only be one instance -- supybot doesn't allow us to load
|
||||
multiple instances of a single plugin. So instead of adding
|
||||
the rng in <function>__init__</function>, we can just add it
|
||||
as a attribute to the class itself. Like so (replacing the
|
||||
<function>pass</function> statement again):
|
||||
</para>
|
||||
<programlisting>
|
||||
rng = random.Random()
|
||||
</programlisting>
|
||||
<para>
|
||||
And we save two lines of code and make our code a little more
|
||||
clear :)
|
||||
</para>
|
||||
<para>
|
||||
Now that we have an RNG, we need some way to get random
|
||||
numbers. So first, we'll add a command that simply gets the
|
||||
next random number and gives it back to the user. It takes no
|
||||
arguments, of course (what would you give it?). Here's the
|
||||
command, and I'll follow that with the explanation of what
|
||||
each part means.
|
||||
</para>
|
||||
<programlisting>
|
||||
def random(self, irc, msg, args):
|
||||
"""takes no arguments
|
||||
|
||||
Returns the next random number generated by the random number
|
||||
generator.
|
||||
"""
|
||||
irc.reply(str(self.rng.random()))
|
||||
</programlisting>
|
||||
<para>
|
||||
And that's it! Pretty simple, huh? Anyway, you're probably
|
||||
wondering what all that <emphasis>means</emphasis>. We'll
|
||||
start with the <keyword>def</keyword> statement:
|
||||
</para>
|
||||
<programlisting>
|
||||
def random(self, irc, msg, args):
|
||||
</programlisting>
|
||||
<para>
|
||||
What that does is define a command
|
||||
<botcommand>random</botcommand>. You can call it by saying
|
||||
"@random" (or whatever prefix character your specific bot
|
||||
uses). The arguments are a bit less obvious.
|
||||
<varname>self</varname> is self-evident (hah!).
|
||||
<varname>irc</varname> is the <classname>Irc</classname>
|
||||
object passed to the command; <varname>msg</varname> is the
|
||||
original <classname>IrcMsg</classname> object. But you're
|
||||
really not going to have to deal with either of these too much
|
||||
(with the exception of calling <function>irc.reply</function>
|
||||
or <function>irc.error</function>). What you're
|
||||
<emphasis>really</emphasis> interested in is the
|
||||
<varname>args</varname> arg. That is a list of all the
|
||||
arguments passed to your command, pre-parsed and already
|
||||
evaluated (i.e., you never have to worry about nested
|
||||
commands, or handling double quoted strings, or splitting on
|
||||
whitespace – the work has already been done for you).
|
||||
You can read about the <classname>Irc</classname> object in
|
||||
<filename>irclib.py</filename> (you won't find
|
||||
<function>.reply</function> or <function>.error</function>
|
||||
there, though, because you're actually getting an
|
||||
<classname>IrcObjectProxy</classname>, but that's beyond the
|
||||
level we want to describe here :)). You can read about the
|
||||
<varname>msg</varname> object in
|
||||
<filename>ircmsgs.py</filename>. But again, you'll very
|
||||
rarely be using these objects.
|
||||
</para>
|
||||
<para>
|
||||
(In case you're curious, the answer is yes, you
|
||||
<emphasis>must</emphasis> name your arguments <varname>(self,
|
||||
irc, msg, args)</varname>. The names of those arguments is
|
||||
one of the ways that supybot uses to determine which methods
|
||||
in a plugin class are commands and which aren't. And while
|
||||
we're talking about naming restrictions, all your commands
|
||||
should be named in all-lowercase with no underscores. Before
|
||||
calling a command, supybot always converts the command name to
|
||||
lowercase and removes all dashes and underscores. On the
|
||||
other hand, you now know an easy way to make sure a method is
|
||||
never called (even if its arguments are <varname>(self, irc,
|
||||
msg, args)</varname>, however unlikely that may be). Just
|
||||
name it with an underscore or an uppercase letter in it :))
|
||||
</para>
|
||||
<para>
|
||||
You'll also note that the docstring is odd. The wonderful
|
||||
thing about the supybot framework is that it's easy to write
|
||||
complete commands with help and everything: the docstring
|
||||
<emphasis>is</emphasis> the help! Given the above docstring,
|
||||
this is what a supybot does:
|
||||
</para>
|
||||
<ircsession>
|
||||
<jemfinch> @help random
|
||||
<angryman> jemfinch: (random takes no arguments) -- Returns the
|
||||
next random number from the random number generator.
|
||||
</ircsession>
|
||||
<para>
|
||||
Now on to the actual body of the function:
|
||||
</para>
|
||||
<programlisting>
|
||||
irc.reply(msg, str(self.rng.random()))
|
||||
</programlisting>
|
||||
<para>
|
||||
<function>irc.reply</function> simply takes one simple
|
||||
argument: a string The string is the reply to be sent. Don't
|
||||
worry about length restrictions or anything
|
||||
– if the string you want to send is too big for an IRC
|
||||
message (and oftentimes that turns out to be the case :)) the
|
||||
Supybot framework handles that entirely transparently to you.
|
||||
Do make sure, however, that you give
|
||||
<function>irc.reply</function> a string. It doesn't take
|
||||
anything else (sometimes even unicode fails!). That's why we
|
||||
have "str(self.rng.random())" instead of simply
|
||||
"self.rng.random()" – we had to give
|
||||
<function>irc.reply</function> a string.
|
||||
</para>
|
||||
<para>
|
||||
Anyway, now that we have an RNG, we have a need for seed! Of
|
||||
course, Python gives us a good seed already (it uses the
|
||||
current time as a seed if we don't give it one) but users
|
||||
might want to be able to repeat "random" sequences, so letting
|
||||
them set the seed is a good thing. So we'll add a seed
|
||||
command to give the RNG a specific seed:
|
||||
</para>
|
||||
<programlisting>
|
||||
def seed(self, irc, msg, args):
|
||||
"""<seed>
|
||||
|
||||
Sets the seed of the random number generator. <seed> must be
|
||||
an int or a long.
|
||||
"""
|
||||
seed = privmsgs.getArgs(args)
|
||||
try:
|
||||
seed = long(seed)
|
||||
except ValueError:
|
||||
# It wasn't a valid long!
|
||||
irc.error(msg, '<seed> must be a valid int or long.')
|
||||
return
|
||||
self.rng.seed(seed)
|
||||
irc.replySuccess()
|
||||
</programlisting>
|
||||
<para>
|
||||
So this one's a bit more complicated. But it's still pretty
|
||||
simple. The method name is <botcommand>seed</botcommand> so
|
||||
that'll be the command name. The arguments are the same, the
|
||||
docstring is of the same form, so we don't need to go over
|
||||
that again. The body of the function, however, is
|
||||
significantly different.
|
||||
</para>
|
||||
<para>
|
||||
<function>privmsgs.getArgs</function> is a function you're
|
||||
going to be seeing a lot of when you write plugins for
|
||||
supybot. What it does is basically give you the right number
|
||||
of arguments for your comamnd. In this case, we want one
|
||||
argument. But we might have been given any number of
|
||||
arguments by the user. So
|
||||
<function>privmsgs.getArgs</function> joins them
|
||||
appropriately, leaving us with one single "seed" argument (by
|
||||
default, it returns one argument as a single value; more
|
||||
arguments are returned in a tuple/list). Yes, we could've
|
||||
just said "seed = args[0]" and gotten the first argument, but
|
||||
what if the user didn't pass us an argument at all? Then
|
||||
we've got to catch the <classname>IndexError</classname> from
|
||||
<varname>args[0]</varname> and complain to the user about it.
|
||||
<function>privmsgs.getArgs</function>, on the other hand,
|
||||
handles all that for us. If the user didn't give us enough
|
||||
arguments, it'll reply with the help string for the command,
|
||||
thus saving us the effort.
|
||||
</para>
|
||||
<para>
|
||||
So we have the seed from
|
||||
<function>privmsgs.getArgs</function>. But it's a string.
|
||||
The next three lines is pretty darn obvious: we're just
|
||||
converting the string to a int of some sort. But if it's not,
|
||||
that's when we're going to call
|
||||
<function>irc.error</function>. It has the same interface as
|
||||
we saw before in <function>irc.reply</function>, but it makes
|
||||
sure to remind the user that an error has been encountered
|
||||
(currently, that means it puts <literal>"Error: "</literal> at
|
||||
the beginning of the message). After erroring, we return.
|
||||
It's important to remember this <keyword>return</keyword>
|
||||
here; otherwise, we'll just keep going down through the
|
||||
function and try to use this <varname>seed</varname> variable
|
||||
that never got assigned. A good general rule of thumb is that
|
||||
any time you use <function>irc.error</function>, you'll want
|
||||
to return immediately afterwards.
|
||||
</para>
|
||||
<para>
|
||||
Then we set the seed – that's a simple function on our
|
||||
rng object. Assuming that succeeds (and doesn't raise an
|
||||
exception, which it shouldn't, because we already read the
|
||||
documentation and know that it should work) we reply to say
|
||||
that everything worked fine. That's what
|
||||
<function>irc.replySuccess</function> says. By default, it
|
||||
has the very dry (and appropriately robot-like) "The operation
|
||||
succeeded." but you're perfectly welcome to customize it
|
||||
yourself – the registry was written to be modified!
|
||||
</para>
|
||||
<para>
|
||||
So that's a bit more complicated command. But we still
|
||||
haven't dealt with multiple arguments. Let's do that
|
||||
next.
|
||||
</para>
|
||||
<para>
|
||||
So these random numbers are useful, but they're not the kind
|
||||
of random numbers we usually want in Real Life. In Real Life,
|
||||
we like to tell someone to "pick a number between 1 and 10."
|
||||
So let's write a function that does that. Of course, we won't
|
||||
hardcode the 1 or the 10 into the function, but we'll take
|
||||
them as arguments. First the function:
|
||||
</para>
|
||||
<programlisting>
|
||||
def range(self, irc, msg, args):
|
||||
"""<start> <end>
|
||||
|
||||
Returns a number between <start> and <end>, inclusive (i.e., the number
|
||||
can be either of the endpoints.
|
||||
"""
|
||||
(start, end) = privmsgs.getArgs(args, required=2)
|
||||
try:
|
||||
end = int(end)
|
||||
start = int(start)
|
||||
except ValueError:
|
||||
irc.error(msg, '<start> and <end> must both be integers.')
|
||||
return
|
||||
# .randrange() doesn't include the endpoint, so we use end+1.
|
||||
irc.reply(msg, str(self.rng.randrange(start, end+1)))
|
||||
</programlisting>
|
||||
<para>
|
||||
Pretty simple. This is becoming old hat by now. The only new
|
||||
thing here is the call to
|
||||
<function>privmsgs.getArgs</function>. We have to make sure,
|
||||
since we want two values, to pass a keyword parameter
|
||||
"required" into <function>privmsgs.getArgs</function>. Of
|
||||
course, <function>privmsgs.getArgs</function> handles all the
|
||||
checking for missing arguments and whatnot so we don't have
|
||||
to.
|
||||
</para>
|
||||
<para>
|
||||
The <classname>Random</classname> object we're using offers us
|
||||
a "sample" method that takes a sequence and a number (we'll
|
||||
call it <varname>N</varname>) and returns a list of
|
||||
<varname>N</varname> items taken randomly from the sequence.
|
||||
So I'll show you an example that takes advantage of multiple
|
||||
arguments but doesn't use
|
||||
<function>privmsgs.getArgs</function> (and thus has to handle
|
||||
its own errors if the number of arguments isn't right).
|
||||
Here's the code:
|
||||
</para>
|
||||
<programlisting>
|
||||
def sample(self, irc, msg, args):
|
||||
"""<number of items> [<text> ...]
|
||||
|
||||
Returns a sample of the <number of items> taken from the remaining
|
||||
arguments. Obviously <number of items> must be less than the number
|
||||
of arguments given.
|
||||
"""
|
||||
try:
|
||||
n = int(args.pop(0))
|
||||
except IndexError: # raised by .pop(0)
|
||||
raise callbacks.ArgumentError
|
||||
except ValueError:
|
||||
irc.error('<number of items> must be an integer.')
|
||||
return
|
||||
if n > len(args):
|
||||
irc.error('<number of items> must be less than the number '
|
||||
'of arguments.')
|
||||
return
|
||||
sample = self.rng.sample(args, n)
|
||||
irc.reply(utils.commaAndify(map(repr, sample)))
|
||||
</programlisting>
|
||||
<para>
|
||||
Most everything here is familiar. The difference between this
|
||||
and the previous examples is that we're dealing with
|
||||
<varname>args</varname> directly, rather than through
|
||||
<function>getArgs</function>. Since we already have the
|
||||
arguments in a list, it doesn't make any sense to have
|
||||
<function>privmsgs.getArgs</function> smush them all together
|
||||
into a big long string that we'll just have to re-split. But
|
||||
we still want the nice error handling of
|
||||
<function>privmsgs.getArgs</function>. So what do we do? We
|
||||
raise <classname>callbacks.ArgumentError</classname>! That's
|
||||
the secret juju that <function>privmsgs.getArgs</function> is
|
||||
doing; now we're just doing it ourself. Someone up our
|
||||
callchain knows how to handle it so a neat error message is
|
||||
returned. So in this function, if
|
||||
<function>.pop(0)</function> fails, we weren't given enough
|
||||
arguments and thus need to tell the user how to call us.
|
||||
</para>
|
||||
<para>
|
||||
So we have the args, we have the number, we do a simple call
|
||||
to <function>random.sample</function> and then we do this
|
||||
funky <function>utils.commaAndify</function> to it. Yeah, so
|
||||
I was running low on useful names :) Anyway, what it does is
|
||||
take a list of strings and return a string with them joined by
|
||||
a comma, the last one being joined with a comma and "and". So
|
||||
the list ['foo', 'bar', 'baz'] becomes "foo, bar, and baz".
|
||||
It's pretty useful for showing the user lists in a useful
|
||||
form. We map the strings with <function>repr()</function>
|
||||
first just to surround them with quotes.
|
||||
</para>
|
||||
<para>
|
||||
So we have one more example. Yes, I hear your groans, but
|
||||
it's pedagogically useful :) This time we're going to write a
|
||||
command that makes the bot roll a die. It'll take one
|
||||
argument (the number of sides on the die) and will respond
|
||||
with the equivalent of "/me rolls a __" where __ is the number
|
||||
the bot rolled. So here's the code:
|
||||
</para>
|
||||
<programlisting>
|
||||
def diceroll(self, irc, msg, args):
|
||||
"""[<number of sides>]
|
||||
|
||||
Rolls a die with <number of sides> sides. The default number
|
||||
of sides is 6.
|
||||
"""
|
||||
try:
|
||||
n = privmsgs.getArgs(args, required=0, optional=1)
|
||||
if not n:
|
||||
n = 6
|
||||
n = int(n)
|
||||
except ValueError:
|
||||
irc.error(msg, 'Dice have integer numbers of sides. Use one.')
|
||||
return
|
||||
s = 'rolls a %s' % self.rng.randrange(1, n+1)
|
||||
irc.reply(s, action=True)
|
||||
</programlisting>
|
||||
<para>
|
||||
There's a lot of stuff you haven't seen before in there. The
|
||||
most important, though, is the first thing you'll notice
|
||||
that's different: the <function>privmsg.getArgs</function>
|
||||
call. Here we're offering a default argument in case the user
|
||||
is too lazy to supply one (or just wants a nice, standard
|
||||
six-sided die :)) <function>privmsgs.getArgs</function>
|
||||
supports that; we'll just tell it that we don't
|
||||
<emphasis>need</emphasis> any arguments (via
|
||||
<varname>required=0</varname>) and that we <emphasis>might
|
||||
like</emphasis> one argument (<varname>optional=1</varname>).
|
||||
If the user provides an argument, we'll get it -- if they
|
||||
don't, we'll just get an empty string. Hence the "if not n: n
|
||||
= 6", where we provide the default.
|
||||
</para>
|
||||
<para>
|
||||
You'll also note that <function>irc.reply</function> was given
|
||||
a keyword argument here, <varname>action</varname>. This
|
||||
means that the reply is to be made as an action rather than a
|
||||
normal reply.
|
||||
</para>
|
||||
<para>
|
||||
So that's our plugin. 5 commands, each building in
|
||||
complexity. You should now be able to write most anything you
|
||||
want to do in Supybot. Except regexp-based plugins, but
|
||||
that's a story for another day (and those aren't nearly as
|
||||
cool as these command-based callbacks anyway :)). Now we need
|
||||
to flesh it out to make it a full-fledged plugin.
|
||||
</para>
|
||||
</sect2>
|
||||
<sect2>
|
||||
<title>Using the registry in your plugin</title>
|
||||
<para>
|
||||
TODO: Describe the registry and how to write a proper plugin
|
||||
configure function.
|
||||
</para>
|
||||
</sect2>
|
||||
<para>
|
||||
We've written our own plugin from scratch (well, from the
|
||||
boilerplate that we got from
|
||||
<script>scripts/newplugin.py</script> :)) and
|
||||
survived! Now go write more plugins for supybot, and send
|
||||
them to me so I can use them too :)
|
||||
</para>
|
||||
</sect1>
|
||||
</article>
|
@ -1,46 +0,0 @@
|
||||
(define %stylesheet% "../stylesheets/supybot.css")
|
||||
|
||||
(element botcommand
|
||||
(make element gi: "span"
|
||||
attributes: '(("class" "botcommand"))
|
||||
(process-children)))
|
||||
|
||||
(element plugin
|
||||
(make element gi: "span"
|
||||
attributes: '(("class" "plugin"))
|
||||
(process-children)))
|
||||
|
||||
(element flag
|
||||
(make element gi: "span"
|
||||
attributes: '(("class" "flag"))
|
||||
(process-children)))
|
||||
|
||||
(element nick
|
||||
(make element gi: "span"
|
||||
attributes: '(("class" "nick"))
|
||||
(process-children)))
|
||||
|
||||
(element capability
|
||||
(make element gi: "span"
|
||||
attributes: '(("class" "capability"))
|
||||
(process-children)))
|
||||
|
||||
(element registrygroup
|
||||
(make element gi: "span"
|
||||
attributes: '(("class" "registrygroup"))
|
||||
(process-children)))
|
||||
|
||||
(element ircsession
|
||||
(make element gi: "pre"
|
||||
attributes: '(("class" "ircsession"))
|
||||
(process-children)))
|
||||
|
||||
(element script
|
||||
(make element gi: "span"
|
||||
attributes: '(("class" "script"))
|
||||
(process-children)))
|
||||
|
||||
(element channel
|
||||
(make element gi: "span"
|
||||
attributes: '(("class" "channel"))
|
||||
(process-children)))
|
@ -1,43 +0,0 @@
|
||||
(define %mono-font-family% "Courier New")
|
||||
|
||||
(element botcommand
|
||||
(make sequence
|
||||
font-family-name: %mono-font-family%))
|
||||
|
||||
(element plugin
|
||||
(make sequence
|
||||
font-weight: 'bold))
|
||||
|
||||
(element flag
|
||||
(make sequence
|
||||
font-posture: 'italic))
|
||||
|
||||
(element nick
|
||||
(make sequence
|
||||
font-family-name: %mono-font-family%))
|
||||
|
||||
(element capability
|
||||
(make sequence
|
||||
font-weight: 'bold))
|
||||
|
||||
(element registrygroup
|
||||
(make sequence
|
||||
font-weight: 'bold))
|
||||
|
||||
(element ircsession
|
||||
(make paragraph
|
||||
font-family-name: %mono-font-family%
|
||||
space-before: 12pt
|
||||
space-after: 12pt
|
||||
start-indent: 6pt
|
||||
lines: 'asis
|
||||
input-whitespace-treatment: 'preserve))
|
||||
|
||||
(element script
|
||||
(make sequence
|
||||
font-family-name: %mono-font-family%))
|
||||
|
||||
(element channel
|
||||
(make sequence
|
||||
font-weight: 'bold))
|
||||
|
@ -1,41 +0,0 @@
|
||||
.channel {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.botcommand {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.flag {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.nick {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.plugin {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.capability {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.registrygroup {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.ircsession {
|
||||
font-family: monospace;
|
||||
display: block;
|
||||
background-color: #666666;
|
||||
}
|
||||
|
||||
.script {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.channel {
|
||||
font-weight: bold;
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
<!DOCTYPE style-sheet PUBLIC "-//James Clark//DTD DSSSL Style Sheet//EN" [
|
||||
<!ENTITY print-ss PUBLIC
|
||||
"-//Norman Walsh//DOCUMENT DocBook Print Stylesheet//EN" CDATA DSSSL>
|
||||
<!ENTITY html-ss PUBLIC
|
||||
"-//Norman Walsh//DOCUMENT DocBook HTML Stylesheet//EN" CDATA DSSSL>
|
||||
<!ENTITY supybot-print SYSTEM "supybot-print.dsl">
|
||||
<!ENTITY supybot-html SYSTEM "supybot-html.dsl">
|
||||
]>
|
||||
|
||||
<style-sheet>
|
||||
<style-specification id="print" use="print-stylesheet">
|
||||
<style-specification-body>
|
||||
&supybot-print;
|
||||
</style-specification-body>
|
||||
</style-specification>
|
||||
<style-specification id="html" use="html-stylesheet">
|
||||
<style-specification-body>
|
||||
&supybot-html;
|
||||
</style-specification-body>
|
||||
</style-specification>
|
||||
<external-specification id="print-stylesheet" document="print-ss">
|
||||
<external-specification id="html-stylesheet" document="html-ss">
|
||||
</style-sheet>
|
@ -1,139 +0,0 @@
|
||||
<!-- Segregate all of our stuff into its own class for possible extension
|
||||
later and just because I wanted to write my own class :) -->
|
||||
<!ENTITY % local.supybot.tech.char.class "">
|
||||
<!ENTITY % supybot.tech.char.class "BotCommand|Plugin|Flag|Nick|Capability
|
||||
|RegistryGroup|Registry|Script
|
||||
|Channel %local.supybot.tech.char.class;">
|
||||
|
||||
<!-- Stuff that isn't supybot-specific, but it's python-related and no
|
||||
suitable element exists in the DocBook DTD -->
|
||||
<!ENTITY % local.python.tech.char.class "">
|
||||
<!ENTITY % python.tech.char.class "Module|Keyword
|
||||
%local.python.tech.char.class;">
|
||||
|
||||
<!-- Pretty much all of our stuff fits where stuff in the tech.char class
|
||||
goes, so we simply add our stuff using the local extension -->
|
||||
<!ENTITY % local.tech.char.class "|%supybot.tech.char.class;
|
||||
|%python.tech.char.class;">
|
||||
|
||||
<!-- linespecific is the same class as things like screen and programlisting,
|
||||
so it's added here to fit with the DocBook stuff (i.e., so putting an
|
||||
ircsession in where one of those previous two elements would be is a
|
||||
valid operation -->
|
||||
<!ENTITY % local.linespecific.class "|IrcSession">
|
||||
|
||||
<!-- Source the original DocBook DTD -->
|
||||
<!ENTITY % DocBookDTD PUBLIC "-//OASIS//DTD DocBook V4.1//EN">
|
||||
%DocBookDTD;
|
||||
|
||||
<!ELEMENT BotCommand - - ((%smallcptr.char.mix)+)>
|
||||
<!ENTITY % local.botcommand.attrib "">
|
||||
<!ENTITY % botcommand.role.attrib "%role.attrib;">
|
||||
<!ATTLIST BotCommand
|
||||
%common.attrib;
|
||||
%local.botcommand.attrib;
|
||||
%botcommand.role.attrib;
|
||||
>
|
||||
|
||||
<!ELEMENT Plugin - - ((%smallcptr.char.mix)+)>
|
||||
<!ENTITY % local.plugin.attrib "">
|
||||
<!ENTITY % plugin.role.attrib "%role.attrib;">
|
||||
<!ATTLIST Plugin
|
||||
%common.attrib;
|
||||
%local.plugin.attrib;
|
||||
%plugin.role.attrib;
|
||||
>
|
||||
|
||||
<!ELEMENT Flag - - ((%smallcptr.char.mix)+)>
|
||||
<!ENTITY % local.flag.attrib "">
|
||||
<!ENTITY % flag.role.attrib
|
||||
"
|
||||
flagtype (arg|noarg) #IMPLIED
|
||||
%role.attrib;"
|
||||
>
|
||||
<!ATTLIST Flag
|
||||
%common.attrib;
|
||||
%local.flag.attrib;
|
||||
%flag.role.attrib;
|
||||
>
|
||||
|
||||
<!ELEMENT Nick - - ((%smallcptr.char.mix)+)>
|
||||
<!ENTITY % local.nick.attrib "">
|
||||
<!ENTITY % nick.role.attrib "%role.attrib;">
|
||||
<!ATTLIST Nick
|
||||
%common.attrib;
|
||||
%local.nick.attrib;
|
||||
%nick.role.attrib;
|
||||
>
|
||||
|
||||
<!ELEMENT Capability - - ((%smallcptr.char.mix)+)>
|
||||
<!ENTITY % local.capability.attrib "">
|
||||
<!ENTITY % capability.role.attrib "%role.attrib;">
|
||||
<!ATTLIST Capability
|
||||
%common.attrib;
|
||||
%local.capability.attrib;
|
||||
%capability.role.attrib;
|
||||
>
|
||||
|
||||
<!ELEMENT Comment - - ((%smallcptr.char.mix)+)>
|
||||
<!ENTITY % local.comment.attrib "">
|
||||
<!ENTITY % comment.role.attrib "%role.attrib;">
|
||||
<!ATTLIST Comment
|
||||
%common.attrib;
|
||||
%local.comment.attrib;
|
||||
%comment.role.attrib;
|
||||
>
|
||||
|
||||
<!ELEMENT RegistryGroup - - ((%smallcptr.char.mix)+)>
|
||||
<!ENTITY % local.registrygroup.attrib "">
|
||||
<!ENTITY % registrygroup.role.attrib "%role.attrib;">
|
||||
<!ATTLIST RegistryGroup
|
||||
%common.attrib;
|
||||
%local.registrygroup.attrib;
|
||||
%registrygroup.role.attrib;
|
||||
>
|
||||
|
||||
<!ELEMENT Registry - - ((RegistryGroup|Comment)+)>
|
||||
<!ENTITY % local.registry.attrib "">
|
||||
<!ENTITY % registry.role.attrib "%role.attrib;">
|
||||
<!ATTLIST Registry
|
||||
%common.attrib;
|
||||
%local.registry.attrib;
|
||||
%registry.role.attrib;
|
||||
>
|
||||
|
||||
<!ELEMENT IrcSession - - ((%smallcptr.char.mix)+)>
|
||||
<!ENTITY % local.ircsession.attrib "">
|
||||
<!ENTITY % ircsession.role.attrib "%role.attrib;">
|
||||
<!ATTLIST IrcSession
|
||||
%common.attrib;
|
||||
%local.ircsession.attrib;
|
||||
%ircsession.role.attrib;
|
||||
>
|
||||
|
||||
<!ELEMENT Script - - ((%smallcptr.char.mix)+)>
|
||||
<!ENTITY % local.script.attrib "">
|
||||
<!ENTITY % script.role.attrib "%role.attrib;">
|
||||
<!ATTLIST Script
|
||||
%common.attrib;
|
||||
%local.script.attrib;
|
||||
%script.role.attrib;
|
||||
>
|
||||
|
||||
<!ELEMENT Channel - - ((%smallcptr.char.mix)+)>
|
||||
<!ENTITY % local.channel.attrib "">
|
||||
<!ENTITY % channel.role.attrib "%role.attrib;">
|
||||
<!ATTLIST Channel
|
||||
%common.attrib;
|
||||
%local.channel.attrib;
|
||||
%channel.role.attrib;
|
||||
>
|
||||
|
||||
<!ELEMENT Module - - ((%smallcptr.char.mix)+)>
|
||||
<!ENTITY % local.module.attrib "">
|
||||
<!ENTITY % module.role.attrib "%role.attrib;">
|
||||
<!ATTLIST Module
|
||||
%common.attrib;
|
||||
%local.module.attrib;
|
||||
%module.role.attrib;
|
||||
>
|
240
docs/FAQ
240
docs/FAQ
@ -1,240 +0,0 @@
|
||||
Q: Why does my bot not recognize me or tell me that I don't have the
|
||||
"owner" capability?
|
||||
|
||||
A: Because you've not given it anything to recognize you from!
|
||||
You'll need to identify with the bot ("help identify" to see how
|
||||
that works) or add your hostmask to your user record ("help
|
||||
addhostmask" to see how that works) for it to know that you're you.
|
||||
You may wish to note that addhostmask can accept a password; rather
|
||||
than identify, you can send the command "addhostmask myOwnerUser
|
||||
[hostmask] myOwnerUserPassword" and the bot will add your current
|
||||
hostmask to your owner user (of course, you should change
|
||||
myOwnerUser and myOwnerUserPassword appropriately for your bot).
|
||||
|
||||
|
||||
Q: What's a hostmask?
|
||||
|
||||
A: Each user on IRC is uniquely identified by a string which we call
|
||||
a "hostmask." The IRC RFC refers to it as a prefix. Either way,
|
||||
it consists of a nick, a user, and a host, in the form
|
||||
nick!user@host. If your Supybot complains that something you've
|
||||
given to it isn't a hostmask, make sure that you have those three
|
||||
components and that they're joined in the appropriate manner.
|
||||
|
||||
|
||||
Q: How do I make my Supybot op my users?
|
||||
|
||||
A: First, you'll have to make sure that your users register with the
|
||||
bot. They can do this with the "register" command. After they do
|
||||
so, you'll want to add the #channel,op capability to their user.
|
||||
Use the "channel addcapability" command to do this. After that,
|
||||
your users should be able to use the "op" command to get ops.
|
||||
|
||||
If you want your users to be auto-opped when they join the channel,
|
||||
you'll need to load the Enforcer plugin and turn its autoOp
|
||||
configuration variable on. Use the "config" command to do so.
|
||||
Here's an example of how to do these steps:
|
||||
|
||||
<jemfinch> I'm going to make an example session for giving
|
||||
you auto-ops, for our FAQ.
|
||||
<dunk1> ah ok ;]
|
||||
<jemfinch> First, I need you to register with supybot, using
|
||||
the "register" command (remember to send it in
|
||||
private).
|
||||
<dunk1> done
|
||||
<jemfinch> what name are you registered under?
|
||||
<dunk1> dunk1
|
||||
<jemfinch> ok, cool.
|
||||
<jemfinch> @channel addcapability dunk1 op
|
||||
<supybot> jemfinch: The operation succeeded.
|
||||
<jemfinch> now use the "op" command to get ops.
|
||||
<dunk1> @op
|
||||
--- supybot gives channel operator status to dunk1
|
||||
<dunk1> works!
|
||||
<dunk1> ;]
|
||||
<jemfinch> @load Enforcer
|
||||
<supybot> jemfinch: The operation succeeded.
|
||||
<jemfinch> @config channel supybot.plugins.Enforcer.autoOp On
|
||||
<supybot> jemfinch: The operation succeeded.
|
||||
<jemfinch> ok, now cycle the channel (part and then rejoin)
|
||||
<-- dunk1 (dunker@freebsd.nl) has left #supybot
|
||||
--> dunk1 (dunker@freebsd.nl) has joined #supybot
|
||||
--- supybot gives channel operator status to dunk1
|
||||
<jemfinch> cool, thanks :)
|
||||
|
||||
|
||||
Q: Can users with the "admin" capability change configuration
|
||||
variables?
|
||||
|
||||
A: Currently, no. Feel free to make your case to us as to why a
|
||||
certain configuration variable should only require the "admin"
|
||||
capability instead of the "owner" capability, and if we agree
|
||||
with you, we'll change it for the next release.
|
||||
|
||||
|
||||
Q: Can Supybot do factoids?
|
||||
|
||||
A: Supybot most certainly can! In fact, we offer three full-fledged
|
||||
factoids-related plugins!
|
||||
|
||||
Factoids (written by jemfinch) is Supybot's original
|
||||
factoids-related plugin. It offers full integration with Supybot's
|
||||
nested commands as well as a complete 1:n key to factoid ratio,
|
||||
with lookup by individual number. Factoids also uses a
|
||||
channel-specific database instead of a global database, though
|
||||
that's configurable with the
|
||||
supybot.databases.plugins.channelSpecific configuration variable.
|
||||
|
||||
MoobotFactoids (written by Strike) is much more full-featured,
|
||||
offering users the ability to define factoids in a slightly more
|
||||
user-friendly way, as well as parsing factoids to handle <reply>,
|
||||
<action>, and alternations (defining a factoid "test" as
|
||||
"<reply>(foo|bar|baz)" will make the bot send "foo" or "bar" or
|
||||
"baz" to the channel (without the normal "test is " at the
|
||||
beginning)). If you're accustomed to Moobot's factoids or
|
||||
Blootbot's factoids, then this is the Factoids plugin for you.
|
||||
Unfortunately, due to the more natural definition syntax (required
|
||||
to be compatible with Moobot) you can't define Factoids with nested
|
||||
commands; you'll have to evaluate the command first and then copy
|
||||
the result into your factoid definition.
|
||||
|
||||
Infobot (written by jamessan) is used for Infobot compatibility;
|
||||
if you still want the basic functionality of Infobot, this is the
|
||||
plugin to use.
|
||||
|
||||
|
||||
Q: Can I import my Infobot/Blootbot/Moobot factoids into Supybot?
|
||||
|
||||
A: As of present, we have no automated way to do so. Strike has
|
||||
written a few scripts for importing a Moobot database into
|
||||
MoobotFactoids, however, so you'll want to talk to him about
|
||||
helping you with that. We're certainly happy to help you convert
|
||||
such databases; if you can provide us with such a database exported
|
||||
to a flat file, we can probably do the rest of the work to write a
|
||||
script that imports it into a database for one of our
|
||||
factoids-related plugins.
|
||||
|
||||
|
||||
Q: Do I really have to use separate databases for each channel?
|
||||
|
||||
A: Of course not! We default to separate databases for each channel
|
||||
because, well, that's what jemfinch always thought was
|
||||
reasonable. Anyway, if you change the configuration variable
|
||||
supybot.databases.plugins.channelSpecific to False instead of
|
||||
True, for *most* databases, each channel will share the same
|
||||
database (the exceptions are ChannelStats, Herald, Seen, and
|
||||
WordStats, which are inherently rather channel-based).
|
||||
|
||||
|
||||
Q: Karma doesn't seem to work for me.
|
||||
|
||||
A: Karma by default doesn't acknowledge karma updates. If you check
|
||||
the karma of whatever you increased/decreased, you'll note that
|
||||
your increment or decrement still took place. If you'd rather
|
||||
Karma acknowledge karma updates, change the
|
||||
supybot.plugins.Karma.response configuration variable to On.
|
||||
|
||||
|
||||
Q: I added an alias, but it doesn't work!
|
||||
|
||||
A: Take a look at "help <alias you added>". If the alias the bot has
|
||||
listed doesn't match what you're giving it, chances are you need
|
||||
to quote your alias in order for the brackets not to be
|
||||
evaluated. For instance, if you're adding an alias to give you a
|
||||
link to your homepage, you need to say:
|
||||
|
||||
alias add mylink "format concat http://my.host.com/ [urlquote $1]"
|
||||
|
||||
and not:
|
||||
|
||||
alias add mylink format concat http://my.host.com/ [urlquote $1]
|
||||
|
||||
The first version works; the second version will always return the
|
||||
same url.
|
||||
|
||||
Q: Is there a command that can tell me what capability another
|
||||
command requires?
|
||||
|
||||
A: No, there isn't, and there probably never will be. Commands have
|
||||
the flexibility to check any capabilities they wish to check;
|
||||
while this flexibility is useful, it also makes it hard to guess
|
||||
what capability a certain command requires. We could make a
|
||||
solution that would work in a large majority of cases, but it
|
||||
wouldn't (and couldn't!) be absolutely correct in all
|
||||
circumstances, and since we're anal and we hate doing things
|
||||
halfway, we probably won't ever add this partial solution.
|
||||
|
||||
Besides, is the error message so bad? If we did have such a
|
||||
command, many users would call the command, see that they could
|
||||
perform it, and then run the command, thus doubling the activity
|
||||
in the channel. Is that something you want?
|
||||
|
||||
|
||||
Q: How do I make my Supybot connect to multiple servers?
|
||||
|
||||
A: Just use the "connect" command in the Network plugin. Easy as pie!
|
||||
|
||||
|
||||
Q: My bot can't handle nicks with brackets in them! It always
|
||||
complains about something not being a valid command, or about
|
||||
spurious or missing right brackets, etc.
|
||||
|
||||
A: You should quote arguments (using double quotes, like this:
|
||||
"foo[bar]") that have brackets in them that you don't wish to be
|
||||
evaluated as nested commands. Otherwise, you can turn off nested
|
||||
commands by setting supybot.commands.nested to False, or change
|
||||
the brackets that nest commands, by setting
|
||||
supybot.commands.nested.brackets to some other value (like <>,
|
||||
which can't occur in IRC nicks).
|
||||
|
||||
|
||||
Q: I've edited my configuration file, but my Supybot doesn't notice
|
||||
the changes! Even if I restart it, it doesn't see them. What's
|
||||
the deal?
|
||||
|
||||
A: Supybot won't reload its configuration files unless you tell it
|
||||
to. In addition, when Supybot exits (and periodically while it
|
||||
runs) it flushes its configuration file to disk. The safest way
|
||||
to avoid problems with configuration file edits is simply to exit
|
||||
the bot before editing the configuration file(s). If you don't
|
||||
wish to do that, however, you can edit the file, save the changes,
|
||||
and tell the bot to reload its configuration, either via the
|
||||
reload command in the Config plugin, or by sending the bot a
|
||||
SIGHUP. There is a brief period in this whole sequence where the
|
||||
bot can flush its configuration to disk after you write your
|
||||
changes, but we even have something to fix that: set the
|
||||
configuration variable supybot.flush to False, and then reload the
|
||||
configuration.
|
||||
|
||||
|
||||
Q: I found a bug, what do I do?
|
||||
|
||||
A: Submit it on Sourceforge through our Sourceforge project page:
|
||||
<http://sourceforge.net/tracker/?group_id=58965&atid=489447>. If
|
||||
Sourceforge happens to be down when you try to submit your bug,
|
||||
then post it in the "Supybot Developer Discussion" forum at our
|
||||
forums at <http://forums.supybot.org/>. If that doesn't work,
|
||||
email supybot-bugs@lists.sourceforge.net. If that doesn't work,
|
||||
email jemfinch@supybot.org. If that doesn't work, find yourself
|
||||
some carrier pigeons and ... hah! You thought I was serious!
|
||||
|
||||
Anyway, when you submit your bug, we'll need several things. If
|
||||
the bug involved an uncaught exception, we need the traceback
|
||||
(basically the stuff from "Uncaught exception in ..." to the next
|
||||
log entry). We'd also like to see the commands that caused the
|
||||
bug, or happened around the time you saw the bug. If the bug
|
||||
involved a database, we'd love to see the database. Remember, it's
|
||||
always worse to send us too little information in a bug report than
|
||||
too much.
|
||||
|
||||
|
||||
Q: Is there a way just to load *all* the plugins Supybot has?
|
||||
|
||||
A: No, there isn't. Even if there were, some plugins conflict with
|
||||
other plugins, so it wouldn't make much sense to load them. For
|
||||
instance, what would a bot do with Factoids, MoobotFactoids, and
|
||||
Infobot all loaded? Probably just annoy people :)
|
||||
|
||||
If you want to know more about the plugins that are available,
|
||||
check out our "Plugin index" at our website.
|
||||
|
@ -1,71 +0,0 @@
|
||||
These are a list of recurrent bugs in Supybot, and ways to notice
|
||||
them. It just might come in useful for people maintaining code.
|
||||
|
||||
1. Using == or != when you mean ircutils.strEqual. Nicks, prefixes,
|
||||
and channels should be compared for equality not by == or !=, but
|
||||
by ircutils.strEqual. This does a case-normalized check. Don't
|
||||
just use lower() because that doesn't use the rfc1459 casemapping.
|
||||
|
||||
To find:
|
||||
grep == plugins/*.py | egrep "nick|channel"
|
||||
grep "!=" plugins/*.py | egrep "nick|channel"
|
||||
|
||||
2. Using a warning log when it really should be an info log. Users
|
||||
don't like to see warnings. If we have warning logs, they'll
|
||||
complain. So let's try to make them complain as little as
|
||||
possible, and only use warnings when you've encountered a *very*
|
||||
odd situation, or when you need more information before
|
||||
determining if something is correct.
|
||||
|
||||
An example: The Services plugin has two methods, doNickservNotice
|
||||
and doChanservNotice, which doNotice dispatches to appropriately.
|
||||
Now, we can't possibly predict all the possible messages that
|
||||
ChanServ or NickServ might send to us. So I put a default clause
|
||||
in there that just says, "Hey, this is an unexpected message from
|
||||
ChanServ/NickServ: ..." I log this at warning level because I
|
||||
want to know when there's a NickServ/ChanServ message I haven't
|
||||
seen before. This works: the warning makes users report it.
|
||||
|
||||
Another example: We used to log failures in snarfers at the
|
||||
warning level. But do the users really want to be warned when a
|
||||
given "url" isn't valid? No! So now we just make it an info log,
|
||||
so that users who care can still see why a snarfer didn't snarf,
|
||||
but no one complains to us about it.
|
||||
|
||||
To find:
|
||||
grep log.warning plugins/*.py
|
||||
|
||||
3. Spelling errors. They plague almost every large piece of
|
||||
software. Supybot is no different, but we have a weapon against
|
||||
them: find-strings.py. Give find-strings.py a set of files, and
|
||||
it'll extract all the non-raw literal strings from the source code
|
||||
and output them to a file, with the originating file, line number,
|
||||
and string. Spell-checking Supybot is just a matter of taking
|
||||
this 10,000 line file and an hour and running aspell over it,
|
||||
correcting the errors in the original file as you find them.
|
||||
|
||||
To find:
|
||||
find-strings.py src/*.py plugins/*.py scripts/supybot*
|
||||
|
||||
4. Pegging the CPU. It has happened in the past that bugs in Supybot
|
||||
have caused 100% CPU usage. These are insidious, and generally
|
||||
hard to track down. Here are our tools against them; we assuming
|
||||
that the bug is reproducible.
|
||||
|
||||
First, I load the Debug plugin and settrace to a file, then I
|
||||
quickly reproduce the bug, and let the CPU spin for awhile. Then
|
||||
I kill the bot and check the trace file (which should be large).
|
||||
I look for patterns that would indicate an infinite loop of some
|
||||
sort.
|
||||
|
||||
Second, I strace the bot when it's looping. If I see output, it's
|
||||
making syscalls; if I don't see any output, it's not. If it's
|
||||
making syscalls, that might mean it's looping in the network
|
||||
drivers somehow; if not, it's somewhere else.
|
||||
|
||||
Third, I check that no regexps could be causing it. They're
|
||||
notorious for appearing safe, but actually hiding exponential
|
||||
complexity code (the kind of code that strong cryptography is
|
||||
based on).
|
||||
|
||||
After that, I pray harder :)
|
@ -1,143 +0,0 @@
|
||||
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 :)
|
||||
|
||||
First things first. You should have already read through INSTALL
|
||||
before reading any further.
|
||||
|
||||
Ok, so let's assume your bot connected to the server fine 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".
|
||||
|
||||
Now that you know how to deal with plugins having commands with the
|
||||
same name, 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.
|
||||
|
||||
Now, if you do want to play around with loading plugins, you're going
|
||||
to need to have the owner capability. If you ran the wizard, then
|
||||
chances are 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 (without the quotes), 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 your owner user and password 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 addhostmask command: it lets you add a hostmask to your user
|
||||
record so the bot recognizes you by your hostmask instead of requiring
|
||||
you to always identify with it before it recognizes you. Use the help
|
||||
command to see how this command works. Here's how I often use it:
|
||||
|
||||
addhostmask 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.
|
||||
|
||||
Now, with all that out of the way, we can continue telling you how to
|
||||
load plugins :) 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 Fun plugin, then "load Fun". Simple, right?
|
||||
If you need a list of the plugins you can load, you'll have to either
|
||||
list the directory the plugins are in, or you'll have to check our
|
||||
website -- look for the "Plugin index."
|
||||
|
||||
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.
|
||||
|
||||
You should now have a solid foundation for using Supybot. Be sure to
|
||||
check the help that is built-in to the bot itself if you have any
|
||||
questions, and enjoy using Supybot!
|
48
docs/HACKING
48
docs/HACKING
@ -1,48 +0,0 @@
|
||||
So, you want to hack on Supybot? Cool! I'm glad -- more developers
|
||||
means more users, and more users means better software (although I
|
||||
suppose more developers means better software even without the
|
||||
addition of more users :))
|
||||
|
||||
Anyway, there are a few things you should know before you submit your
|
||||
code to be accepted into Supybot. The first, and most important
|
||||
thing is that we really do value your contribution. We may say that
|
||||
it's not appropriate for the core distribution and any number of
|
||||
varying reasons, but regardless, we're happy that you're hacking on
|
||||
Supybot and bending it to your will, and we'll be happy to post your
|
||||
patch as long as it applies cleanly.
|
||||
|
||||
The second thing you should know is that, despite the fact that we're
|
||||
happy you want to contribute to Supybot, we're not afraid to piss you
|
||||
off by turning down your code. We won't hesitate to reject code
|
||||
because it's "bad" or because it doesn't fit our style guidelines
|
||||
(read docs/STYLE). We don't really care if it makes you angry or
|
||||
makes you use another IRC bot; we're in the practice of writing good
|
||||
software, not placating whiners. Despite this, we're not entirely
|
||||
heartless, and if you've done something we're interested in, we're
|
||||
willing to work with you and your code until such a time as it's
|
||||
ready to be accepted into the core. But if, at some point, we say,
|
||||
"This needs fixed" and you say, "I refuse to fix it," you can go put
|
||||
your code on the patch tracker; our time together is done. Supybot
|
||||
is #1 here -- we don't care about your feelings, we don't care about
|
||||
jamessan's feelings, we don't care about jemfinch's feelings if it
|
||||
means that the code quality and user experience of Supybot is to
|
||||
suffer.
|
||||
|
||||
Anyway, the normal process is that you'll submit a few patches,
|
||||
jemfinch will review them and tell you what needs to happen for them
|
||||
to be accepted into the core, you'll fix those problems, jemfinch
|
||||
will review them again, that cycle will repeat a few times. When
|
||||
your code is to jemfinch's satisfaction, it'll be integrated into the
|
||||
core. For many people, this is the end of the line. For some
|
||||
others (perhaps you!), you'll continue to write patches for Supybot,
|
||||
and your coding ability and commitment will be obvious through
|
||||
those. If your code quality is consistently high enough that
|
||||
jemfinch (or other Supybot developers) don't have to spend a
|
||||
significant amount of time reviewing your code, you'll be added as a
|
||||
developer on the SF.net project and given commit access to our CVS
|
||||
repository. From then on, you can do what you want, but be aware
|
||||
that the other developers are watching what you do -- if you have a
|
||||
big architecture change, you should probably talk to them before you
|
||||
commit.
|
||||
|
||||
So welcome aboard, and have fun hacking on Supybot!
|
271
docs/INTERFACES
271
docs/INTERFACES
@ -1,271 +0,0 @@
|
||||
These are the interfaces for some of the objects you'll deal with if
|
||||
you code for Supybot.
|
||||
|
||||
ircmsgs.IrcMsg:
|
||||
This is the object that represents an IRC message. It has
|
||||
several methods and attributes. The most important thing
|
||||
about this class, however, is that it *is* hashable, and thus
|
||||
*cannot* be modified. Do not change any attributes; any code
|
||||
that modifies an IRC message is *broken* and should not
|
||||
exist.
|
||||
|
||||
Interesting Methods:
|
||||
__init__: One of the more complex initializers in
|
||||
a class. It can be used in three different ways:
|
||||
|
||||
1) It can be given a string, as one received from
|
||||
the server, which it will then parse into its
|
||||
separate components and instantiate the class
|
||||
with those components as attributes.
|
||||
|
||||
2) It can be given a command, some (optional)
|
||||
arguments, and a (optional) prefix, and will
|
||||
instantiate the class with those components as
|
||||
attributes.
|
||||
|
||||
3) It can be given, in addition to any of the
|
||||
above arguments, a 'msg' keyword argument that
|
||||
will use the attributes of msg as defaults.
|
||||
This exists to make it easier to copy
|
||||
messages, since the class is immutable.
|
||||
|
||||
__str__: This returns the message in a string form
|
||||
suitable for sending to a server.
|
||||
|
||||
__repr__: This returns the message in a form
|
||||
suitable for eval(), assuming the name "IrcMsg" is
|
||||
in your namespace and is bound to this class.
|
||||
|
||||
Interesting Attributes:
|
||||
This is the meat of this class. These are
|
||||
generally what you'll be looking at with IrcMsgs.
|
||||
|
||||
command: This is the command of the IrcMsg --
|
||||
PRIVMSG, NOTICE, WHOIS, etc.
|
||||
|
||||
args: This is a tuple of the arguments to the
|
||||
IrcMsg. Some messages have arguments, some don't,
|
||||
depending on what command they are. You are, of
|
||||
course, always assured that args exists and is a
|
||||
tuple, though it might be empty.
|
||||
|
||||
prefix: This is the hostmask of the person/server
|
||||
the message is from. In general, you won't be
|
||||
setting this on your outgoing messages, but
|
||||
incoming messages will always have one. This is
|
||||
the whole hostmask; if the message was received
|
||||
from a server, it'll be the server's hostmask; if
|
||||
the message was received from a user, it'll be the
|
||||
whole user hostmask. In that case, however, it's
|
||||
also parsed out into the nick/user/host
|
||||
attributes, which are probably more useful to
|
||||
check for many purposes.
|
||||
|
||||
nick: If the message was sent by a user, this will
|
||||
be the nick of the user. If it was sent by a
|
||||
server, this will be the server's name (something
|
||||
like calvino.freenode.net or similar).
|
||||
|
||||
user: If the message was sent by a user, this will
|
||||
be the user string of the user -- what they put
|
||||
into their IRC client for their "full name." If
|
||||
it was sent by a server, it'll be the server's
|
||||
name, again.
|
||||
|
||||
host: If the message was sent by a user, this will
|
||||
be the host portion of their hostmask. If it was
|
||||
sent by a server, it'll be the server's name (yet
|
||||
again :))
|
||||
|
||||
|
||||
irclib.Irc:
|
||||
This is the object to handle everything about IRC except the
|
||||
actual connection to the server itself. (*NOTE* that the
|
||||
object actually received by commands in subclasses of
|
||||
callbacks.Privmsg is an IrcObjectProxy, which is described
|
||||
later. It augments the following interface with several
|
||||
methods of its own to help plugin authors.)
|
||||
|
||||
Interesting Methods:
|
||||
The two following messages (queueMsg and
|
||||
sendMsg) are the methods by far most commonly
|
||||
called by plugin authors. They're generally
|
||||
the only methods you need to pay attention to
|
||||
if you're writing plugins.
|
||||
|
||||
queueMsg: Queues a message for sending to the
|
||||
server. The queue is generally FIFO, but it
|
||||
does prioritize messages based on their command.
|
||||
|
||||
sendMsg: Queues a message for sending to the
|
||||
server prior to any messages in the normal
|
||||
queue. This is exactly a FIFO queue, no
|
||||
reordering is done at all.
|
||||
|
||||
The following two methods are the most important
|
||||
for people writing new IrcDrivers. Otherwise,
|
||||
you really don't need to pay attention to them.
|
||||
|
||||
feedMsg: Feeds the Irc object a message for it
|
||||
handle appropriately, as well as passing it on
|
||||
to callbacks.
|
||||
|
||||
takeMsg: If the Irc object has a message it's
|
||||
ready to send to the server, this will return
|
||||
it. Otherwise, it will return None.
|
||||
|
||||
The next several methods are of far more marginal
|
||||
utility. But someone may need them, so they're
|
||||
documented here.
|
||||
|
||||
addCallback: Takes a callback to add to the list
|
||||
of callbacks in the Irc object. See the
|
||||
interface for IrcCallback for more information.
|
||||
|
||||
getCallback: Gets a callback by name, if it is
|
||||
in the Irc object's list of callbacks. If it
|
||||
it isn't, returns None.
|
||||
|
||||
removeCallback: Removes a callback by name.
|
||||
Returns a list of the callbacks removed (since
|
||||
it is technically possible to have multiple
|
||||
callbacks with the same name. This list may
|
||||
be empty.
|
||||
|
||||
__init__: Requires a nick. Optional arguments
|
||||
include user and ident, which default to the
|
||||
nick given, password, which defaults to the empty
|
||||
password, and callbacks, a list of callbacks
|
||||
(which defaults to nothing, an empty list).
|
||||
|
||||
reset: Resets the Irc object to its original
|
||||
state, as well as sends a reset() to every
|
||||
callbacks.
|
||||
|
||||
die: Kills the IRC object and all its callbacks.
|
||||
|
||||
Interesting attributes:
|
||||
nick: The current nick of the bot.
|
||||
|
||||
prefix: The current prefix of the bot.
|
||||
|
||||
server: The current server the bot is connected to.
|
||||
|
||||
network: The current network name the bot is connected to.
|
||||
|
||||
afterConnect: False until the bot has received a
|
||||
command sent after the connection is finished --
|
||||
376, 377, or 422.
|
||||
|
||||
state: An IrcState object for this particular
|
||||
connection. See the interface for the IrcState
|
||||
object for more information.
|
||||
|
||||
|
||||
irclib.IrcCallback:
|
||||
Interesting Methods:
|
||||
name: Returns the name of the callback. The
|
||||
default implementation simply returns the name
|
||||
of the class.
|
||||
|
||||
__call__: Called by the Irc object with itself
|
||||
and the message whenever a message is fed to
|
||||
the Irc object. Nothing is done with the return
|
||||
value.
|
||||
|
||||
inFilter: Called by the Irc object with itself
|
||||
and the message whenever a message is fed to
|
||||
the Irc object. The return value should be an
|
||||
IrcMsg object to be passed to the next callback
|
||||
in the Irc's list of callbacks. If None is
|
||||
returned, all processing stops. This gives
|
||||
callbacks an oppurtunity to "filter" incoming
|
||||
messages before general callbacks are given
|
||||
them.
|
||||
|
||||
outFilter: Basically equivalent to inFilter,
|
||||
except instead of being called on messages
|
||||
as they enter the Irc object, it's called on
|
||||
messages as they leave the Irc object.
|
||||
|
||||
die: Called when the parent Irc is told to
|
||||
die. This gives callbacks an oppurtunity to
|
||||
close open files, network connections, or
|
||||
databases before they're deleted.
|
||||
|
||||
reset: Called when the parent Irc is told to
|
||||
reset (which is generally when reconnecting
|
||||
to the server). Most callbacks don't need
|
||||
to define this.
|
||||
|
||||
Interesting attributes:
|
||||
priority: Determines the priority of the
|
||||
callback in the Irc object's list of
|
||||
callbacks. Defaults to 99; the valid range
|
||||
includes 0 through sys.maxint-1 (don't use
|
||||
sys.maxint itself, that's reserved for the
|
||||
Misc plugin). The lower the number, the
|
||||
higher the priority. High priority
|
||||
callbacks are called earlier in the
|
||||
inFilter cycle, earlier in the __call__
|
||||
cycle, and later in the outFilter cycle --
|
||||
basically, they're given the first chances
|
||||
on the way in and the last chances on the
|
||||
way out.
|
||||
|
||||
|
||||
callbacks.IrcObjectProxy:
|
||||
IrcObjectProxy is a proxy for an irclib.Irc instance that
|
||||
serves to provide a much fuller interface for handling
|
||||
replies and errors as well as to handle the nesting of
|
||||
commands. This is what you'll be dealing with almost all the
|
||||
time when writing commands; when writing doCommand methods
|
||||
(the kind you read about in the interface description of
|
||||
irclib.IrcCallback) you'll be dealing with plain old
|
||||
irclib.Irc objects.
|
||||
|
||||
Interesting methods:
|
||||
reply: Called to reply to the current message
|
||||
with a string that is to be the reply.
|
||||
|
||||
replySuccess, replyError: These reply with the
|
||||
configured responses for success and generic
|
||||
error, respectively. If an additional argument
|
||||
is given, it's (intelligently) appended to the
|
||||
generic message to be more specific.
|
||||
|
||||
error: Called to send an error reply to the
|
||||
current message; not only does the response
|
||||
indicate an error, but commands that error out
|
||||
break the nested-command chain, which is
|
||||
generally useful for not confusing the user :)
|
||||
|
||||
errorNoCapability: Like error, except it accepts
|
||||
the capability that's missing and integrates it
|
||||
into the configured error message for such
|
||||
things. Also accepts an additional string for a
|
||||
more descriptive message, if that's what you
|
||||
want.
|
||||
|
||||
errorPossibleBug, errorNotRegistered,
|
||||
errorNoUser, errorRequiresPrivacy: These methods
|
||||
reply with the appropriate configured error
|
||||
message for the conditions in their names; they
|
||||
all take an additional arguments to be more
|
||||
specific about the conditions they indicate, but
|
||||
this argument is very rarely necessary.
|
||||
|
||||
getRealIrc: Returns the actual Irc object being
|
||||
proxied for.
|
||||
|
||||
replies: Sends a collection of messages to a given
|
||||
target, much like reply; except in this case, the user
|
||||
can configure whether the messages will be sent
|
||||
one-by-one or combined into a single message. Thus, the
|
||||
method accepts a "prefixer" argument, which prefixes the
|
||||
messages with a given string (or according to a given
|
||||
function), a "joiner" string (or function) used to join
|
||||
the messages into a single message if necessary, and an
|
||||
onlyPrefixFirst argument which determines whether only
|
||||
the first message will be prefixed when the messages are
|
||||
sent separately (it defaults to False).
|
@ -1,61 +0,0 @@
|
||||
So here's a general *programming* introduction to what the different
|
||||
modules do and what services they provide. It is, however, only an
|
||||
introduction. Read the modules themselves for a much more detailed
|
||||
explanation :)
|
||||
|
||||
fix.py: Stuff that Python should (but doesn't) include by default.
|
||||
|
||||
cdb.py: A constant database library, translated from C (and my O'Caml
|
||||
version) More information available at
|
||||
http://cr.yp.to/cdb.html. Not currently used since we
|
||||
switched to PySQLite.
|
||||
|
||||
ansi.py: Contains different ANSI color sequences.
|
||||
Mostly used by the debug module.
|
||||
|
||||
conf.py: The configuration file for the bot -- it sets a lot of
|
||||
variables that other modules check for when they have
|
||||
questions about what they need to do.
|
||||
|
||||
world.py: Just a dropping off place for some globals that need to be
|
||||
shared among all the modules. It's obviously not used *a
|
||||
lot*, but some things seem to fit better here than anywhere
|
||||
else.
|
||||
|
||||
privmsgs.py: Basic stuff relating to callbacks.Privmsg, the base class
|
||||
for most plugins.
|
||||
|
||||
callbacks.py: A few basic callbacks providing significant
|
||||
functionality. You'll likely be inheriting from Privmsg
|
||||
quite a bit.
|
||||
|
||||
ircdb.py: The users and channels databases are here, as well as the
|
||||
IrcUser and IrcChannel classes. Look here when you want to
|
||||
restrict a command to only certain users.
|
||||
|
||||
irclib.py: Provides the most important class in the irclib, Irc. It
|
||||
represents a connection to an IRC server, but it's entirely
|
||||
separate from the network implementation. It's connected
|
||||
to the network (or whatever else drives it) by a "driver"
|
||||
module which uses its feedMsg/takeMsg functions.
|
||||
|
||||
drivers.py: The baseclass (IrcDriver) for various drivers to drive
|
||||
irclib.Irc classes. Also, the driving mechanism.
|
||||
|
||||
asyncoreDrivers.py: The asyncore-based drivers for use with the bot.
|
||||
|
||||
socketDrivers.py: The plain old socket-based drivers for use with the
|
||||
bot.
|
||||
|
||||
twistedDrivers.py: The Twisted <http://www.twistedmatrix.com/> drivers
|
||||
for use with the bot.
|
||||
|
||||
ircmsgs.py: The IrcMsg class (get to know it :)) and various functions
|
||||
for making the creation of IrcMsgs easier.
|
||||
|
||||
ircutils.py: Various utility functions for Irc -- read the module to
|
||||
see what goodies are there :)
|
||||
|
||||
schedule.py: A schedule driver (which is automatically registered with
|
||||
the drivers module) to run things at a particular time,
|
||||
or at specified periods of time.
|
@ -1,449 +0,0 @@
|
||||
*******************************************************************************
|
||||
The plugins/Random.py included with the distribution has been
|
||||
updated to use an argument-parsing module recently added to Supybot
|
||||
but not yet documented. Still, all of the functions used in this
|
||||
tutorial are available, so the code (as shown in this tutorial*
|
||||
should still work.
|
||||
*******************************************************************************
|
||||
|
||||
Ok, so you want to write a callback for Supybot. Good, then this is
|
||||
the place to be. We're going to start from the top (the highest
|
||||
level, where Supybot code does the most work for you) and move lower
|
||||
after that.
|
||||
|
||||
So have you used Supybot? If not, you need to go use it, get a feel
|
||||
for it, see how the various commands work and such.
|
||||
|
||||
So now that we know you've used Supybot, we'll start getting into
|
||||
details.
|
||||
|
||||
First, the easiest way to start writing a module is to use the wizard
|
||||
provided, scripts/newplugin.py. Here's an example session:
|
||||
|
||||
-----
|
||||
functor% supybot-newplugin
|
||||
What should the name of the plugin be? Random
|
||||
Supybot offers two major types of plugins: command-based and regexp-
|
||||
based. Command-based plugins are the kind of plugins you've seen most
|
||||
when you've used supybot. They're also the most featureful and
|
||||
easiest to write. Commands can be nested, for instance, whereas
|
||||
regexp-based callbacks can't do nesting. That doesn't mean that
|
||||
you'll never want regexp-based callbacks. They offer a flexibility
|
||||
that command-based callbacks don't offer; however, they don't tie into
|
||||
the whole system as well. If you need to combine a command-based
|
||||
callback with some regexp-based methods, you can do so by subclassing
|
||||
callbacks.PrivmsgCommandAndRegexp and then adding a class-level
|
||||
attribute "regexps" that is a sets.Set of methods that are regexp-
|
||||
based. But you'll have to do that yourself after this wizard is
|
||||
finished :)
|
||||
Do you want a command-based plugin or a regexp-based plugin? [command/
|
||||
regexp] command
|
||||
Sometimes you'll want a callback to be threaded. If its methods
|
||||
(command or regexp-based, either one) will take a signficant amount
|
||||
of time to run, you'll want to thread them so they don't block
|
||||
the entire bot.
|
||||
|
||||
Does your plugin need to be threaded? [y/n] n
|
||||
Your new plugin template is Random.py
|
||||
functor%
|
||||
-----
|
||||
|
||||
So that's what it looks like. Now let's look at the source code (if
|
||||
you'd like to look at it in your programming editor, the whole plugin
|
||||
is available as examples/Random.py):
|
||||
|
||||
-----
|
||||
#!/usr/bin/env python
|
||||
|
||||
###
|
||||
# Copyright (c) 2004, Jeremiah Fincher
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions, and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions, and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
# * Neither the name of the author of this software nor the name of
|
||||
# contributors to this software may be used to endorse or promote products
|
||||
# derived from this software without specific prior written consent.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
###
|
||||
|
||||
"""
|
||||
Add the module docstring here. This will be used by the setup.py script.
|
||||
"""
|
||||
|
||||
__revision__ = "$Id$"
|
||||
__author__ = ''
|
||||
|
||||
import supybot.plugins as plugins
|
||||
|
||||
import supybot.conf as conf
|
||||
import supybot.utils as utils
|
||||
import supybot.privmsgs as privmsgs
|
||||
import supybot.callbacks as callbacks
|
||||
|
||||
|
||||
def configure(advanced):
|
||||
# This will be called by setup.py to configure this module. Advanced is
|
||||
# a bool that specifies whether the user identified himself as an advanced
|
||||
# user or not. You should effect your configuration by manipulating the
|
||||
# registry as appropriate.
|
||||
from questions import expect, anything, something, yn
|
||||
conf.registerPlugin('Random', True)
|
||||
|
||||
class Random(callbacks.Privmsg):
|
||||
pass
|
||||
|
||||
|
||||
Class = Random
|
||||
|
||||
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78:
|
||||
-----
|
||||
|
||||
So a few notes, before we customize it.
|
||||
|
||||
You'll probably want to change the copyright notice to be your name.
|
||||
It wouldn't stick even if you kept my name, so you might as well :)
|
||||
|
||||
Describe what you want the plugin to do in the docstring. This is
|
||||
used in supybot-wizard in order to explain to the user the purpose of
|
||||
the module. It's also returned when someone asks the bot for help for
|
||||
a given module (instead of help for a certain command). We'll change
|
||||
this one to "Lots of stuff relating to random numbers."
|
||||
|
||||
Then there are the imports. The callbacks module is used (the class
|
||||
you're given subclasses callbacks.Privmsg) but the privmsgs module
|
||||
isn't used. That's alright; we can almost guarantee you'll use it, so
|
||||
we go ahead and add the import to the template.
|
||||
|
||||
Then you see a "configure" function. This is the function that's
|
||||
called when users decide to add your module in supybot-wizard. You'll
|
||||
note that by default it simply registers the plugin to be
|
||||
automatically loaded on startup. For many plugins this is all you
|
||||
need; for more complex plugins, you might need to ask questions and
|
||||
add commands based on the answers.
|
||||
|
||||
Now comes the meat of the plugin: the plugin class.
|
||||
|
||||
What you're given is a skeleton: a simple subclass of
|
||||
callbacks.Privmsg for you to start with. Now let's add a command.
|
||||
|
||||
I don't know what you know about random number generators, but the
|
||||
short of it is that they start at a certain number (a seed) and they
|
||||
continue (via some somewhat complicated/unpredictable algorithm) from
|
||||
there. This seed (and the rest of the sequence, really) is all nice
|
||||
and packaged up in Python's random module, the Random object. So the
|
||||
first thing we're going to have to do is give our plugin a Random
|
||||
object.
|
||||
|
||||
Normally, when we want to give instances of a class an object, we'll
|
||||
do so in the __init__ method. And that works great for plugins, too.
|
||||
The one thing you have to be careful of is that you call the
|
||||
superclass __init__ method at the end of your own __init__. So to add
|
||||
this random.Random object to our plugin, we can replace the "pass"
|
||||
statement with this:
|
||||
|
||||
def __init__(self):
|
||||
self.rng = random.Random()
|
||||
callbacks.Privmsg.__init__(self)
|
||||
|
||||
(rng is an abbreviation for "random number generator," in case you
|
||||
were curious)
|
||||
|
||||
Do be careful not to give your __init__ any arguments (other than
|
||||
self, of course). There's no way anything will ever get to them! If
|
||||
you have some sort of initial values you need to get to your plugin
|
||||
before it can do anything interesting, you should get those values
|
||||
from the registry.
|
||||
|
||||
There's an easier way to get our plugin to have its own rng than to
|
||||
define an __init__. Plugins are unique among classes because we're
|
||||
always certain that there will only be one instance -- supybot doesn't
|
||||
allow us to load multiple instances of a single plugin. So instead of
|
||||
adding the rng in __init__, we can just add it as a attribute to the
|
||||
class itself. Like so (replacing the "pass" statement again):
|
||||
|
||||
rng = random.Random()
|
||||
|
||||
And we save two lines of code and make our code a little more clear :)
|
||||
|
||||
Now that we have an RNG, we need some way to get random numbers. So
|
||||
first, we'll add a command that simply gets the next random number and
|
||||
gives it back to the user. It takes no arguments, of course (what
|
||||
would you give it?). Here's the command, and I'll follow that with
|
||||
the explanation of what each part means.
|
||||
|
||||
def random(self, irc, msg, args):
|
||||
"""takes no arguments
|
||||
|
||||
Returns the next random number generated by the random number
|
||||
generator.
|
||||
"""
|
||||
irc.reply(str(self.rng.random()))
|
||||
|
||||
And that's it! Pretty simple, huh? Anyway, you're probably wondering
|
||||
what all that *means*. We'll start with the def statement:
|
||||
|
||||
def random(self, irc, msg, args):
|
||||
|
||||
What that does is define a command "random". You can call it by
|
||||
saying "@random" (or whatever prefix character your specific bot
|
||||
uses). The arguments are a bit less obvious. Self is self-evident
|
||||
(hah!). irc is the Irc object passed to the command; msg is the
|
||||
original IrcMsg object. But you're really not going to have to deal
|
||||
with either of these too much (with the exception of calling irc.reply
|
||||
or irc.error). What you're *really* interested in is the args arg.
|
||||
That is a list of all the arguments passed to your command, pre-parsed
|
||||
and already evaluated (i.e., you never have to worry about nested
|
||||
commands, or handling double quoted strings, or splitting on
|
||||
whitespace -- the work has already been done for you). You can read
|
||||
about the Irc object in irclib.py (you won't find .reply or .error
|
||||
there, though, because you're actually getting an IrcObjectProxy, but
|
||||
that's beyond the level we want to describe here :)). You can read
|
||||
about the msg object in ircmsgs.py. But again, you'll very rarely be
|
||||
using these objects.
|
||||
|
||||
(In case you're curious, the answer is yes, you *must* name your
|
||||
arguments (self, irc, msg, args). The names of those arguments is one
|
||||
of the ways that supybot uses to determine which methods in a plugin
|
||||
class are commands and which aren't. And while we're talking about
|
||||
naming restrictions, all your commands should be named in
|
||||
all-lowercase with no underscores. Before calling a command, supybot
|
||||
always converts the command name to lowercase and removes all dashes
|
||||
and underscores. On the other hand, you now know an easy way to make
|
||||
sure a method is never called (even if its arguments are (self, irc,
|
||||
msg, args), however unlikely that may be). Just name it with an
|
||||
underscore or an uppercase letter in it :))
|
||||
|
||||
You'll also note that the docstring is odd. The wonderful thing about
|
||||
the supybot framework is that it's easy to write complete commands
|
||||
with help and everything: the docstring *IS* the help! Given the
|
||||
above docstring, this is what a supybot does:
|
||||
|
||||
<jemfinch> @help random
|
||||
<angryman> jemfinch: (random takes no arguments) -- Returns the
|
||||
next random number from the random number generator.
|
||||
|
||||
Now on to the actual body of the function:
|
||||
|
||||
irc.reply(str(self.rng.random()))
|
||||
|
||||
irc.reply takes one simple argument: a string. The string is the
|
||||
reply to be sent. Don't worry about length restrictions or anything
|
||||
-- if the string you want to send is too big for an IRC message (and
|
||||
oftentimes that turns out to be the case :)) the Supybot framework
|
||||
handles that entirely transparently to you. Do make sure, however,
|
||||
that you give irc.reply a string. It doesn't take anything else
|
||||
(sometimes even unicode fails!). That's why we have
|
||||
"str(self.rng.random())" instead of simply "self.rng.random()" -- we
|
||||
had to give irc.reply a string.
|
||||
|
||||
Anyway, now that we have an RNG, we have a need for seed! Of course,
|
||||
Python gives us a good seed already (it uses the current time as a
|
||||
seed if we don't give it one) but users might want to be able to
|
||||
repeat "random" sequences, so letting them set the seed is a good
|
||||
thing. So we'll add a seed command to give the RNG a specific seed:
|
||||
|
||||
def seed(self, irc, msg, args):
|
||||
"""<seed>
|
||||
|
||||
Sets the seed of the random number generator. <seed> must be
|
||||
an int or a long.
|
||||
"""
|
||||
seed = privmsgs.getArgs(args)
|
||||
try:
|
||||
seed = long(seed)
|
||||
except ValueError:
|
||||
# It wasn't a valid long!
|
||||
irc.error('<seed> must be a valid int or long.')
|
||||
return
|
||||
self.rng.seed(seed)
|
||||
irc.replySuccess()
|
||||
|
||||
So this one's a bit more complicated. But it's still pretty simple.
|
||||
The method name is "seed" so that'll be the command name. The
|
||||
arguments are the same, the docstring is of the same form, so we don't
|
||||
need to go over that again. The body of the function, however, is
|
||||
significantly different.
|
||||
|
||||
privmsgs.getArgs is a function you're going to be seeing a lot of when
|
||||
you write plugins for Supybot. What it does is basically give you the
|
||||
right number of arguments for your comamnd. In this case, we want one
|
||||
argument. But we might have been given any number of arguments by the
|
||||
user. So privmsgs.getArgs joins them appropriately, leaving us with
|
||||
one single "seed" argument (by default, it returns one argument as a
|
||||
single value; more arguments are returned in a tuple/list). Yes, we
|
||||
could've just said "seed = args[0]" and gotten the first argument, but
|
||||
what if the user didn't pass us an argument at all? Then we've got to
|
||||
catch the IndexError from args[0] and complain to the user about it.
|
||||
privmsgs.getArgs, on the other hand, handles all that for us. If the
|
||||
user didn't give us enough arguments, it'll reply with the help string
|
||||
for the command, thus saving us the effort.
|
||||
|
||||
So we have the seed from privmsgs.getArgs. But it's a string. The
|
||||
next three lines are pretty darn obvious: we're just converting the
|
||||
string to a int of some sort. But if it's not, that's when we're
|
||||
going to call irc.error. It has the same interface as we saw before
|
||||
in irc.reply, but it makes sure to remind the user that an error has
|
||||
been encountered (currently, that means it puts "Error: " at the
|
||||
beginning of the message). After erroring, we return. It's important
|
||||
to remember this return here; otherwise, we'll just keep going down
|
||||
through the function and try to use this "seed" variable that never
|
||||
got assigned. A good general rule of thumb is that any time you use
|
||||
irc.error, you'll want to return immediately afterwards.
|
||||
|
||||
Then we set the seed -- that's a simple function on our rng object.
|
||||
Assuming that succeeds (and doesn't raise an exception, which it
|
||||
shouldn't, because we already read the documentation and know that it
|
||||
should work) we reply to say that everything worked fine. That's what
|
||||
irc.replySuccess says. By default, it has the very dry (and
|
||||
appropriately robot-like) "The operation succeeded." but you're
|
||||
perfectly welcome to customize it yourself -- the registry was written to
|
||||
be modified!
|
||||
|
||||
So that's a bit more complicated command. But we still haven't dealt
|
||||
with multiple arguments. Let's do that next.
|
||||
|
||||
So these random numbers are useful, but they're not the kind of random
|
||||
numbers we usually want in Real Life. In Real Life, we like to tell
|
||||
someone to "pick a number between 1 and 10." So let's write a
|
||||
function that does that. Of course, we won't hardcode the 1 or the 10
|
||||
into the function, but we'll take them as arguments. First the
|
||||
function:
|
||||
|
||||
def range(self, irc, msg, args):
|
||||
"""<start> <end>
|
||||
|
||||
Returns a number between <start> and <end>, inclusive (i.e., the number
|
||||
can be either of the endpoints.
|
||||
"""
|
||||
(start, end) = privmsgs.getArgs(args, required=2)
|
||||
try:
|
||||
end = int(end)
|
||||
start = int(start)
|
||||
except ValueError:
|
||||
irc.error('<start> and <end> must both be integers.')
|
||||
return
|
||||
# .randrange() doesn't include the endpoint, so we use end+1.
|
||||
irc.reply(str(self.rng.randrange(start, end+1)))
|
||||
|
||||
Pretty simple. This is becoming old hat by now. The only new thing
|
||||
here is the call to privmsgs.getArgs. We have to make sure, since we
|
||||
want two values, to pass a keyword parameter "required" into
|
||||
privmsgs.getArgs. Of course, privmsgs.getArgs handles all the
|
||||
checking for missing arguments and whatnot so we don't have to.
|
||||
|
||||
The Random object we're using offers us a "sample" method that takes a
|
||||
sequence and a number (we'll call it N) and returns a list of N items
|
||||
taken randomly from the sequence. So I'll show you an example that
|
||||
takes advantage of multiple arguments but doesn't use privmsgs.getArgs
|
||||
(and thus has to handle its own errors if the number of arguments
|
||||
isn't right). Here's the code:
|
||||
|
||||
def sample(self, irc, msg, args):
|
||||
"""<number of items> [<text> ...]
|
||||
|
||||
Returns a sample of the <number of items> taken from the remaining
|
||||
arguments. Obviously <number of items> must be less than the number
|
||||
of arguments given.
|
||||
"""
|
||||
try:
|
||||
n = int(args.pop(0))
|
||||
except IndexError: # raised by .pop(0)
|
||||
raise callbacks.ArgumentError
|
||||
except ValueError:
|
||||
irc.error('<number of items> must be an integer.')
|
||||
return
|
||||
if n > len(args):
|
||||
irc.error('<number of items> must be less than the number '
|
||||
'of arguments.')
|
||||
return
|
||||
sample = self.rng.sample(args, n)
|
||||
irc.reply(utils.commaAndify(map(repr, sample)))
|
||||
|
||||
Most everything here is familiar. The difference between this and the
|
||||
previous examples is that we're dealing with args directly, rather
|
||||
than through getArgs. Since we already have the arguments in a list,
|
||||
it doesn't make any sense to have privmsgs.getArgs smush them all
|
||||
together into a big long string that we'll just have to re-split. But
|
||||
we still want the nice error handling of privmsgs.getArgs. So what do
|
||||
we do? We raise callbacks.ArgumentError! That's the secret juju that
|
||||
privmsgs.getArgs is doing; now we're just doing it ourself. Someone
|
||||
up our callchain knows how to handle it so a neat error message is
|
||||
returned. So in this function, if .pop(0) fails, we weren't given
|
||||
enough arguments and thus need to tell the user how to call us.
|
||||
|
||||
So we have the args, we have the number, we do a simple call to
|
||||
random.sample and then we do this funky utils.commaAndify to it.
|
||||
Yeah, so I was running low on useful names :) Anyway, what it does is
|
||||
take a list of strings and return a string with them joined by a
|
||||
comma, the last one being joined with a comma and "and". So the list
|
||||
['foo', 'bar', 'baz'] becomes "foo, bar, and baz". It's pretty useful
|
||||
for showing the user lists in a useful form. We map the strings with
|
||||
repr() first just to surround them with quotes.
|
||||
|
||||
So we have one more example. Yes, I hear your groans, but it's
|
||||
pedagogically useful :) This time we're going to write a command that
|
||||
makes the bot roll a die. It'll take one argument (the number of
|
||||
sides on the die) and will respond with the equivalent of "/me rolls a
|
||||
__" where __ is the number the bot rolled. So here's the code:
|
||||
|
||||
def diceroll(self, irc, msg, args):
|
||||
"""[<number of sides>]
|
||||
|
||||
Rolls a die with <number of sides> sides. The default number
|
||||
of sides is 6.
|
||||
"""
|
||||
try:
|
||||
n = privmsgs.getArgs(args, required=0, optional=1)
|
||||
if not n:
|
||||
n = 6
|
||||
n = int(n)
|
||||
except ValueError:
|
||||
irc.error('Dice have integer numbers of sides. Use one.')
|
||||
return
|
||||
s = 'rolls a %s' % self.rng.randrange(1, n+1)
|
||||
irc.reply(s, action=True)
|
||||
|
||||
There's a lot of stuff you haven't seen before in there. The most
|
||||
important, though, is the first thing you'll notice that's different:
|
||||
the privmsg.getArgs call. Here we're offering a default argument in
|
||||
case the user is too lazy to supply one (or just wants a nice,
|
||||
standard six-sided die :)) privmsgs.getArgs supports that; we'll just
|
||||
tell it that we don't *need* any arguments (via required=0) and that
|
||||
we *might like* one argument (optional=1). If the user provides an
|
||||
argument, we'll get it -- if they don't, we'll just get an empty
|
||||
string. Hence the "if not n: n = 6", where we provide the default.
|
||||
|
||||
You'll also note that irc.reply was given a keyword argument here,
|
||||
"action". This means that the reply is to be made as an action rather
|
||||
than a normal reply.
|
||||
|
||||
So that's our plugin. 5 commands, each building in complexity. You
|
||||
should now be able to write most anything you want to do in Supybot.
|
||||
Except regexp-based plugins, but that's a story for another day (and
|
||||
those aren't nearly as cool as these command-based callbacks anyway
|
||||
:)). Now we need to flesh it out to make it a full-fledged plugin.
|
||||
|
||||
TODO: Describe the registry and how to write a proper plugin configure
|
||||
function.
|
||||
|
||||
We've written our own plugin from scratch (well, from the boilerplate
|
||||
that we got from scripts/newplugin.py :)) and survived! Now go write
|
||||
more plugins for supybot, and send them to me so I can use them too :)
|
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.
|
@ -1,43 +0,0 @@
|
||||
.\" Process this file with
|
||||
.\" groff -man -Tascii supybot-adduser.1
|
||||
.\"
|
||||
.TH SUPYBOT\-ADDUSER 1 "SEPTEMBER 2004"
|
||||
.SH NAME
|
||||
supybot\-adduser \- Adds a user to a Supybot users.conf file
|
||||
.SH SYNOPSIS
|
||||
.B supybot\-adduser
|
||||
.RI [ options ] " users.conf
|
||||
.SH DESCRIPTION
|
||||
.B
|
||||
supybot\-adduser
|
||||
adds a user to the specified users.conf file.
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
.B \-\^\-version
|
||||
Show version of program.
|
||||
.TP
|
||||
.BR \-h ", " \-\^\-help
|
||||
Show summary of options.
|
||||
.TP
|
||||
.BR \-u " NAME" "\fR,\fP \-\^\-username=" NAME
|
||||
Specifies the username to use for the new user.
|
||||
.TP
|
||||
.BR \-x ", " \-\^\-hashed
|
||||
Hash encrypt the password.
|
||||
.TP
|
||||
.BR \-n ", " \-\^\-plain
|
||||
Store the password in plain text.
|
||||
.TP
|
||||
.BR \-c " CAPABILITY" "\fR,\fP \-\^\-capability=" CAPABILITY
|
||||
Capability the user should have; this option may be given
|
||||
multiple times.
|
||||
.SH "SEE ALSO"
|
||||
.IR python (1),
|
||||
.IR supybot (1),
|
||||
.IR supybot\-wizard (1),
|
||||
.IR supybot\-newplugin (1)
|
||||
.SH AUTHOR
|
||||
This manual page was originally written by James Vega
|
||||
<vega dot james at gmail dot com>. Permission is granted to copy,
|
||||
distribute and/or modify this document under the terms of the Supybot
|
||||
license, a BSD\-style license.
|
@ -1,41 +0,0 @@
|
||||
.\" Process this file with
|
||||
.\" groff -man -Tascii supybot-newplugin.1
|
||||
.\"
|
||||
.TH SUPYBOT\-NEWPLUGIN 1 "SEPTEMBER 2004"
|
||||
.SH NAME
|
||||
supybot\-newplugin \- A wizard for creating Supybot plugins
|
||||
.SH SYNOPSIS
|
||||
.B supybot\-newplugin
|
||||
.RI [ options ]
|
||||
.SH DESCRIPTION
|
||||
.B
|
||||
supybot\-newplugin
|
||||
is a wizard that creates a template python source file for a new
|
||||
.IR supybot (1)
|
||||
plugin.
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
.B \-\^\-version
|
||||
Show version of program.
|
||||
.TP
|
||||
.BR \-h ", " \-\^\-help
|
||||
Show summary of options.
|
||||
.TP
|
||||
.BR \-r ", " \-\^\-regexp
|
||||
Uses a regexp\-based callback.
|
||||
.TP
|
||||
.BI \-n " NAME" "\fR,\fP \-\^\-name=" NAME
|
||||
Sets the name for the plugin.
|
||||
.TP
|
||||
.BR \-t ", " \-\^\-thread
|
||||
Makes the plugin threaded.
|
||||
.SH "SEE ALSO"
|
||||
.IR python (1),
|
||||
.IR supybot (1),
|
||||
.IR supybot\-wizard (1),
|
||||
.IR supybot\-adduser (1),
|
||||
.SH AUTHOR
|
||||
This manual page was originally written by James Vega
|
||||
<vega dot james at gmail dot com>. Permission is granted to copy,
|
||||
distribute and/or modify this document under the terms of the Supybot
|
||||
license, a BSD\-style license.
|
@ -1,40 +0,0 @@
|
||||
.\" Process this file with
|
||||
.\" groff -man -Tascii supybot-wizard.1
|
||||
.\"
|
||||
.TH SUPYBOT\-WIZARD 1 "SEPTEMBER 2004"
|
||||
.SH NAME
|
||||
supybot\-wizard \- A wizard for creating Supybot configuration files
|
||||
.SH SYNOPSIS
|
||||
.B supybot\-wizard
|
||||
.RI [ options ]
|
||||
.SH DESCRIPTION
|
||||
.B
|
||||
supybot\-wizard
|
||||
is an in\-depth wizard that provides a nice user interface for creating
|
||||
configuration files for
|
||||
.IR supybot (1).
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
.B \-\^\-version
|
||||
Show version of program.
|
||||
.TP
|
||||
.BR \-h ", " \-\^\-help
|
||||
Show summary of options.
|
||||
.TP
|
||||
.B \-\^\-allow\-root
|
||||
Determines whether the wizard will be allowed to run as root. You do not
|
||||
want this. Do not do it. Even if you think you want it, you do not.
|
||||
.TP
|
||||
.B \-\^\-no\-network
|
||||
Determines whether the wizard will be allowed to run without a network
|
||||
connection.
|
||||
.SH "SEE ALSO"
|
||||
.IR python (1),
|
||||
.IR supybot (1),
|
||||
.IR supybot\-adduser (1),
|
||||
.IR supybot\-newplugin (1)
|
||||
.SH AUTHOR
|
||||
This manual page was originally written by James Vega
|
||||
<vega dot james at gmail dot com>. Permission is granted to copy,
|
||||
distribute and/or modify this document under the terms of the Supybot
|
||||
license, a BSD\-style license.
|
@ -1,68 +0,0 @@
|
||||
.\" Process this file with
|
||||
.\" groff -man -Tascii supybot.1
|
||||
.\"
|
||||
.TH SUPYBOT 1 "SEPTEMBER 2004"
|
||||
.SH NAME
|
||||
supybot \- A robust and user friendly Python IRC bot
|
||||
.SH SYNOPSIS
|
||||
.B supybot
|
||||
.RI [ options ] " configFile
|
||||
.SH DESCRIPTION
|
||||
.B
|
||||
Supybot
|
||||
is a robust, user\-friendly, and programmer\-friendly Python IRC bot.
|
||||
It aims to be an adequate replacement for most existing IRC bots. It
|
||||
includes a very flexible and powerful ACL system for controlling access
|
||||
to commands, as well as more than 50 builtin plugins providing around
|
||||
400 actual commands.
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
.B \-\^\-version
|
||||
Show version of program.
|
||||
.TP
|
||||
.BR \-h ", " \-\^\-help
|
||||
Show summary of options.
|
||||
.TP
|
||||
.BR \-P ", " \-\^\-profile
|
||||
Enable profiling.
|
||||
.TP
|
||||
.B \-O
|
||||
Optimizes asserts out of the code; \-O0 optimizes asserts and uses
|
||||
.IR psyco .
|
||||
.TP
|
||||
.BI \-n " NICK" "\fR,\fP \-\^\-nick=" NICK
|
||||
Nick the bot should use.
|
||||
.TP
|
||||
.BI \-u " USER" "\fR,\fP \-\^\-user=" USER
|
||||
Full username the bot should use.
|
||||
.TP
|
||||
.BI \-i " IDENT" "\fR,\fP \-\^\-ident=" IDENT
|
||||
Ident the bot should use.
|
||||
.TP
|
||||
.BR \-d ", " \-\^\-daemon
|
||||
Determines whether the bot will daemonize. This is a no\-op on
|
||||
non\-POSIX systems.
|
||||
.TP
|
||||
.B \-\^\-allow\-default\-owner
|
||||
Determines whether the bot will allow its defaultCapabilities not to
|
||||
include "\-owner", thus giving all users the owner capability by
|
||||
default. This is dumb, hence we require a command\-line option to
|
||||
enable it.
|
||||
.TP
|
||||
.B \-\^\-allow\-root
|
||||
Determines whether the bot will be allowed to run as root. You do not
|
||||
want this. Do not do it. Even if you think you want it, you do not.
|
||||
.TP
|
||||
.B \-\^\-debug
|
||||
Determines whether some extra debugging stuff will be logged by this
|
||||
script.
|
||||
.SH "SEE ALSO"
|
||||
.IR python (1),
|
||||
.IR supybot\-wizard (1),
|
||||
.IR supybot\-adduser (1),
|
||||
.IR supybot\-newplugin (1)
|
||||
.SH AUTHOR
|
||||
This manual page was originally written by James Vega
|
||||
<vega dot james at gmail dot com>. Permission is granted to copy,
|
||||
distribute and/or modify this document under the terms of the Supybot
|
||||
license, a BSD\-style license.
|
@ -1,450 +0,0 @@
|
||||
"""Beautiful Soup
|
||||
Elixir and Tonic
|
||||
"The Screen-Scraper's Friend"
|
||||
|
||||
The BeautifulSoup class turns arbitrarily bad HTML into a tree-like
|
||||
nested tag-soup list of Tag objects and text snippets. A Tag object
|
||||
corresponds to an HTML tag. It knows about the HTML tag's attributes,
|
||||
and contains a representation of everything contained between the
|
||||
original tag and its closing tag (if any). It's easy to extract Tags
|
||||
that meet certain criteria.
|
||||
|
||||
A well-formed HTML document will yield a well-formed data
|
||||
structure. An ill-formed HTML document will yield a correspondingly
|
||||
ill-formed data structure. If your document is only locally
|
||||
well-formed, you can use this to process the well-formed part of it.
|
||||
|
||||
#Example:
|
||||
#--------
|
||||
from BeautifulSoup import BeautifulSoup
|
||||
text = '''<html>
|
||||
<head><title>The Title</title></head>
|
||||
<body>
|
||||
<a class="foo" href="http://www.crummy.com/">Link <i>text (italicized)</i></a>
|
||||
<a href="http://www.foo.com/">Link text 2</a>
|
||||
</body>
|
||||
</html>'''
|
||||
soup = BeautifulSoup()
|
||||
soup.feed(text)
|
||||
print soup("a") #Returns a list of 2 Tag objects, one for each link in
|
||||
#the source
|
||||
print soup.first("a", {'class':'foo'})['href'] #Returns http://www.crummy.com/
|
||||
print soup.first("title").contents[0] #Returns "The title"
|
||||
print soup.first("a", {'href':'http://www.crummy.com/'}).first("i").contents[0]
|
||||
#Returns "text (italicized)"
|
||||
|
||||
#Example of SQL-style attribute wildcards -- all four 'find' calls will
|
||||
#find the link.
|
||||
#----------------------------------------------------------------------
|
||||
soup = BeautifulSoup()
|
||||
soup.feed('''<a href="http://foo.com/">bla</a>''')
|
||||
print soup.fetch('a', {'href': 'http://foo.com/'})
|
||||
print soup.fetch('a', {'href': 'http://%'})
|
||||
print soup.fetch('a', {'href': '%.com/'})
|
||||
print soup.fetch('a', {'href': '%o.c%'})
|
||||
|
||||
#Example with horrible HTML:
|
||||
#---------------------------
|
||||
soup = BeautifulSoup()
|
||||
soup.feed('''<body>
|
||||
Go <a class="that" href="here.html"><i>here</i></a>
|
||||
or <i>go <b><a href="index.html">Home</a>
|
||||
</html>''')
|
||||
print soup.fetch('a') #Returns a list of 2 Tag objects.
|
||||
print soup.first(attrs={'href': 'here.html'})['class'] #Returns "that"
|
||||
print soup.first(attrs={'class': 'that'}).first('i').contents[0] #returns "here"
|
||||
|
||||
This library has no external dependencies. It works with Python 1.5.2
|
||||
and up. If you can install a Python extension, you might want to use
|
||||
the ElementTree Tidy HTML Tree Builder instead:
|
||||
http://www.effbot.org/zone/element-tidylib.htm
|
||||
|
||||
You can use BeautifulSoup on any SGML-like substance, such as XML or a
|
||||
domain-specific language that looks like HTML but has different tag
|
||||
names. For such purposes you may want to use the BeautifulStoneSoup
|
||||
class, which knows nothing at all about HTML per se. I also reserve
|
||||
the right to make the BeautifulSoup parser smarter between releases,
|
||||
so if you want forwards-compatibility without having to think about
|
||||
it, you might want to go with BeautifulStoneSoup.
|
||||
|
||||
Release status:
|
||||
|
||||
(I do a new release whenever I make a change that breaks backwards
|
||||
compatibility.)
|
||||
|
||||
Current release:
|
||||
|
||||
Applied patch from Richie Hindle (richie at entrian dot com) that
|
||||
makes tag.string a shorthand for tag.contents[0].string when the tag
|
||||
has only one string-owning child.
|
||||
|
||||
1.2 "Who for such dainties would not stoop?" (2004/07/08): Applied
|
||||
patch from Ben Last (ben at benlast dot com) that made
|
||||
Tag.renderContents() correctly handle Unicode.
|
||||
|
||||
Made BeautifulStoneSoup even dumber by making it not implicitly
|
||||
close a tag when another tag of the same type is encountered; only
|
||||
when an actual closing tag is encountered. This change courtesy of
|
||||
Fuzzy (mike at pcblokes dot com). BeautifulSoup still works as
|
||||
before.
|
||||
|
||||
1.1 "Swimming in a hot tureen": Added more 'nestable' tags. Changed
|
||||
popping semantics so that when a nestable tag is encountered, tags are
|
||||
popped up to the previously encountered nestable tag (of whatever kind).
|
||||
I will revert this if enough people complain, but it should make
|
||||
more people's lives easier than harder.
|
||||
|
||||
This enhancement was suggested by Anthony Baxter (anthony at
|
||||
interlink dot com dot au).
|
||||
|
||||
1.0 "So rich and green": Initial release.
|
||||
|
||||
Retreived from: http://www.crummy.com/software/BeautifulSoup/
|
||||
"""
|
||||
|
||||
__author__ = "Leonard Richardson (leonardr@segfault.org)"
|
||||
__version__ = "1.1 $Revision$"
|
||||
__date__ = "$Date$"
|
||||
__copyright__ = "Copyright (c) 2004 Leonard Richardson"
|
||||
__license__ = "Python"
|
||||
|
||||
from sgmllib import SGMLParser
|
||||
import string
|
||||
import types
|
||||
|
||||
class PageElement:
|
||||
"""Contains the navigational information for some part of the page
|
||||
(either a tag or a piece of text)"""
|
||||
|
||||
def __init__(self, parent=None, previous=None):
|
||||
self.parent = parent
|
||||
self.previous = previous
|
||||
self.next = None
|
||||
|
||||
class NavigableText(PageElement):
|
||||
|
||||
"""A simple wrapper around a string that keeps track of where in
|
||||
the document the string was found. Doesn't implement all the
|
||||
string methods because I'm lazy. You could have this extend
|
||||
UserString if you were using 2.2."""
|
||||
|
||||
def __init__(self, string, parent=None, previous=None):
|
||||
PageElement.__init__(self, parent, previous)
|
||||
self.string = string
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.string == str(other)
|
||||
|
||||
def __str__(self):
|
||||
return self.string
|
||||
|
||||
def strip(self):
|
||||
return self.string.strip()
|
||||
|
||||
class Tag(PageElement):
|
||||
|
||||
"""Represents a found HTML tag with its attributes and contents."""
|
||||
|
||||
def __init__(self, name, attrs={}, parent=None, previous=None):
|
||||
PageElement.__init__(self, parent, previous)
|
||||
self.name = name
|
||||
self.attrs = attrs
|
||||
self.contents = []
|
||||
self.foundClose = 0
|
||||
|
||||
def get(self, key, default=None):
|
||||
return self._getAttrMap().get(key, default)
|
||||
|
||||
def __call__(self, *args):
|
||||
return apply(self.fetch, args)
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self._getAttrMap()[key]
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self._getAttrMap()
|
||||
self.attrMap[key] = value
|
||||
for i in range(0, len(self.attrs)):
|
||||
if self.attrs[i][0] == key:
|
||||
self.attrs[i] = (key, value)
|
||||
|
||||
def _getAttrMap(self):
|
||||
if not hasattr(self, 'attrMap'):
|
||||
self.attrMap = {}
|
||||
for (key, value) in self.attrs:
|
||||
self.attrMap[key] = value
|
||||
return self.attrMap
|
||||
|
||||
def __repr__(self):
|
||||
return str(self)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self == other
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, Tag) or self.name != other.name or self.attrs != other.attrs or len(self.contents) != len(other.contents):
|
||||
return 0
|
||||
for i in range(0, len(self.contents)):
|
||||
if self.contents[i] != other.contents[i]:
|
||||
return 0
|
||||
return 1
|
||||
|
||||
def __str__(self):
|
||||
attrs = ''
|
||||
if self.attrs:
|
||||
for key, val in self.attrs:
|
||||
attrs = attrs + ' %s="%s"' % (key, val)
|
||||
close = ''
|
||||
closeTag = ''
|
||||
if self.isSelfClosing():
|
||||
close = ' /'
|
||||
elif self.foundClose:
|
||||
closeTag = '</%s>' % self.name
|
||||
s = self.renderContents()
|
||||
if not hasattr(self, 'hideTag'):
|
||||
s = '<%s%s%s>' % (self.name, attrs, close) + s + closeTag
|
||||
return s
|
||||
|
||||
def renderContents(self):
|
||||
s='' #non-Unicode
|
||||
for c in self.contents:
|
||||
try:
|
||||
s = s + str(c)
|
||||
except UnicodeEncodeError:
|
||||
if type(s) <> types.UnicodeType:
|
||||
s = s.decode('utf8') #convert ascii to Unicode
|
||||
#str() should, strictly speaking, not return a Unicode
|
||||
#string, but NavigableText never checks and will return
|
||||
#Unicode data if it was initialised with it.
|
||||
s = s + str(c)
|
||||
return s
|
||||
|
||||
def isSelfClosing(self):
|
||||
return self.name in BeautifulSoup.SELF_CLOSING_TAGS
|
||||
|
||||
def append(self, tag):
|
||||
self.contents.append(tag)
|
||||
|
||||
def first(self, name=None, attrs={}, contents=None, recursive=1):
|
||||
r = None
|
||||
l = self.fetch(name, attrs, contents, recursive)
|
||||
if l:
|
||||
r = l[0]
|
||||
return r
|
||||
|
||||
def fetch(self, name=None, attrs={}, contents=None, recursive=1):
|
||||
"""Extracts Tag objects that match the given criteria. You
|
||||
can specify the name of the Tag, any attributes you want the
|
||||
Tag to have, and what text and Tags you want to see inside the
|
||||
Tag."""
|
||||
if contents and type(contents) != type([]):
|
||||
contents = [contents]
|
||||
results = []
|
||||
for i in self.contents:
|
||||
if isinstance(i, Tag):
|
||||
if not name or i.name == name:
|
||||
match = 1
|
||||
for attr, value in attrs.items():
|
||||
check = i.get(attr)
|
||||
#By default, find the specific value called for.
|
||||
#Use SQL-style wildcards to find substrings, prefix,
|
||||
#suffix, etc.
|
||||
result = (check == value)
|
||||
if check and value:
|
||||
if len(value) > 1 and value[0] == '%' and value[-1] == '%' and value[-2] != '\\':
|
||||
result = (check.find(value[1:-1]) != -1)
|
||||
elif value[0] == '%':
|
||||
print "blah"
|
||||
result = check.rfind(value[1:]) == len(check)-len(value)+1
|
||||
elif value[-1] == '%':
|
||||
result = check.find(value[:-1]) == 0
|
||||
if not result:
|
||||
match = 0
|
||||
break
|
||||
match = match and (not contents or i.contents == contents)
|
||||
if match:
|
||||
results.append(i)
|
||||
if recursive:
|
||||
results.extend(i.fetch(name, attrs, contents, recursive))
|
||||
return results
|
||||
|
||||
class BeautifulSoup(SGMLParser, Tag):
|
||||
|
||||
"""The actual parser. It knows the following facts about HTML, and
|
||||
not much else:
|
||||
|
||||
* Some tags have no closing tag and should be interpreted as being
|
||||
closed as soon as they are encountered.
|
||||
|
||||
* Most tags can't be nested; encountering an open tag when there's
|
||||
already an open tag of that type in the stack means that the
|
||||
previous tag of that type should be implicitly closed. However,
|
||||
some tags can be nested. When a nestable tag is encountered,
|
||||
it's okay to close all unclosed tags up to the last nestable
|
||||
tag. It might not be safe to close any more, so that's all it
|
||||
closes.
|
||||
|
||||
* The text inside some tags (ie. 'script') may contain tags which
|
||||
are not really part of the document and which should be parsed
|
||||
as text, not tags. If you want to parse the text as tags, you can
|
||||
always get it and parse it explicitly."""
|
||||
|
||||
SELF_CLOSING_TAGS = ['br', 'hr', 'input', 'img', 'meta', 'spacer',
|
||||
'link', 'frame']
|
||||
NESTABLE_TAGS = ['font', 'table', 'tr', 'td', 'th', 'tbody', 'p',
|
||||
'div']
|
||||
QUOTE_TAGS = ['script']
|
||||
|
||||
IMPLICITLY_CLOSE_TAGS = 1
|
||||
|
||||
def __init__(self, text=None):
|
||||
Tag.__init__(self, '[document]')
|
||||
SGMLParser.__init__(self)
|
||||
self.quoteStack = []
|
||||
self.hideTag = 1
|
||||
self.reset()
|
||||
if text:
|
||||
self.feed(text)
|
||||
|
||||
def feed(self, text):
|
||||
SGMLParser.feed(self, text)
|
||||
self.endData()
|
||||
|
||||
def reset(self):
|
||||
SGMLParser.reset(self)
|
||||
self.currentData = ''
|
||||
self.currentTag = None
|
||||
self.tagStack = []
|
||||
self.pushTag(self)
|
||||
|
||||
def popTag(self, closedTagName=None):
|
||||
tag = self.tagStack.pop()
|
||||
if closedTagName == tag.name:
|
||||
tag.foundClose = 1
|
||||
|
||||
# Tags with just one string-owning child get the same string
|
||||
# property as the child, so that soup.tag.string is shorthand
|
||||
# for soup.tag.contents[0].string
|
||||
if len(self.currentTag.contents) == 1 and \
|
||||
hasattr(self.currentTag.contents[0], 'string'):
|
||||
self.currentTag.string = self.currentTag.contents[0].string
|
||||
|
||||
#print "Pop", tag.name
|
||||
self.currentTag = self.tagStack[-1]
|
||||
return self.currentTag
|
||||
|
||||
def pushTag(self, tag):
|
||||
#print "Push", tag.name
|
||||
if self.currentTag:
|
||||
self.currentTag.append(tag)
|
||||
self.tagStack.append(tag)
|
||||
self.currentTag = self.tagStack[-1]
|
||||
|
||||
def endData(self):
|
||||
if self.currentData:
|
||||
if not string.strip(self.currentData):
|
||||
if '\n' in self.currentData:
|
||||
self.currentData = '\n'
|
||||
else:
|
||||
self.currentData = ' '
|
||||
o = NavigableText(self.currentData, self.currentTag, self.previous)
|
||||
if self.previous:
|
||||
self.previous.next = o
|
||||
self.previous = o
|
||||
self.currentTag.contents.append(o)
|
||||
self.currentData = ''
|
||||
|
||||
def _popToTag(self, name, closedTag=0):
|
||||
"""Pops the tag stack up to and including the most recent
|
||||
instance of the given tag. If a list of tags is given, will
|
||||
accept any of those tags as an excuse to stop popping, and will
|
||||
*not* pop the tag that caused it to stop popping."""
|
||||
if self.IMPLICITLY_CLOSE_TAGS:
|
||||
closedTag = 1
|
||||
numPops = 0
|
||||
mostRecentTag = None
|
||||
oneTag = (type(name) == types.StringType)
|
||||
for i in range(len(self.tagStack)-1, 0, -1):
|
||||
thisTag = self.tagStack[i].name
|
||||
if (oneTag and thisTag == name) \
|
||||
or (not oneTag and thisTag in name):
|
||||
numPops = len(self.tagStack)-i
|
||||
break
|
||||
if not oneTag:
|
||||
numPops = numPops - 1
|
||||
|
||||
closedTagName = None
|
||||
if closedTag:
|
||||
closedTagName = name
|
||||
|
||||
for i in range(0, numPops):
|
||||
mostRecentTag = self.popTag(closedTagName)
|
||||
return mostRecentTag
|
||||
|
||||
def unknown_starttag(self, name, attrs):
|
||||
if self.quoteStack:
|
||||
#This is not a real tag.
|
||||
#print "<%s> is not real!" % name
|
||||
attrs = map(lambda(x, y): '%s="%s"' % (x, y), attrs)
|
||||
self.handle_data('<%s %s>' % (name, attrs))
|
||||
return
|
||||
self.endData()
|
||||
tag = Tag(name, attrs, self.currentTag, self.previous)
|
||||
if self.previous:
|
||||
self.previous.next = tag
|
||||
self.previous = tag
|
||||
if not name in self.SELF_CLOSING_TAGS:
|
||||
if name in self.NESTABLE_TAGS:
|
||||
self._popToTag(self.NESTABLE_TAGS)
|
||||
else:
|
||||
self._popToTag(name)
|
||||
self.pushTag(tag)
|
||||
if name in self.SELF_CLOSING_TAGS:
|
||||
self.popTag()
|
||||
if name in self.QUOTE_TAGS:
|
||||
#print "Beginning quote (%s)" % name
|
||||
self.quoteStack.append(name)
|
||||
|
||||
def unknown_endtag(self, name):
|
||||
if self.quoteStack and self.quoteStack[-1] != name:
|
||||
#This is not a real end tag.
|
||||
#print "</%s> is not real!" % name
|
||||
self.handle_data('</%s>' % name)
|
||||
return
|
||||
self.endData()
|
||||
self._popToTag(name, 1)
|
||||
if self.quoteStack and self.quoteStack[-1] == name:
|
||||
#print "That's the end of %s!" % self.quoteStack[-1]
|
||||
self.quoteStack.pop()
|
||||
|
||||
def handle_data(self, data):
|
||||
self.currentData = self.currentData + data
|
||||
|
||||
def handle_comment(self, text):
|
||||
"Propagate comments right through."
|
||||
self.handle_data("<!--%s-->" % text)
|
||||
|
||||
def handle_charref(self, ref):
|
||||
"Propagate char refs right through."
|
||||
self.handle_data('&#%s;' % ref)
|
||||
|
||||
def handle_entityref(self, ref):
|
||||
"Propagate entity refs right through."
|
||||
self.handle_data('&%s;' % ref)
|
||||
|
||||
def handle_decl(self, data):
|
||||
"Propagate DOCTYPEs right through."
|
||||
self.handle_data('<!%s>' % data)
|
||||
|
||||
class BeautifulStoneSoup(BeautifulSoup):
|
||||
|
||||
"""A version of BeautifulSoup that doesn't know anything at all
|
||||
about what HTML tags have special behavior. Useful for parsing
|
||||
things that aren't HTML, or when BeautifulSoup makes an assumption
|
||||
counter to what you were expecting."""
|
||||
|
||||
IMPLICITLY_CLOSE_TAGS = 0
|
||||
|
||||
SELF_CLOSING_TAGS = []
|
||||
NESTABLE_TAGS = []
|
||||
QUOTE_TAGS = []
|
@ -1,85 +0,0 @@
|
||||
"""
|
||||
Facade that hides the differences between the SOAPpy and SOAP.py
|
||||
libraries, so that google.py doesn't have to deal with them.
|
||||
|
||||
@author: Brian Landers <brian@bluecoat93.org>
|
||||
@license: Python
|
||||
@version: 0.5.4
|
||||
"""
|
||||
|
||||
import warnings
|
||||
from distutils.version import LooseVersion
|
||||
|
||||
__author__ = "Brian Landers <brian@bluecoat93.org>"
|
||||
__version__ = "0.6"
|
||||
__license__ = "Python"
|
||||
|
||||
#
|
||||
# Wrapper around the python 'warnings' facility
|
||||
#
|
||||
def warn( message, level=RuntimeWarning ):
|
||||
warnings.warn( message, level, stacklevel=3 )
|
||||
|
||||
# We can't use older version of SOAPpy, due to bugs that break the Google API
|
||||
minSOAPpyVersion = "0.11.3"
|
||||
|
||||
#
|
||||
# Try loading SOAPpy first. If that fails, fall back to the old SOAP.py
|
||||
#
|
||||
SOAPpy = None
|
||||
try:
|
||||
import SOAPpy
|
||||
from SOAPpy import SOAPProxy, Types
|
||||
|
||||
if LooseVersion( minSOAPpyVersion ) > \
|
||||
LooseVersion( SOAPpy.version.__version__ ):
|
||||
|
||||
warn( "Versions of SOAPpy before %s have known bugs that prevent " +
|
||||
"PyGoogle from functioning." % minSOAPpyVersion )
|
||||
raise ImportError
|
||||
|
||||
except ImportError:
|
||||
warn( "SOAPpy not imported. Trying legacy SOAP.py.",
|
||||
DeprecationWarning )
|
||||
try:
|
||||
import SOAP
|
||||
except ImportError:
|
||||
raise RuntimeError( "Unable to find SOAPpy or SOAP. Can't continue.\n" )
|
||||
|
||||
#
|
||||
# Constants that differ between the modules
|
||||
#
|
||||
if SOAPpy:
|
||||
false = Types.booleanType(0)
|
||||
true = Types.booleanType(1)
|
||||
structType = Types.structType
|
||||
faultType = Types.faultType
|
||||
else:
|
||||
false = SOAP.booleanType(0)
|
||||
true = SOAP.booleanType(1)
|
||||
structType = SOAP.structType
|
||||
faultType = SOAP.faultType
|
||||
|
||||
#
|
||||
# Get a SOAP Proxy object in the correct way for the module we're using
|
||||
#
|
||||
def getProxy( url, namespace, http_proxy ):
|
||||
if SOAPpy:
|
||||
return SOAPProxy( url,
|
||||
namespace = namespace,
|
||||
http_proxy = http_proxy )
|
||||
|
||||
else:
|
||||
return SOAP.SOAPProxy( url,
|
||||
namespace = namespace,
|
||||
http_proxy = http_proxy )
|
||||
|
||||
#
|
||||
# Convert an object to a dictionary in the proper way for the module
|
||||
# we're using for SOAP
|
||||
#
|
||||
def toDict( obj ):
|
||||
if SOAPpy:
|
||||
return obj._asdict()
|
||||
else:
|
||||
return obj._asdict
|
@ -1,478 +0,0 @@
|
||||
"""
|
||||
################################################################################
|
||||
#
|
||||
# SOAPpy - Cayce Ullman (cayce@actzero.com)
|
||||
# Brian Matthews (blm@actzero.com)
|
||||
# Gregory Warnes (gregory_r_warnes@groton.pfizer.com)
|
||||
# Christopher Blunck (blunck@gst.com)
|
||||
#
|
||||
################################################################################
|
||||
# Copyright (c) 2003, Pfizer
|
||||
# Copyright (c) 2001, Cayce Ullman.
|
||||
# Copyright (c) 2001, Brian Matthews.
|
||||
#
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
# Redistributions of source code must retain the above copyright notice, this
|
||||
# list of conditions and the following disclaimer.
|
||||
#
|
||||
# Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
#
|
||||
# Neither the name of actzero, inc. nor the names of its contributors may
|
||||
# be used to endorse or promote products derived from this software without
|
||||
# specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
|
||||
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#
|
||||
################################################################################
|
||||
"""
|
||||
|
||||
ident = '$Id$'
|
||||
from version import __version__
|
||||
|
||||
from __future__ import nested_scopes
|
||||
|
||||
#import xml.sax
|
||||
import urllib
|
||||
from types import *
|
||||
import re
|
||||
import base64
|
||||
|
||||
# SOAPpy modules
|
||||
from Errors import *
|
||||
from Config import Config
|
||||
from Parser import parseSOAPRPC
|
||||
from SOAPBuilder import buildSOAP
|
||||
from Utilities import *
|
||||
from Types import faultType, simplify
|
||||
|
||||
################################################################################
|
||||
# Client
|
||||
################################################################################
|
||||
|
||||
|
||||
def SOAPUserAgent():
|
||||
return "SOAPpy " + __version__ + " (pywebsvcs.sf.net)"
|
||||
|
||||
|
||||
class SOAPAddress:
|
||||
def __init__(self, url, config = Config):
|
||||
proto, uri = urllib.splittype(url)
|
||||
|
||||
# apply some defaults
|
||||
if uri[0:2] != '//':
|
||||
if proto != None:
|
||||
uri = proto + ':' + uri
|
||||
|
||||
uri = '//' + uri
|
||||
proto = 'http'
|
||||
|
||||
host, path = urllib.splithost(uri)
|
||||
|
||||
try:
|
||||
int(host)
|
||||
host = 'localhost:' + host
|
||||
except:
|
||||
pass
|
||||
|
||||
if not path:
|
||||
path = '/'
|
||||
|
||||
if proto not in ('http', 'https', 'httpg'):
|
||||
raise IOError, "unsupported SOAP protocol"
|
||||
if proto == 'httpg' and not config.GSIclient:
|
||||
raise AttributeError, \
|
||||
"GSI client not supported by this Python installation"
|
||||
if proto == 'https' and not config.SSLclient:
|
||||
raise AttributeError, \
|
||||
"SSL client not supported by this Python installation"
|
||||
|
||||
self.user,host = urllib.splituser(host)
|
||||
self.proto = proto
|
||||
self.host = host
|
||||
self.path = path
|
||||
|
||||
def __str__(self):
|
||||
return "%(proto)s://%(host)s%(path)s" % self.__dict__
|
||||
|
||||
__repr__ = __str__
|
||||
|
||||
|
||||
class HTTPTransport:
|
||||
def getNS(self, original_namespace, data):
|
||||
"""Extract the (possibly extended) namespace from the returned
|
||||
SOAP message."""
|
||||
|
||||
if type(original_namespace) == StringType:
|
||||
pattern="xmlns:\w+=['\"](" + original_namespace + "[^'\"]*)['\"]"
|
||||
match = re.search(pattern, data)
|
||||
if match:
|
||||
return match.group(1)
|
||||
else:
|
||||
return original_namespace
|
||||
else:
|
||||
return original_namespace
|
||||
|
||||
# Need a Timeout someday?
|
||||
def call(self, addr, data, namespace, soapaction = None, encoding = None,
|
||||
http_proxy = None, config = Config):
|
||||
|
||||
import httplib
|
||||
|
||||
if not isinstance(addr, SOAPAddress):
|
||||
addr = SOAPAddress(addr, config)
|
||||
|
||||
# Build a request
|
||||
if http_proxy:
|
||||
real_addr = http_proxy
|
||||
real_path = addr.proto + "://" + addr.host + addr.path
|
||||
else:
|
||||
real_addr = addr.host
|
||||
real_path = addr.path
|
||||
|
||||
if addr.proto == 'httpg':
|
||||
from pyGlobus.io import GSIHTTP
|
||||
r = GSIHTTP(real_addr, tcpAttr = config.tcpAttr)
|
||||
elif addr.proto == 'https':
|
||||
r = httplib.HTTPS(real_addr)
|
||||
else:
|
||||
r = httplib.HTTP(real_addr)
|
||||
|
||||
r.putrequest("POST", real_path)
|
||||
|
||||
r.putheader("Host", addr.host)
|
||||
r.putheader("User-agent", SOAPUserAgent())
|
||||
t = 'text/xml';
|
||||
if encoding != None:
|
||||
t += '; charset="%s"' % encoding
|
||||
r.putheader("Content-type", t)
|
||||
r.putheader("Content-length", str(len(data)))
|
||||
|
||||
# if user is not a user:passwd format
|
||||
# we'll receive a failure from the server. . .I guess (??)
|
||||
if addr.user != None:
|
||||
val = base64.encodestring(addr.user)
|
||||
r.putheader('Authorization','Basic ' + val.replace('\012',''))
|
||||
|
||||
# This fixes sending either "" or "None"
|
||||
if soapaction == None or len(soapaction) == 0:
|
||||
r.putheader("SOAPAction", "")
|
||||
else:
|
||||
r.putheader("SOAPAction", '"%s"' % soapaction)
|
||||
|
||||
if config.dumpHeadersOut:
|
||||
s = 'Outgoing HTTP headers'
|
||||
debugHeader(s)
|
||||
print "POST %s %s" % (real_path, r._http_vsn_str)
|
||||
print "Host:", addr.host
|
||||
print "User-agent: SOAPpy " + __version__ + " (http://pywebsvcs.sf.net)"
|
||||
print "Content-type:", t
|
||||
print "Content-length:", len(data)
|
||||
print 'SOAPAction: "%s"' % soapaction
|
||||
debugFooter(s)
|
||||
|
||||
r.endheaders()
|
||||
|
||||
if config.dumpSOAPOut:
|
||||
s = 'Outgoing SOAP'
|
||||
debugHeader(s)
|
||||
print data,
|
||||
if data[-1] != '\n':
|
||||
print
|
||||
debugFooter(s)
|
||||
|
||||
# send the payload
|
||||
r.send(data)
|
||||
|
||||
# read response line
|
||||
code, msg, headers = r.getreply()
|
||||
|
||||
content_type = headers.get("content-type","text/xml")
|
||||
content_length = headers.get("Content-length")
|
||||
if content_length == None:
|
||||
# No Content-Length provided; just read the whole socket
|
||||
# This won't work with HTTP/1.1 chunked encoding
|
||||
data = r.getfile().read()
|
||||
message_len = len(data)
|
||||
else:
|
||||
message_len = int(content_length)
|
||||
data = r.getfile().read(message_len)
|
||||
|
||||
if(config.debug):
|
||||
print "code=",code
|
||||
print "msg=", msg
|
||||
print "headers=", headers
|
||||
print "content-type=", content_type
|
||||
print "data=", data
|
||||
|
||||
if config.dumpHeadersIn:
|
||||
s = 'Incoming HTTP headers'
|
||||
debugHeader(s)
|
||||
if headers.headers:
|
||||
print "HTTP/1.? %d %s" % (code, msg)
|
||||
print "\n".join(map (lambda x: x.strip(), headers.headers))
|
||||
else:
|
||||
print "HTTP/0.9 %d %s" % (code, msg)
|
||||
debugFooter(s)
|
||||
|
||||
def startswith(string, val):
|
||||
return string[0:len(val)] == val
|
||||
|
||||
if code == 500 and not \
|
||||
( startswith(content_type, "text/xml") and message_len > 0 ):
|
||||
raise HTTPError(code, msg)
|
||||
|
||||
if config.dumpSOAPIn:
|
||||
s = 'Incoming SOAP'
|
||||
debugHeader(s)
|
||||
print data,
|
||||
if (len(data)>0) and (data[-1] != '\n'):
|
||||
print
|
||||
debugFooter(s)
|
||||
|
||||
if code not in (200, 500):
|
||||
raise HTTPError(code, msg)
|
||||
|
||||
|
||||
# get the new namespace
|
||||
if namespace is None:
|
||||
new_ns = None
|
||||
else:
|
||||
new_ns = self.getNS(namespace, data)
|
||||
|
||||
# return response payload
|
||||
return data, new_ns
|
||||
|
||||
################################################################################
|
||||
# SOAP Proxy
|
||||
################################################################################
|
||||
class SOAPProxy:
|
||||
def __init__(self, proxy, namespace = None, soapaction = None,
|
||||
header = None, methodattrs = None, transport = HTTPTransport,
|
||||
encoding = 'UTF-8', throw_faults = 1, unwrap_results = None,
|
||||
http_proxy=None, config = Config, noroot = 0,
|
||||
simplify_objects=None):
|
||||
|
||||
# Test the encoding, raising an exception if it's not known
|
||||
if encoding != None:
|
||||
''.encode(encoding)
|
||||
|
||||
# get default values for unwrap_results and simplify_objects
|
||||
# from config
|
||||
if unwrap_results is None:
|
||||
self.unwrap_results=config.unwrap_results
|
||||
else:
|
||||
self.unwrap_results=unwrap_results
|
||||
|
||||
if simplify_objects is None:
|
||||
self.simplify_objects=config.simplify_objects
|
||||
else:
|
||||
self.simplify_objects=simplify_objects
|
||||
|
||||
self.proxy = SOAPAddress(proxy, config)
|
||||
self.namespace = namespace
|
||||
self.soapaction = soapaction
|
||||
self.header = header
|
||||
self.methodattrs = methodattrs
|
||||
self.transport = transport()
|
||||
self.encoding = encoding
|
||||
self.throw_faults = throw_faults
|
||||
self.http_proxy = http_proxy
|
||||
self.config = config
|
||||
self.noroot = noroot
|
||||
|
||||
# GSI Additions
|
||||
if hasattr(config, "channel_mode") and \
|
||||
hasattr(config, "delegation_mode"):
|
||||
self.channel_mode = config.channel_mode
|
||||
self.delegation_mode = config.delegation_mode
|
||||
#end GSI Additions
|
||||
|
||||
def invoke(self, method, args):
|
||||
return self.__call(method, args, {})
|
||||
|
||||
def __call(self, name, args, kw, ns = None, sa = None, hd = None,
|
||||
ma = None):
|
||||
|
||||
ns = ns or self.namespace
|
||||
ma = ma or self.methodattrs
|
||||
|
||||
if sa: # Get soapaction
|
||||
if type(sa) == TupleType:
|
||||
sa = sa[0]
|
||||
else:
|
||||
if self.soapaction:
|
||||
sa = self.soapaction
|
||||
else:
|
||||
sa = name
|
||||
|
||||
if hd: # Get header
|
||||
if type(hd) == TupleType:
|
||||
hd = hd[0]
|
||||
else:
|
||||
hd = self.header
|
||||
|
||||
hd = hd or self.header
|
||||
|
||||
if ma: # Get methodattrs
|
||||
if type(ma) == TupleType: ma = ma[0]
|
||||
else:
|
||||
ma = self.methodattrs
|
||||
ma = ma or self.methodattrs
|
||||
|
||||
m = buildSOAP(args = args, kw = kw, method = name, namespace = ns,
|
||||
header = hd, methodattrs = ma, encoding = self.encoding,
|
||||
config = self.config, noroot = self.noroot)
|
||||
|
||||
|
||||
call_retry = 0
|
||||
try:
|
||||
|
||||
r, self.namespace = self.transport.call(self.proxy, m, ns, sa,
|
||||
encoding = self.encoding,
|
||||
http_proxy = self.http_proxy,
|
||||
config = self.config)
|
||||
|
||||
except Exception, ex:
|
||||
#
|
||||
# Call failed.
|
||||
#
|
||||
# See if we have a fault handling vector installed in our
|
||||
# config. If we do, invoke it. If it returns a true value,
|
||||
# retry the call.
|
||||
#
|
||||
# In any circumstance other than the fault handler returning
|
||||
# true, reraise the exception. This keeps the semantics of this
|
||||
# code the same as without the faultHandler code.
|
||||
#
|
||||
|
||||
if hasattr(self.config, "faultHandler"):
|
||||
if callable(self.config.faultHandler):
|
||||
call_retry = self.config.faultHandler(self.proxy, ex)
|
||||
if not call_retry:
|
||||
raise
|
||||
else:
|
||||
raise
|
||||
else:
|
||||
raise
|
||||
|
||||
if call_retry:
|
||||
r, self.namespace = self.transport.call(self.proxy, m, ns, sa,
|
||||
encoding = self.encoding,
|
||||
http_proxy = self.http_proxy,
|
||||
config = self.config)
|
||||
|
||||
|
||||
p, attrs = parseSOAPRPC(r, attrs = 1)
|
||||
|
||||
try:
|
||||
throw_struct = self.throw_faults and \
|
||||
isinstance (p, faultType)
|
||||
except:
|
||||
throw_struct = 0
|
||||
|
||||
if throw_struct:
|
||||
#print p
|
||||
raise p
|
||||
|
||||
# If unwrap_results=1 and there is only element in the struct,
|
||||
# SOAPProxy will assume that this element is the result
|
||||
# and return it rather than the struct containing it.
|
||||
# Otherwise SOAPproxy will return the struct with all the
|
||||
# elements as attributes.
|
||||
if self.unwrap_results:
|
||||
try:
|
||||
count = 0
|
||||
for i in p.__dict__.keys():
|
||||
if i[0] != "_": # don't count the private stuff
|
||||
count += 1
|
||||
t = getattr(p, i)
|
||||
if count == 1: # Only one piece of data, bubble it up
|
||||
p = t
|
||||
except:
|
||||
pass
|
||||
|
||||
# Automatically simplfy SOAP complex types into the
|
||||
# corresponding python types. (structType --> dict,
|
||||
# arrayType --> array, etc.)
|
||||
if self.simplify_objects:
|
||||
p = simplify(p)
|
||||
|
||||
if self.config.returnAllAttrs:
|
||||
return p, attrs
|
||||
return p
|
||||
|
||||
def _callWithBody(self, body):
|
||||
return self.__call(None, body, {})
|
||||
|
||||
def __getattr__(self, name): # hook to catch method calls
|
||||
if name == '__del__':
|
||||
raise AttributeError, name
|
||||
return self.__Method(self.__call, name, config = self.config)
|
||||
|
||||
# To handle attribute wierdness
|
||||
class __Method:
|
||||
# Some magic to bind a SOAP method to an RPC server.
|
||||
# Supports "nested" methods (e.g. examples.getStateName) -- concept
|
||||
# borrowed from xmlrpc/soaplib -- www.pythonware.com
|
||||
# Altered (improved?) to let you inline namespaces on a per call
|
||||
# basis ala SOAP::LITE -- www.soaplite.com
|
||||
|
||||
def __init__(self, call, name, ns = None, sa = None, hd = None,
|
||||
ma = None, config = Config):
|
||||
|
||||
self.__call = call
|
||||
self.__name = name
|
||||
self.__ns = ns
|
||||
self.__sa = sa
|
||||
self.__hd = hd
|
||||
self.__ma = ma
|
||||
self.__config = config
|
||||
return
|
||||
|
||||
def __call__(self, *args, **kw):
|
||||
if self.__name[0] == "_":
|
||||
if self.__name in ["__repr__","__str__"]:
|
||||
return self.__repr__()
|
||||
else:
|
||||
return self.__f_call(*args, **kw)
|
||||
else:
|
||||
return self.__r_call(*args, **kw)
|
||||
|
||||
def __getattr__(self, name):
|
||||
if name == '__del__':
|
||||
raise AttributeError, name
|
||||
if self.__name[0] == "_":
|
||||
# Don't nest method if it is a directive
|
||||
return self.__class__(self.__call, name, self.__ns,
|
||||
self.__sa, self.__hd, self.__ma)
|
||||
|
||||
return self.__class__(self.__call, "%s.%s" % (self.__name, name),
|
||||
self.__ns, self.__sa, self.__hd, self.__ma)
|
||||
|
||||
def __f_call(self, *args, **kw):
|
||||
if self.__name == "_ns": self.__ns = args
|
||||
elif self.__name == "_sa": self.__sa = args
|
||||
elif self.__name == "_hd": self.__hd = args
|
||||
elif self.__name == "_ma": self.__ma = args
|
||||
return self
|
||||
|
||||
def __r_call(self, *args, **kw):
|
||||
return self.__call(self.__name, args, kw, self.__ns, self.__sa,
|
||||
self.__hd, self.__ma)
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s at %d>" % (self.__class__, id(self))
|
@ -1,202 +0,0 @@
|
||||
"""
|
||||
################################################################################
|
||||
# Copyright (c) 2003, Pfizer
|
||||
# Copyright (c) 2001, Cayce Ullman.
|
||||
# Copyright (c) 2001, Brian Matthews.
|
||||
#
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
# Redistributions of source code must retain the above copyright notice, this
|
||||
# list of conditions and the following disclaimer.
|
||||
#
|
||||
# Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
#
|
||||
# Neither the name of actzero, inc. nor the names of its contributors may
|
||||
# be used to endorse or promote products derived from this software without
|
||||
# specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
|
||||
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#
|
||||
################################################################################
|
||||
"""
|
||||
|
||||
ident = '$Id$'
|
||||
from version import __version__
|
||||
|
||||
import copy, socket
|
||||
from types import *
|
||||
|
||||
from NS import NS
|
||||
|
||||
################################################################################
|
||||
# Configuration class
|
||||
################################################################################
|
||||
|
||||
class SOAPConfig:
|
||||
__readonly = ('SSLserver', 'SSLclient', 'GSIserver', 'GSIclient')
|
||||
|
||||
def __init__(self, config = None, **kw):
|
||||
d = self.__dict__
|
||||
|
||||
if config:
|
||||
if not isinstance(config, SOAPConfig):
|
||||
raise AttributeError, \
|
||||
"initializer must be SOAPConfig instance"
|
||||
|
||||
s = config.__dict__
|
||||
|
||||
for k, v in s.items():
|
||||
if k[0] != '_':
|
||||
d[k] = v
|
||||
else:
|
||||
# Setting debug also sets returnFaultInfo,
|
||||
# dumpHeadersIn, dumpHeadersOut, dumpSOAPIn, and dumpSOAPOut
|
||||
self.debug = 0
|
||||
self.dumpFaultInfo = 0
|
||||
# Setting namespaceStyle sets typesNamespace, typesNamespaceURI,
|
||||
# schemaNamespace, and schemaNamespaceURI
|
||||
self.namespaceStyle = '1999'
|
||||
self.strictNamespaces = 0
|
||||
self.typed = 1
|
||||
self.buildWithNamespacePrefix = 1
|
||||
self.returnAllAttrs = 0
|
||||
|
||||
# Strict checking of range for floats and doubles
|
||||
self.strict_range = 0
|
||||
|
||||
# Default encoding for dictionary keys
|
||||
self.dict_encoding = 'ascii'
|
||||
|
||||
# New argument name handling mechanism. See
|
||||
# README.MethodParameterNaming for details
|
||||
self.specialArgs = 1
|
||||
|
||||
# If unwrap_results=1 and there is only element in the struct,
|
||||
# SOAPProxy will assume that this element is the result
|
||||
# and return it rather than the struct containing it.
|
||||
# Otherwise SOAPproxy will return the struct with all the
|
||||
# elements as attributes.
|
||||
self.unwrap_results = 1
|
||||
|
||||
# Automatically convert SOAP complex types, and
|
||||
# (recursively) public contents into the corresponding
|
||||
# python types. (Private subobjects have names that start
|
||||
# with '_'.)
|
||||
#
|
||||
# Conversions:
|
||||
# - faultType --> raise python exception
|
||||
# - arrayType --> array
|
||||
# - compoundType --> dictionary
|
||||
#
|
||||
self.simplify_objects = 0
|
||||
|
||||
# Per-class authorization method. If this is set, before
|
||||
# calling a any class method, the specified authorization
|
||||
# method will be called. If it returns 1, the method call
|
||||
# will proceed, otherwise the call will throw with an
|
||||
# authorization error.
|
||||
self.authMethod = None
|
||||
|
||||
# Globus Support if pyGlobus.io available
|
||||
try:
|
||||
from pyGlobus import io;
|
||||
d['GSIserver'] = 1
|
||||
d['GSIclient'] = 1
|
||||
except:
|
||||
d['GSIserver'] = 0
|
||||
d['GSIclient'] = 0
|
||||
|
||||
|
||||
# Server SSL support if M2Crypto.SSL available
|
||||
try:
|
||||
from M2Crypto import SSL
|
||||
d['SSLserver'] = 1
|
||||
except:
|
||||
d['SSLserver'] = 0
|
||||
|
||||
# Client SSL support if socket.ssl available
|
||||
try:
|
||||
from socket import ssl
|
||||
d['SSLclient'] = 1
|
||||
except:
|
||||
d['SSLclient'] = 0
|
||||
|
||||
for k, v in kw.items():
|
||||
if k[0] != '_':
|
||||
setattr(self, k, v)
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
if name in self.__readonly:
|
||||
raise AttributeError, "readonly configuration setting"
|
||||
|
||||
d = self.__dict__
|
||||
|
||||
if name in ('typesNamespace', 'typesNamespaceURI',
|
||||
'schemaNamespace', 'schemaNamespaceURI'):
|
||||
|
||||
if name[-3:] == 'URI':
|
||||
base, uri = name[:-3], 1
|
||||
else:
|
||||
base, uri = name, 0
|
||||
|
||||
if type(value) == StringType:
|
||||
if NS.NSMAP.has_key(value):
|
||||
n = (value, NS.NSMAP[value])
|
||||
elif NS.NSMAP_R.has_key(value):
|
||||
n = (NS.NSMAP_R[value], value)
|
||||
else:
|
||||
raise AttributeError, "unknown namespace"
|
||||
elif type(value) in (ListType, TupleType):
|
||||
if uri:
|
||||
n = (value[1], value[0])
|
||||
else:
|
||||
n = (value[0], value[1])
|
||||
else:
|
||||
raise AttributeError, "unknown namespace type"
|
||||
|
||||
d[base], d[base + 'URI'] = n
|
||||
|
||||
try:
|
||||
d['namespaceStyle'] = \
|
||||
NS.STMAP_R[(d['typesNamespace'], d['schemaNamespace'])]
|
||||
except:
|
||||
d['namespaceStyle'] = ''
|
||||
|
||||
elif name == 'namespaceStyle':
|
||||
value = str(value)
|
||||
|
||||
if not NS.STMAP.has_key(value):
|
||||
raise AttributeError, "unknown namespace style"
|
||||
|
||||
d[name] = value
|
||||
n = d['typesNamespace'] = NS.STMAP[value][0]
|
||||
d['typesNamespaceURI'] = NS.NSMAP[n]
|
||||
n = d['schemaNamespace'] = NS.STMAP[value][1]
|
||||
d['schemaNamespaceURI'] = NS.NSMAP[n]
|
||||
|
||||
elif name == 'debug':
|
||||
d[name] = \
|
||||
d['returnFaultInfo'] = \
|
||||
d['dumpHeadersIn'] = \
|
||||
d['dumpHeadersOut'] = \
|
||||
d['dumpSOAPIn'] = \
|
||||
d['dumpSOAPOut'] = value
|
||||
|
||||
else:
|
||||
d[name] = value
|
||||
|
||||
|
||||
Config = SOAPConfig()
|
@ -1,79 +0,0 @@
|
||||
"""
|
||||
################################################################################
|
||||
#
|
||||
# SOAPpy - Cayce Ullman (cayce@actzero.com)
|
||||
# Brian Matthews (blm@actzero.com)
|
||||
# Gregory Warnes (gregory_r_warnes@groton.pfizer.com)
|
||||
# Christopher Blunck (blunck@gst.com)
|
||||
#
|
||||
################################################################################
|
||||
# Copyright (c) 2003, Pfizer
|
||||
# Copyright (c) 2001, Cayce Ullman.
|
||||
# Copyright (c) 2001, Brian Matthews.
|
||||
#
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
# Redistributions of source code must retain the above copyright notice, this
|
||||
# list of conditions and the following disclaimer.
|
||||
#
|
||||
# Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
#
|
||||
# Neither the name of actzero, inc. nor the names of its contributors may
|
||||
# be used to endorse or promote products derived from this software without
|
||||
# specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
|
||||
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#
|
||||
################################################################################
|
||||
"""
|
||||
|
||||
ident = '$Id$'
|
||||
from version import __version__
|
||||
|
||||
import exceptions
|
||||
|
||||
################################################################################
|
||||
# Exceptions
|
||||
################################################################################
|
||||
class Error(exceptions.Exception):
|
||||
def __init__(self, msg):
|
||||
self.msg = msg
|
||||
def __str__(self):
|
||||
return "<Error : %s>" % self.msg
|
||||
__repr__ = __str__
|
||||
def __call__(self):
|
||||
return (msg,)
|
||||
|
||||
class RecursionError(Error):
|
||||
pass
|
||||
|
||||
class UnknownTypeError(Error):
|
||||
pass
|
||||
|
||||
class HTTPError(Error):
|
||||
# indicates an HTTP protocol error
|
||||
def __init__(self, code, msg):
|
||||
self.code = code
|
||||
self.msg = msg
|
||||
def __str__(self):
|
||||
return "<HTTPError %s %s>" % (self.code, self.msg)
|
||||
__repr__ = __str__
|
||||
def __call___(self):
|
||||
return (self.code, self.msg, )
|
||||
|
||||
class UnderflowError(exceptions.ArithmeticError):
|
||||
pass
|
||||
|
@ -1,143 +0,0 @@
|
||||
"""
|
||||
GSIServer - Contributed by Ivan R. Judson <judson@mcs.anl.gov>
|
||||
|
||||
|
||||
################################################################################
|
||||
#
|
||||
# SOAPpy - Cayce Ullman (cayce@actzero.com)
|
||||
# Brian Matthews (blm@actzero.com)
|
||||
# Gregory Warnes (gregory_r_warnes@groton.pfizer.com)
|
||||
# Christopher Blunck (blunck@gst.com)
|
||||
#
|
||||
################################################################################
|
||||
# Copyright (c) 2003, Pfizer
|
||||
# Copyright (c) 2001, Cayce Ullman.
|
||||
# Copyright (c) 2001, Brian Matthews.
|
||||
#
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
# Redistributions of source code must retain the above copyright notice, this
|
||||
# list of conditions and the following disclaimer.
|
||||
#
|
||||
# Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
#
|
||||
# Neither the name of actzero, inc. nor the names of its contributors may
|
||||
# be used to endorse or promote products derived from this software without
|
||||
# specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
|
||||
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#
|
||||
################################################################################
|
||||
"""
|
||||
|
||||
ident = '$Id$'
|
||||
from version import __version__
|
||||
|
||||
from __future__ import nested_scopes
|
||||
|
||||
#import xml.sax
|
||||
import re
|
||||
import socket
|
||||
import sys
|
||||
import SocketServer
|
||||
from types import *
|
||||
import BaseHTTPServer
|
||||
|
||||
# SOAPpy modules
|
||||
from Parser import parseSOAPRPC
|
||||
from Config import SOAPConfig
|
||||
from Types import faultType, voidType, simplify
|
||||
from NS import NS
|
||||
from SOAPBuilder import buildSOAP
|
||||
from Utilities import debugHeader, debugFooter
|
||||
|
||||
try: from M2Crypto import SSL
|
||||
except: pass
|
||||
|
||||
#####
|
||||
|
||||
from Server import *
|
||||
|
||||
from pyGlobus.io import GSITCPSocketServer, ThreadingGSITCPSocketServer
|
||||
from pyGlobus import ioc
|
||||
|
||||
def GSIConfig():
|
||||
config = SOAPConfig()
|
||||
config.channel_mode = ioc.GLOBUS_IO_SECURE_CHANNEL_MODE_GSI_WRAP
|
||||
config.delegation_mode = ioc.GLOBUS_IO_SECURE_DELEGATION_MODE_FULL_PROXY
|
||||
config.tcpAttr = None
|
||||
config.authMethod = "_authorize"
|
||||
return config
|
||||
|
||||
Config = GSIConfig()
|
||||
|
||||
class GSISOAPServer(GSITCPSocketServer, SOAPServerBase):
|
||||
def __init__(self, addr = ('localhost', 8000),
|
||||
RequestHandler = SOAPRequestHandler, log = 0,
|
||||
encoding = 'UTF-8', config = Config, namespace = None):
|
||||
|
||||
# Test the encoding, raising an exception if it's not known
|
||||
if encoding != None:
|
||||
''.encode(encoding)
|
||||
|
||||
self.namespace = namespace
|
||||
self.objmap = {}
|
||||
self.funcmap = {}
|
||||
self.encoding = encoding
|
||||
self.config = config
|
||||
self.log = log
|
||||
|
||||
self.allow_reuse_address= 1
|
||||
|
||||
GSITCPSocketServer.__init__(self, addr, RequestHandler,
|
||||
self.config.channel_mode,
|
||||
self.config.delegation_mode,
|
||||
tcpAttr = self.config.tcpAttr)
|
||||
|
||||
def get_request(self):
|
||||
sock, addr = GSITCPSocketServer.get_request(self)
|
||||
|
||||
return sock, addr
|
||||
|
||||
class ThreadingGSISOAPServer(ThreadingGSITCPSocketServer, SOAPServerBase):
|
||||
|
||||
def __init__(self, addr = ('localhost', 8000),
|
||||
RequestHandler = SOAPRequestHandler, log = 0,
|
||||
encoding = 'UTF-8', config = Config, namespace = None):
|
||||
|
||||
# Test the encoding, raising an exception if it's not known
|
||||
if encoding != None:
|
||||
''.encode(encoding)
|
||||
|
||||
self.namespace = namespace
|
||||
self.objmap = {}
|
||||
self.funcmap = {}
|
||||
self.encoding = encoding
|
||||
self.config = config
|
||||
self.log = log
|
||||
|
||||
self.allow_reuse_address= 1
|
||||
|
||||
ThreadingGSITCPSocketServer.__init__(self, addr, RequestHandler,
|
||||
self.config.channel_mode,
|
||||
self.config.delegation_mode,
|
||||
tcpAttr = self.config.tcpAttr)
|
||||
|
||||
def get_request(self):
|
||||
sock, addr = ThreadingGSITCPSocketServer.get_request(self)
|
||||
|
||||
return sock, addr
|
||||
|
@ -1,104 +0,0 @@
|
||||
"""
|
||||
################################################################################
|
||||
#
|
||||
# SOAPpy - Cayce Ullman (cayce@actzero.com)
|
||||
# Brian Matthews (blm@actzero.com)
|
||||
# Gregory Warnes (gregory_r_warnes@groton.pfizer.com)
|
||||
# Christopher Blunck (blunck@gst.com)
|
||||
#
|
||||
################################################################################
|
||||
# Copyright (c) 2003, Pfizer
|
||||
# Copyright (c) 2001, Cayce Ullman.
|
||||
# Copyright (c) 2001, Brian Matthews.
|
||||
#
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
# Redistributions of source code must retain the above copyright notice, this
|
||||
# list of conditions and the following disclaimer.
|
||||
#
|
||||
# Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
#
|
||||
# Neither the name of actzero, inc. nor the names of its contributors may
|
||||
# be used to endorse or promote products derived from this software without
|
||||
# specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
|
||||
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#
|
||||
################################################################################
|
||||
"""
|
||||
|
||||
from __future__ import nested_scopes
|
||||
|
||||
ident = '$Id$'
|
||||
from version import __version__
|
||||
|
||||
##############################################################################
|
||||
# Namespace Class
|
||||
################################################################################
|
||||
def invertDict(dict):
|
||||
d = {}
|
||||
|
||||
for k, v in dict.items():
|
||||
d[v] = k
|
||||
|
||||
return d
|
||||
|
||||
class NS:
|
||||
XML = "http://www.w3.org/XML/1998/namespace"
|
||||
|
||||
ENV = "http://schemas.xmlsoap.org/soap/envelope/"
|
||||
ENC = "http://schemas.xmlsoap.org/soap/encoding/"
|
||||
|
||||
XSD = "http://www.w3.org/1999/XMLSchema"
|
||||
XSD2 = "http://www.w3.org/2000/10/XMLSchema"
|
||||
XSD3 = "http://www.w3.org/2001/XMLSchema"
|
||||
|
||||
XSD_L = [XSD, XSD2, XSD3]
|
||||
EXSD_L= [ENC, XSD, XSD2, XSD3]
|
||||
|
||||
XSI = "http://www.w3.org/1999/XMLSchema-instance"
|
||||
XSI2 = "http://www.w3.org/2000/10/XMLSchema-instance"
|
||||
XSI3 = "http://www.w3.org/2001/XMLSchema-instance"
|
||||
XSI_L = [XSI, XSI2, XSI3]
|
||||
|
||||
URN = "http://soapinterop.org/xsd"
|
||||
|
||||
# For generated messages
|
||||
XML_T = "xml"
|
||||
ENV_T = "SOAP-ENV"
|
||||
ENC_T = "SOAP-ENC"
|
||||
XSD_T = "xsd"
|
||||
XSD2_T= "xsd2"
|
||||
XSD3_T= "xsd3"
|
||||
XSI_T = "xsi"
|
||||
XSI2_T= "xsi2"
|
||||
XSI3_T= "xsi3"
|
||||
URN_T = "urn"
|
||||
|
||||
NSMAP = {ENV_T: ENV, ENC_T: ENC, XSD_T: XSD, XSD2_T: XSD2,
|
||||
XSD3_T: XSD3, XSI_T: XSI, XSI2_T: XSI2, XSI3_T: XSI3,
|
||||
URN_T: URN}
|
||||
NSMAP_R = invertDict(NSMAP)
|
||||
|
||||
STMAP = {'1999': (XSD_T, XSI_T), '2000': (XSD2_T, XSI2_T),
|
||||
'2001': (XSD3_T, XSI3_T)}
|
||||
STMAP_R = invertDict(STMAP)
|
||||
|
||||
def __init__(self):
|
||||
raise Error, "Don't instantiate this"
|
||||
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,40 +0,0 @@
|
||||
"""This file is here for backward compatibility with versions <= 0.9.9
|
||||
|
||||
Delete when 1.0.0 is released!
|
||||
"""
|
||||
|
||||
ident = '$Id$'
|
||||
from version import __version__
|
||||
|
||||
from Client import *
|
||||
from Config import *
|
||||
from Errors import *
|
||||
from NS import *
|
||||
from Parser import *
|
||||
from SOAPBuilder import *
|
||||
from Server import *
|
||||
from Types import *
|
||||
from Utilities import *
|
||||
import wstools
|
||||
import WSDL
|
||||
|
||||
from warnings import warn
|
||||
|
||||
warn("""
|
||||
|
||||
The sub-module SOAPpy.SOAP is deprecated and is only
|
||||
provided for short-term backward compatibility. Objects are now
|
||||
available directly within the SOAPpy module. Thus, instead of
|
||||
|
||||
from SOAPpy import SOAP
|
||||
...
|
||||
SOAP.SOAPProxy(...)
|
||||
|
||||
use
|
||||
|
||||
from SOAPpy import SOAPProxy
|
||||
...
|
||||
SOAPProxy(...)
|
||||
|
||||
instead.
|
||||
""", DeprecationWarning)
|
@ -1,620 +0,0 @@
|
||||
"""
|
||||
################################################################################
|
||||
# Copyright (c) 2003, Pfizer
|
||||
# Copyright (c) 2001, Cayce Ullman.
|
||||
# Copyright (c) 2001, Brian Matthews.
|
||||
#
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
# Redistributions of source code must retain the above copyright notice, this
|
||||
# list of conditions and the following disclaimer.
|
||||
#
|
||||
# Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
#
|
||||
# Neither the name of actzero, inc. nor the names of its contributors may
|
||||
# be used to endorse or promote products derived from this software without
|
||||
# specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
|
||||
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#
|
||||
################################################################################
|
||||
"""
|
||||
|
||||
ident = '$Id$'
|
||||
from version import __version__
|
||||
|
||||
import cgi
|
||||
import copy
|
||||
from wstools.XMLname import toXMLname, fromXMLname
|
||||
import fpconst
|
||||
|
||||
# SOAPpy modules
|
||||
from Config import Config
|
||||
from NS import NS
|
||||
from Types import *
|
||||
|
||||
# Test whether this Python version has Types.BooleanType
|
||||
# If it doesn't have it, then False and True are serialized as integers
|
||||
try:
|
||||
BooleanType
|
||||
pythonHasBooleanType = 1
|
||||
except NameError:
|
||||
pythonHasBooleanType = 0
|
||||
|
||||
################################################################################
|
||||
# SOAP Builder
|
||||
################################################################################
|
||||
class SOAPBuilder:
|
||||
_xml_top = '<?xml version="1.0"?>\n'
|
||||
_xml_enc_top = '<?xml version="1.0" encoding="%s"?>\n'
|
||||
_env_top = '%(ENV_T)s:Envelope %(ENV_T)s:encodingStyle="%(ENC)s"' % \
|
||||
NS.__dict__
|
||||
_env_bot = '</%(ENV_T)s:Envelope>\n' % NS.__dict__
|
||||
|
||||
# Namespaces potentially defined in the Envelope tag.
|
||||
|
||||
_env_ns = {NS.ENC: NS.ENC_T, NS.ENV: NS.ENV_T,
|
||||
NS.XSD: NS.XSD_T, NS.XSD2: NS.XSD2_T, NS.XSD3: NS.XSD3_T,
|
||||
NS.XSI: NS.XSI_T, NS.XSI2: NS.XSI2_T, NS.XSI3: NS.XSI3_T}
|
||||
|
||||
def __init__(self, args = (), kw = {}, method = None, namespace = None,
|
||||
header = None, methodattrs = None, envelope = 1, encoding = 'UTF-8',
|
||||
use_refs = 0, config = Config, noroot = 0):
|
||||
|
||||
# Test the encoding, raising an exception if it's not known
|
||||
if encoding != None:
|
||||
''.encode(encoding)
|
||||
|
||||
self.args = args
|
||||
self.kw = kw
|
||||
self.envelope = envelope
|
||||
self.encoding = encoding
|
||||
self.method = method
|
||||
self.namespace = namespace
|
||||
self.header = header
|
||||
self.methodattrs= methodattrs
|
||||
self.use_refs = use_refs
|
||||
self.config = config
|
||||
self.out = []
|
||||
self.tcounter = 0
|
||||
self.ncounter = 1
|
||||
self.icounter = 1
|
||||
self.envns = {}
|
||||
self.ids = {}
|
||||
self.depth = 0
|
||||
self.multirefs = []
|
||||
self.multis = 0
|
||||
self.body = not isinstance(args, bodyType)
|
||||
self.noroot = noroot
|
||||
|
||||
def build(self):
|
||||
if Config.debug: print "In build."
|
||||
ns_map = {}
|
||||
|
||||
# Cache whether typing is on or not
|
||||
typed = self.config.typed
|
||||
|
||||
if self.header:
|
||||
# Create a header.
|
||||
self.dump(self.header, "Header", typed = typed)
|
||||
self.header = None # Wipe it out so no one is using it.
|
||||
|
||||
if self.body:
|
||||
# Call genns to record that we've used SOAP-ENV.
|
||||
self.depth += 1
|
||||
body_ns = self.genns(ns_map, NS.ENV)[0]
|
||||
self.out.append("<%sBody>\n" % body_ns)
|
||||
|
||||
if self.method:
|
||||
self.depth += 1
|
||||
a = ''
|
||||
if self.methodattrs:
|
||||
for (k, v) in self.methodattrs.items():
|
||||
a += ' %s="%s"' % (k, v)
|
||||
|
||||
if self.namespace: # Use the namespace info handed to us
|
||||
methodns, n = self.genns(ns_map, self.namespace)
|
||||
else:
|
||||
methodns, n = '', ''
|
||||
|
||||
self.out.append('<%s%s%s%s%s>\n' % (
|
||||
methodns, self.method, n, a, self.genroot(ns_map)))
|
||||
|
||||
try:
|
||||
if type(self.args) != TupleType:
|
||||
args = (self.args,)
|
||||
else:
|
||||
args = self.args
|
||||
|
||||
for i in args:
|
||||
self.dump(i, typed = typed, ns_map = ns_map)
|
||||
|
||||
if hasattr(self.config, "argsOrdering") and self.config.argsOrdering.has_key(self.method):
|
||||
for k in self.config.argsOrdering.get(self.method):
|
||||
self.dump(self.kw.get(k), k, typed = typed, ns_map = ns_map)
|
||||
else:
|
||||
for (k, v) in self.kw.items():
|
||||
self.dump(v, k, typed = typed, ns_map = ns_map)
|
||||
|
||||
except RecursionError:
|
||||
if self.use_refs == 0:
|
||||
# restart
|
||||
b = SOAPBuilder(args = self.args, kw = self.kw,
|
||||
method = self.method, namespace = self.namespace,
|
||||
header = self.header, methodattrs = self.methodattrs,
|
||||
envelope = self.envelope, encoding = self.encoding,
|
||||
use_refs = 1, config = self.config)
|
||||
return b.build()
|
||||
raise
|
||||
|
||||
if self.method:
|
||||
self.out.append("</%s%s>\n" % (methodns, self.method))
|
||||
self.depth -= 1
|
||||
|
||||
if self.body:
|
||||
# dump may add to self.multirefs, but the for loop will keep
|
||||
# going until it has used all of self.multirefs, even those
|
||||
# entries added while in the loop.
|
||||
|
||||
self.multis = 1
|
||||
|
||||
for obj, tag in self.multirefs:
|
||||
self.dump(obj, tag, typed = typed, ns_map = ns_map)
|
||||
|
||||
self.out.append("</%sBody>\n" % body_ns)
|
||||
self.depth -= 1
|
||||
|
||||
if self.envelope:
|
||||
e = map (lambda ns: ' xmlns:%s="%s"' % (ns[1], ns[0]),
|
||||
self.envns.items())
|
||||
|
||||
self.out = ['<', self._env_top] + e + ['>\n'] + \
|
||||
self.out + \
|
||||
[self._env_bot]
|
||||
|
||||
if self.encoding != None:
|
||||
self.out.insert(0, self._xml_enc_top % self.encoding)
|
||||
return ''.join(self.out).encode(self.encoding)
|
||||
|
||||
self.out.insert(0, self._xml_top)
|
||||
return ''.join(self.out)
|
||||
|
||||
def gentag(self):
|
||||
if Config.debug: print "In gentag."
|
||||
self.tcounter += 1
|
||||
return "v%d" % self.tcounter
|
||||
|
||||
def genns(self, ns_map, nsURI):
|
||||
if nsURI == None:
|
||||
return ('', '')
|
||||
|
||||
if type(nsURI) == TupleType: # already a tuple
|
||||
if len(nsURI) == 2:
|
||||
ns, nsURI = nsURI
|
||||
else:
|
||||
ns, nsURI = None, nsURI[0]
|
||||
else:
|
||||
ns = None
|
||||
|
||||
if ns_map.has_key(nsURI):
|
||||
return (ns_map[nsURI] + ':', '')
|
||||
|
||||
if self._env_ns.has_key(nsURI):
|
||||
ns = self.envns[nsURI] = ns_map[nsURI] = self._env_ns[nsURI]
|
||||
return (ns + ':', '')
|
||||
|
||||
if not ns:
|
||||
ns = "ns%d" % self.ncounter
|
||||
self.ncounter += 1
|
||||
ns_map[nsURI] = ns
|
||||
if self.config.buildWithNamespacePrefix:
|
||||
return (ns + ':', ' xmlns:%s="%s"' % (ns, nsURI))
|
||||
else:
|
||||
return ('', ' xmlns="%s"' % (nsURI))
|
||||
|
||||
def genroot(self, ns_map):
|
||||
if self.noroot:
|
||||
return ''
|
||||
|
||||
if self.depth != 2:
|
||||
return ''
|
||||
|
||||
ns, n = self.genns(ns_map, NS.ENC)
|
||||
return ' %sroot="%d"%s' % (ns, not self.multis, n)
|
||||
|
||||
# checkref checks an element to see if it needs to be encoded as a
|
||||
# multi-reference element or not. If it returns None, the element has
|
||||
# been handled and the caller can continue with subsequent elements.
|
||||
# If it returns a string, the string should be included in the opening
|
||||
# tag of the marshaled element.
|
||||
|
||||
def checkref(self, obj, tag, ns_map):
|
||||
if self.depth < 2:
|
||||
return ''
|
||||
|
||||
if not self.ids.has_key(id(obj)):
|
||||
n = self.ids[id(obj)] = self.icounter
|
||||
self.icounter = n + 1
|
||||
|
||||
if self.use_refs == 0:
|
||||
return ''
|
||||
|
||||
if self.depth == 2:
|
||||
return ' id="i%d"' % n
|
||||
|
||||
self.multirefs.append((obj, tag))
|
||||
else:
|
||||
if self.use_refs == 0:
|
||||
raise RecursionError, "Cannot serialize recursive object"
|
||||
|
||||
n = self.ids[id(obj)]
|
||||
|
||||
if self.multis and self.depth == 2:
|
||||
return ' id="i%d"' % n
|
||||
|
||||
self.out.append('<%s href="#i%d"%s/>\n' %
|
||||
(tag, n, self.genroot(ns_map)))
|
||||
return None
|
||||
|
||||
# dumpers
|
||||
|
||||
def dump(self, obj, tag = None, typed = 1, ns_map = {}):
|
||||
if Config.debug: print "In dump.", "obj=", obj
|
||||
ns_map = ns_map.copy()
|
||||
self.depth += 1
|
||||
|
||||
if type(tag) not in (NoneType, StringType, UnicodeType):
|
||||
raise KeyError, "tag must be a string or None"
|
||||
|
||||
try:
|
||||
meth = getattr(self, "dump_" + type(obj).__name__)
|
||||
except AttributeError:
|
||||
if type(obj) == LongType:
|
||||
obj_type = "integer"
|
||||
elif pythonHasBooleanType and type(obj) == BooleanType:
|
||||
obj_type = "boolean"
|
||||
else:
|
||||
obj_type = type(obj).__name__
|
||||
|
||||
self.out.append(self.dumper(None, obj_type, obj, tag, typed,
|
||||
ns_map, self.genroot(ns_map)))
|
||||
else:
|
||||
meth(obj, tag, typed, ns_map)
|
||||
|
||||
|
||||
self.depth -= 1
|
||||
|
||||
# generic dumper
|
||||
def dumper(self, nsURI, obj_type, obj, tag, typed = 1, ns_map = {},
|
||||
rootattr = '', id = '',
|
||||
xml = '<%(tag)s%(type)s%(id)s%(attrs)s%(root)s>%(data)s</%(tag)s>\n'):
|
||||
if Config.debug: print "In dumper."
|
||||
|
||||
if nsURI == None:
|
||||
nsURI = self.config.typesNamespaceURI
|
||||
|
||||
tag = tag or self.gentag()
|
||||
|
||||
tag = toXMLname(tag) # convert from SOAP 1.2 XML name encoding
|
||||
|
||||
a = n = t = ''
|
||||
if typed and obj_type:
|
||||
ns, n = self.genns(ns_map, nsURI)
|
||||
ins = self.genns(ns_map, self.config.schemaNamespaceURI)[0]
|
||||
t = ' %stype="%s%s"%s' % (ins, ns, obj_type, n)
|
||||
|
||||
try: a = obj._marshalAttrs(ns_map, self)
|
||||
except: pass
|
||||
|
||||
try: data = obj._marshalData()
|
||||
except:
|
||||
if (obj_type != "string"): # strings are already encoded
|
||||
data = cgi.escape(str(obj))
|
||||
else:
|
||||
data = obj
|
||||
|
||||
|
||||
return xml % {"tag": tag, "type": t, "data": data, "root": rootattr,
|
||||
"id": id, "attrs": a}
|
||||
|
||||
def dump_float(self, obj, tag, typed = 1, ns_map = {}):
|
||||
if Config.debug: print "In dump_float."
|
||||
tag = tag or self.gentag()
|
||||
|
||||
tag = toXMLname(tag) # convert from SOAP 1.2 XML name encoding
|
||||
|
||||
if Config.strict_range:
|
||||
doubleType(obj)
|
||||
|
||||
if fpconst.isPosInf(obj):
|
||||
obj = "INF"
|
||||
elif fpconst.isNegInf(obj):
|
||||
obj = "-INF"
|
||||
elif fpconst.isNaN(obj):
|
||||
obj = "NaN"
|
||||
else:
|
||||
obj = str(obj)
|
||||
|
||||
# Note: python 'float' is actually a SOAP 'double'.
|
||||
self.out.append(self.dumper(None, "double", obj, tag, typed, ns_map,
|
||||
self.genroot(ns_map)))
|
||||
|
||||
def dump_string(self, obj, tag, typed = 0, ns_map = {}):
|
||||
if Config.debug: print "In dump_string."
|
||||
tag = tag or self.gentag()
|
||||
tag = toXMLname(tag) # convert from SOAP 1.2 XML name encoding
|
||||
|
||||
id = self.checkref(obj, tag, ns_map)
|
||||
if id == None:
|
||||
return
|
||||
|
||||
try: data = obj._marshalData()
|
||||
except: data = obj
|
||||
|
||||
self.out.append(self.dumper(None, "string", cgi.escape(data), tag,
|
||||
typed, ns_map, self.genroot(ns_map), id))
|
||||
|
||||
dump_str = dump_string # For Python 2.2+
|
||||
dump_unicode = dump_string
|
||||
|
||||
def dump_None(self, obj, tag, typed = 0, ns_map = {}):
|
||||
if Config.debug: print "In dump_None."
|
||||
tag = tag or self.gentag()
|
||||
tag = toXMLname(tag) # convert from SOAP 1.2 XML name encoding
|
||||
ns = self.genns(ns_map, self.config.schemaNamespaceURI)[0]
|
||||
|
||||
self.out.append('<%s %snull="1"%s/>\n' %
|
||||
(tag, ns, self.genroot(ns_map)))
|
||||
|
||||
dump_NoneType = dump_None # For Python 2.2+
|
||||
|
||||
def dump_list(self, obj, tag, typed = 1, ns_map = {}):
|
||||
if Config.debug: print "In dump_list.", "obj=", obj
|
||||
tag = tag or self.gentag()
|
||||
tag = toXMLname(tag) # convert from SOAP 1.2 XML name encoding
|
||||
|
||||
if type(obj) == InstanceType:
|
||||
data = obj.data
|
||||
else:
|
||||
data = obj
|
||||
|
||||
id = self.checkref(obj, tag, ns_map)
|
||||
if id == None:
|
||||
return
|
||||
|
||||
try:
|
||||
sample = data[0]
|
||||
empty = 0
|
||||
except:
|
||||
# preserve type if present
|
||||
if getattr(obj,"_typed",None) and getattr(obj,"_type",None):
|
||||
if getattr(obj, "_complexType", None):
|
||||
sample = typedArrayType(typed=obj._type,
|
||||
complexType = obj._complexType)
|
||||
sample._typename = obj._type
|
||||
obj._ns = NS.URN
|
||||
else:
|
||||
sample = typedArrayType(typed=obj._type)
|
||||
else:
|
||||
sample = structType()
|
||||
empty = 1
|
||||
|
||||
# First scan list to see if all are the same type
|
||||
same_type = 1
|
||||
|
||||
if not empty:
|
||||
for i in data[1:]:
|
||||
if type(sample) != type(i) or \
|
||||
(type(sample) == InstanceType and \
|
||||
sample.__class__ != i.__class__):
|
||||
same_type = 0
|
||||
break
|
||||
|
||||
ndecl = ''
|
||||
if same_type:
|
||||
if (isinstance(sample, structType)) or \
|
||||
type(sample) == DictType or \
|
||||
(isinstance(sample, anyType) and \
|
||||
(getattr(sample, "_complexType", None) and \
|
||||
sample._complexType)): # force to urn struct
|
||||
try:
|
||||
tns = obj._ns or NS.URN
|
||||
except:
|
||||
tns = NS.URN
|
||||
|
||||
ns, ndecl = self.genns(ns_map, tns)
|
||||
|
||||
try:
|
||||
typename = sample._typename
|
||||
except:
|
||||
typename = "SOAPStruct"
|
||||
|
||||
t = ns + typename
|
||||
|
||||
elif isinstance(sample, anyType):
|
||||
ns = sample._validNamespaceURI(self.config.typesNamespaceURI,
|
||||
self.config.strictNamespaces)
|
||||
if ns:
|
||||
ns, ndecl = self.genns(ns_map, ns)
|
||||
t = ns + sample._type
|
||||
else:
|
||||
t = 'ur-type'
|
||||
else:
|
||||
typename = type(sample).__name__
|
||||
|
||||
# For Python 2.2+
|
||||
if type(sample) == StringType: typename = 'string'
|
||||
|
||||
# HACK: python 'float' is actually a SOAP 'double'.
|
||||
if typename=="float": typename="double"
|
||||
t = self.genns(ns_map, self.config.typesNamespaceURI)[0] + \
|
||||
typename
|
||||
|
||||
else:
|
||||
t = self.genns(ns_map, self.config.typesNamespaceURI)[0] + \
|
||||
"ur-type"
|
||||
|
||||
try: a = obj._marshalAttrs(ns_map, self)
|
||||
except: a = ''
|
||||
|
||||
ens, edecl = self.genns(ns_map, NS.ENC)
|
||||
ins, idecl = self.genns(ns_map, self.config.schemaNamespaceURI)
|
||||
|
||||
self.out.append(
|
||||
'<%s %sarrayType="%s[%d]" %stype="%sArray"%s%s%s%s%s%s>\n' %
|
||||
(tag, ens, t, len(data), ins, ens, ndecl, edecl, idecl,
|
||||
self.genroot(ns_map), id, a))
|
||||
|
||||
typed = not same_type
|
||||
|
||||
try: elemsname = obj._elemsname
|
||||
except: elemsname = "item"
|
||||
|
||||
for i in data:
|
||||
self.dump(i, elemsname, typed, ns_map)
|
||||
|
||||
self.out.append('</%s>\n' % tag)
|
||||
|
||||
dump_tuple = dump_list
|
||||
|
||||
def dump_dictionary(self, obj, tag, typed = 1, ns_map = {}):
|
||||
if Config.debug: print "In dump_dictionary."
|
||||
tag = tag or self.gentag()
|
||||
tag = toXMLname(tag) # convert from SOAP 1.2 XML name encoding
|
||||
|
||||
id = self.checkref(obj, tag, ns_map)
|
||||
if id == None:
|
||||
return
|
||||
|
||||
try: a = obj._marshalAttrs(ns_map, self)
|
||||
except: a = ''
|
||||
|
||||
self.out.append('<%s%s%s%s>\n' %
|
||||
(tag, id, a, self.genroot(ns_map)))
|
||||
|
||||
for (k, v) in obj.items():
|
||||
if k[0] != "_":
|
||||
self.dump(v, k, 1, ns_map)
|
||||
|
||||
self.out.append('</%s>\n' % tag)
|
||||
|
||||
dump_dict = dump_dictionary # For Python 2.2+
|
||||
|
||||
def dump_instance(self, obj, tag, typed = 1, ns_map = {}):
|
||||
if Config.debug: print "In dump_instance.", "obj=", obj, "tag=", tag
|
||||
if not tag:
|
||||
# If it has a name use it.
|
||||
if isinstance(obj, anyType) and obj._name:
|
||||
tag = obj._name
|
||||
else:
|
||||
tag = self.gentag()
|
||||
tag = toXMLname(tag) # convert from SOAP 1.2 XML name encoding
|
||||
|
||||
if isinstance(obj, arrayType): # Array
|
||||
self.dump_list(obj, tag, typed, ns_map)
|
||||
return
|
||||
|
||||
if isinstance(obj, faultType): # Fault
|
||||
cns, cdecl = self.genns(ns_map, NS.ENC)
|
||||
vns, vdecl = self.genns(ns_map, NS.ENV)
|
||||
self.out.append('''<%sFault %sroot="1"%s%s>
|
||||
<faultcode>%s</faultcode>
|
||||
<faultstring>%s</faultstring>
|
||||
''' % (vns, cns, vdecl, cdecl, obj.faultcode, obj.faultstring))
|
||||
if hasattr(obj, "detail"):
|
||||
self.dump(obj.detail, "detail", typed, ns_map)
|
||||
self.out.append("</%sFault>\n" % vns)
|
||||
return
|
||||
|
||||
r = self.genroot(ns_map)
|
||||
|
||||
try: a = obj._marshalAttrs(ns_map, self)
|
||||
except: a = ''
|
||||
|
||||
if isinstance(obj, voidType): # void
|
||||
self.out.append("<%s%s%s></%s>\n" % (tag, a, r, tag))
|
||||
return
|
||||
|
||||
id = self.checkref(obj, tag, ns_map)
|
||||
if id == None:
|
||||
return
|
||||
|
||||
if isinstance(obj, structType):
|
||||
# Check for namespace
|
||||
ndecl = ''
|
||||
ns = obj._validNamespaceURI(self.config.typesNamespaceURI,
|
||||
self.config.strictNamespaces)
|
||||
if ns:
|
||||
ns, ndecl = self.genns(ns_map, ns)
|
||||
tag = ns + tag
|
||||
self.out.append("<%s%s%s%s%s>\n" % (tag, ndecl, id, a, r))
|
||||
|
||||
keylist = obj.__dict__.keys()
|
||||
|
||||
# first write out items with order information
|
||||
if hasattr(obj, '_keyord'):
|
||||
for i in range(len(obj._keyord)):
|
||||
self.dump(obj._aslist(i), obj._keyord[i], 1, ns_map)
|
||||
keylist.remove(obj._keyord[i])
|
||||
|
||||
# now write out the rest
|
||||
for k in keylist:
|
||||
if (k[0] != "_"):
|
||||
self.dump(getattr(obj,k), k, 1, ns_map)
|
||||
|
||||
if isinstance(obj, bodyType):
|
||||
self.multis = 1
|
||||
|
||||
for v, k in self.multirefs:
|
||||
self.dump(v, k, typed = typed, ns_map = ns_map)
|
||||
|
||||
self.out.append('</%s>\n' % tag)
|
||||
|
||||
elif isinstance(obj, anyType):
|
||||
t = ''
|
||||
|
||||
if typed:
|
||||
ns = obj._validNamespaceURI(self.config.typesNamespaceURI,
|
||||
self.config.strictNamespaces)
|
||||
if ns:
|
||||
ons, ondecl = self.genns(ns_map, ns)
|
||||
ins, indecl = self.genns(ns_map,
|
||||
self.config.schemaNamespaceURI)
|
||||
t = ' %stype="%s%s"%s%s' % \
|
||||
(ins, ons, obj._type, ondecl, indecl)
|
||||
|
||||
self.out.append('<%s%s%s%s%s>%s</%s>\n' %
|
||||
(tag, t, id, a, r, obj._marshalData(), tag))
|
||||
|
||||
else: # Some Class
|
||||
self.out.append('<%s%s%s>\n' % (tag, id, r))
|
||||
|
||||
for (k, v) in obj.__dict__.items():
|
||||
if k[0] != "_":
|
||||
self.dump(v, k, 1, ns_map)
|
||||
|
||||
self.out.append('</%s>\n' % tag)
|
||||
|
||||
|
||||
################################################################################
|
||||
# SOAPBuilder's more public interface
|
||||
################################################################################
|
||||
def buildSOAP(args=(), kw={}, method=None, namespace=None, header=None,
|
||||
methodattrs=None,envelope=1,encoding='UTF-8',config=Config,noroot = 0):
|
||||
t = SOAPBuilder(args=args,kw=kw, method=method, namespace=namespace,
|
||||
header=header, methodattrs=methodattrs,envelope=envelope,
|
||||
encoding=encoding, config=config,noroot=noroot)
|
||||
return t.build()
|
@ -1,706 +0,0 @@
|
||||
"""
|
||||
################################################################################
|
||||
#
|
||||
# SOAPpy - Cayce Ullman (cayce@actzero.com)
|
||||
# Brian Matthews (blm@actzero.com)
|
||||
# Gregory Warnes (gregory_r_warnes@groton.pfizer.com)
|
||||
# Christopher Blunck (blunck@gst.com)
|
||||
#
|
||||
################################################################################
|
||||
# Copyright (c) 2003, Pfizer
|
||||
# Copyright (c) 2001, Cayce Ullman.
|
||||
# Copyright (c) 2001, Brian Matthews.
|
||||
#
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
# Redistributions of source code must retain the above copyright notice, this
|
||||
# list of conditions and the following disclaimer.
|
||||
#
|
||||
# Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
#
|
||||
# Neither the name of actzero, inc. nor the names of its contributors may
|
||||
# be used to endorse or promote products derived from this software without
|
||||
# specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
|
||||
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#
|
||||
################################################################################
|
||||
"""
|
||||
|
||||
ident = '$Id$'
|
||||
from version import __version__
|
||||
|
||||
from __future__ import nested_scopes
|
||||
|
||||
#import xml.sax
|
||||
import re
|
||||
import socket
|
||||
import sys
|
||||
import SocketServer
|
||||
from types import *
|
||||
import BaseHTTPServer
|
||||
import thread
|
||||
|
||||
# SOAPpy modules
|
||||
from Parser import parseSOAPRPC
|
||||
from Config import Config
|
||||
from Types import faultType, voidType, simplify
|
||||
from NS import NS
|
||||
from SOAPBuilder import buildSOAP
|
||||
from Utilities import debugHeader, debugFooter
|
||||
|
||||
try: from M2Crypto import SSL
|
||||
except: pass
|
||||
|
||||
ident = '$Id$'
|
||||
|
||||
from version import __version__
|
||||
|
||||
################################################################################
|
||||
# Call context dictionary
|
||||
################################################################################
|
||||
|
||||
_contexts = dict()
|
||||
|
||||
def GetSOAPContext():
|
||||
global _contexts
|
||||
return _contexts[thread.get_ident()]
|
||||
|
||||
################################################################################
|
||||
# Server
|
||||
################################################################################
|
||||
|
||||
# Method Signature class for adding extra info to registered funcs, right now
|
||||
# used just to indicate it should be called with keywords, instead of ordered
|
||||
# params.
|
||||
class MethodSig:
|
||||
def __init__(self, func, keywords=0, context=0):
|
||||
self.func = func
|
||||
self.keywords = keywords
|
||||
self.context = context
|
||||
self.__name__ = func.__name__
|
||||
|
||||
def __call__(self, *args, **kw):
|
||||
return apply(self.func,args,kw)
|
||||
|
||||
class SOAPContext:
|
||||
def __init__(self, header, body, attrs, xmldata, connection, httpheaders,
|
||||
soapaction):
|
||||
|
||||
self.header = header
|
||||
self.body = body
|
||||
self.attrs = attrs
|
||||
self.xmldata = xmldata
|
||||
self.connection = connection
|
||||
self.httpheaders= httpheaders
|
||||
self.soapaction = soapaction
|
||||
|
||||
# A class to describe how header messages are handled
|
||||
class HeaderHandler:
|
||||
# Initially fail out if there are any problems.
|
||||
def __init__(self, header, attrs):
|
||||
for i in header.__dict__.keys():
|
||||
if i[0] == "_":
|
||||
continue
|
||||
|
||||
d = getattr(header, i)
|
||||
|
||||
try:
|
||||
fault = int(attrs[id(d)][(NS.ENV, 'mustUnderstand')])
|
||||
except:
|
||||
fault = 0
|
||||
|
||||
if fault:
|
||||
raise faultType, ("%s:MustUnderstand" % NS.ENV_T,
|
||||
"Required Header Misunderstood",
|
||||
"%s" % i)
|
||||
|
||||
################################################################################
|
||||
# SOAP Server
|
||||
################################################################################
|
||||
class SOAPServerBase:
|
||||
|
||||
def get_request(self):
|
||||
sock, addr = SocketServer.TCPServer.get_request(self)
|
||||
|
||||
if self.ssl_context:
|
||||
sock = SSL.Connection(self.ssl_context, sock)
|
||||
sock._setup_ssl(addr)
|
||||
if sock.accept_ssl() != 1:
|
||||
raise socket.error, "Couldn't accept SSL connection"
|
||||
|
||||
return sock, addr
|
||||
|
||||
def registerObject(self, object, namespace = '', path = ''):
|
||||
if namespace == '' and path == '': namespace = self.namespace
|
||||
if namespace == '' and path != '':
|
||||
namespace = path.replace("/", ":")
|
||||
if namespace[0] == ":": namespace = namespace[1:]
|
||||
self.objmap[namespace] = object
|
||||
|
||||
def registerFunction(self, function, namespace = '', funcName = None,
|
||||
path = ''):
|
||||
if not funcName : funcName = function.__name__
|
||||
if namespace == '' and path == '': namespace = self.namespace
|
||||
if namespace == '' and path != '':
|
||||
namespace = path.replace("/", ":")
|
||||
if namespace[0] == ":": namespace = namespace[1:]
|
||||
if self.funcmap.has_key(namespace):
|
||||
self.funcmap[namespace][funcName] = function
|
||||
else:
|
||||
self.funcmap[namespace] = {funcName : function}
|
||||
|
||||
def registerKWObject(self, object, namespace = '', path = ''):
|
||||
if namespace == '' and path == '': namespace = self.namespace
|
||||
if namespace == '' and path != '':
|
||||
namespace = path.replace("/", ":")
|
||||
if namespace[0] == ":": namespace = namespace[1:]
|
||||
for i in dir(object.__class__):
|
||||
if i[0] != "_" and callable(getattr(object, i)):
|
||||
self.registerKWFunction(getattr(object,i), namespace)
|
||||
|
||||
# convenience - wraps your func for you.
|
||||
def registerKWFunction(self, function, namespace = '', funcName = None,
|
||||
path = ''):
|
||||
if namespace == '' and path == '': namespace = self.namespace
|
||||
if namespace == '' and path != '':
|
||||
namespace = path.replace("/", ":")
|
||||
if namespace[0] == ":": namespace = namespace[1:]
|
||||
self.registerFunction(MethodSig(function,keywords=1), namespace,
|
||||
funcName)
|
||||
|
||||
def unregisterObject(self, object, namespace = '', path = ''):
|
||||
if namespace == '' and path == '': namespace = self.namespace
|
||||
if namespace == '' and path != '':
|
||||
namespace = path.replace("/", ":")
|
||||
if namespace[0] == ":": namespace = namespace[1:]
|
||||
|
||||
del self.objmap[namespace]
|
||||
|
||||
class SOAPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
|
||||
def version_string(self):
|
||||
return '<a href="http://pywebsvcs.sf.net">' + \
|
||||
'SOAPpy ' + __version__ + '</a> (Python ' + \
|
||||
sys.version.split()[0] + ')'
|
||||
|
||||
def date_time_string(self):
|
||||
self.__last_date_time_string = \
|
||||
BaseHTTPServer.BaseHTTPRequestHandler.\
|
||||
date_time_string(self)
|
||||
|
||||
return self.__last_date_time_string
|
||||
|
||||
def do_POST(self):
|
||||
global _contexts
|
||||
|
||||
status = 500
|
||||
try:
|
||||
if self.server.config.dumpHeadersIn:
|
||||
s = 'Incoming HTTP headers'
|
||||
debugHeader(s)
|
||||
print self.raw_requestline.strip()
|
||||
print "\n".join(map (lambda x: x.strip(),
|
||||
self.headers.headers))
|
||||
debugFooter(s)
|
||||
|
||||
data = self.rfile.read(int(self.headers["Content-length"]))
|
||||
|
||||
if self.server.config.dumpSOAPIn:
|
||||
s = 'Incoming SOAP'
|
||||
debugHeader(s)
|
||||
print data,
|
||||
if data[-1] != '\n':
|
||||
print
|
||||
debugFooter(s)
|
||||
|
||||
(r, header, body, attrs) = \
|
||||
parseSOAPRPC(data, header = 1, body = 1, attrs = 1)
|
||||
|
||||
method = r._name
|
||||
args = r._aslist()
|
||||
kw = r._asdict()
|
||||
|
||||
if Config.simplify_objects:
|
||||
args = simplify(args)
|
||||
kw = simplify(kw)
|
||||
|
||||
# Handle mixed named and unnamed arguments by assuming
|
||||
# that all arguments with names of the form "v[0-9]+"
|
||||
# are unnamed and should be passed in numeric order,
|
||||
# other arguments are named and should be passed using
|
||||
# this name.
|
||||
|
||||
# This is a non-standard exension to the SOAP protocol,
|
||||
# but is supported by Apache AXIS.
|
||||
|
||||
# It is enabled by default. To disable, set
|
||||
# Config.specialArgs to False.
|
||||
|
||||
if Config.specialArgs:
|
||||
|
||||
ordered_args = {}
|
||||
named_args = {}
|
||||
|
||||
for (k,v) in kw.items():
|
||||
|
||||
if k[0]=="v":
|
||||
try:
|
||||
i = int(k[1:])
|
||||
ordered_args[i] = v
|
||||
except ValueError:
|
||||
named_args[str(k)] = v
|
||||
|
||||
else:
|
||||
named_args[str(k)] = v
|
||||
|
||||
# We have to decide namespace precedence
|
||||
# I'm happy with the following scenario
|
||||
# if r._ns is specified use it, if not check for
|
||||
# a path, if it's specified convert it and use it as the
|
||||
# namespace. If both are specified, use r._ns.
|
||||
|
||||
ns = r._ns
|
||||
|
||||
if len(self.path) > 1 and not ns:
|
||||
ns = self.path.replace("/", ":")
|
||||
if ns[0] == ":": ns = ns[1:]
|
||||
|
||||
# authorization method
|
||||
a = None
|
||||
|
||||
keylist = ordered_args.keys()
|
||||
keylist.sort()
|
||||
|
||||
# create list in proper order w/o names
|
||||
tmp = map( lambda x: ordered_args[x], keylist)
|
||||
ordered_args = tmp
|
||||
|
||||
#print '<-> Argument Matching Yielded:'
|
||||
#print '<-> Ordered Arguments:' + str(ordered_args)
|
||||
#print '<-> Named Arguments :' + str(named_args)
|
||||
|
||||
resp = ""
|
||||
|
||||
# For fault messages
|
||||
if ns:
|
||||
nsmethod = "%s:%s" % (ns, method)
|
||||
else:
|
||||
nsmethod = method
|
||||
|
||||
try:
|
||||
# First look for registered functions
|
||||
if self.server.funcmap.has_key(ns) and \
|
||||
self.server.funcmap[ns].has_key(method):
|
||||
f = self.server.funcmap[ns][method]
|
||||
|
||||
# look for the authorization method
|
||||
if self.server.config.authMethod != None:
|
||||
authmethod = self.server.config.authMethod
|
||||
if self.server.funcmap.has_key(ns) and \
|
||||
self.server.funcmap[ns].has_key(authmethod):
|
||||
a = self.server.funcmap[ns][authmethod]
|
||||
else:
|
||||
# Now look at registered objects
|
||||
# Check for nested attributes. This works even if
|
||||
# there are none, because the split will return
|
||||
# [method]
|
||||
f = self.server.objmap[ns]
|
||||
|
||||
# Look for the authorization method
|
||||
if self.server.config.authMethod != None:
|
||||
authmethod = self.server.config.authMethod
|
||||
if hasattr(f, authmethod):
|
||||
a = getattr(f, authmethod)
|
||||
|
||||
# then continue looking for the method
|
||||
l = method.split(".")
|
||||
for i in l:
|
||||
f = getattr(f, i)
|
||||
except:
|
||||
info = sys.exc_info()
|
||||
try:
|
||||
resp = buildSOAP(faultType("%s:Client" % NS.ENV_T,
|
||||
"Method Not Found",
|
||||
"%s : %s %s %s" % (nsmethod,
|
||||
info[0],
|
||||
info[1],
|
||||
info[2])),
|
||||
encoding = self.server.encoding,
|
||||
config = self.server.config)
|
||||
finally:
|
||||
del info
|
||||
status = 500
|
||||
else:
|
||||
try:
|
||||
if header:
|
||||
x = HeaderHandler(header, attrs)
|
||||
|
||||
fr = 1
|
||||
|
||||
# call context book keeping
|
||||
# We're stuffing the method into the soapaction if there
|
||||
# isn't one, someday, we'll set that on the client
|
||||
# and it won't be necessary here
|
||||
# for now we're doing both
|
||||
|
||||
if "SOAPAction".lower() not in self.headers.keys() or \
|
||||
self.headers["SOAPAction"] == "\"\"":
|
||||
self.headers["SOAPAction"] = method
|
||||
|
||||
thread_id = thread.get_ident()
|
||||
_contexts[thread_id] = SOAPContext(header, body,
|
||||
attrs, data,
|
||||
self.connection,
|
||||
self.headers,
|
||||
self.headers["SOAPAction"])
|
||||
|
||||
# Do an authorization check
|
||||
if a != None:
|
||||
if not apply(a, (), {"_SOAPContext" :
|
||||
_contexts[thread_id] }):
|
||||
raise faultType("%s:Server" % NS.ENV_T,
|
||||
"Authorization failed.",
|
||||
"%s" % nsmethod)
|
||||
|
||||
# If it's wrapped, some special action may be needed
|
||||
if isinstance(f, MethodSig):
|
||||
c = None
|
||||
|
||||
if f.context: # retrieve context object
|
||||
c = _contexts[thread_id]
|
||||
|
||||
if Config.specialArgs:
|
||||
if c:
|
||||
named_args["_SOAPContext"] = c
|
||||
fr = apply(f, ordered_args, named_args)
|
||||
elif f.keywords:
|
||||
# This is lame, but have to de-unicode
|
||||
# keywords
|
||||
|
||||
strkw = {}
|
||||
|
||||
for (k, v) in kw.items():
|
||||
strkw[str(k)] = v
|
||||
if c:
|
||||
strkw["_SOAPContext"] = c
|
||||
fr = apply(f, (), strkw)
|
||||
elif c:
|
||||
fr = apply(f, args, {'_SOAPContext':c})
|
||||
else:
|
||||
fr = apply(f, args, {})
|
||||
|
||||
else:
|
||||
if Config.specialArgs:
|
||||
fr = apply(f, ordered_args, named_args)
|
||||
else:
|
||||
fr = apply(f, args, {})
|
||||
|
||||
|
||||
if type(fr) == type(self) and \
|
||||
isinstance(fr, voidType):
|
||||
resp = buildSOAP(kw = {'%sResponse' % method: fr},
|
||||
encoding = self.server.encoding,
|
||||
config = self.server.config)
|
||||
else:
|
||||
resp = buildSOAP(kw =
|
||||
{'%sResponse' % method: {'Result': fr}},
|
||||
encoding = self.server.encoding,
|
||||
config = self.server.config)
|
||||
|
||||
# Clean up _contexts
|
||||
if _contexts.has_key(thread_id):
|
||||
del _contexts[thread_id]
|
||||
|
||||
except Exception, e:
|
||||
import traceback
|
||||
info = sys.exc_info()
|
||||
|
||||
try:
|
||||
if self.server.config.dumpFaultInfo:
|
||||
s = 'Method %s exception' % nsmethod
|
||||
debugHeader(s)
|
||||
traceback.print_exception(info[0], info[1],
|
||||
info[2])
|
||||
debugFooter(s)
|
||||
|
||||
if isinstance(e, faultType):
|
||||
f = e
|
||||
else:
|
||||
f = faultType("%s:Server" % NS.ENV_T,
|
||||
"Method Failed",
|
||||
"%s" % nsmethod)
|
||||
|
||||
if self.server.config.returnFaultInfo:
|
||||
f._setDetail("".join(traceback.format_exception(
|
||||
info[0], info[1], info[2])))
|
||||
elif not hasattr(f, 'detail'):
|
||||
f._setDetail("%s %s" % (info[0], info[1]))
|
||||
finally:
|
||||
del info
|
||||
|
||||
resp = buildSOAP(f, encoding = self.server.encoding,
|
||||
config = self.server.config)
|
||||
status = 500
|
||||
else:
|
||||
status = 200
|
||||
except faultType, e:
|
||||
import traceback
|
||||
info = sys.exc_info()
|
||||
try:
|
||||
if self.server.config.dumpFaultInfo:
|
||||
s = 'Received fault exception'
|
||||
debugHeader(s)
|
||||
traceback.print_exception(info[0], info[1],
|
||||
info[2])
|
||||
debugFooter(s)
|
||||
|
||||
if self.server.config.returnFaultInfo:
|
||||
e._setDetail("".join(traceback.format_exception(
|
||||
info[0], info[1], info[2])))
|
||||
elif not hasattr(e, 'detail'):
|
||||
e._setDetail("%s %s" % (info[0], info[1]))
|
||||
finally:
|
||||
del info
|
||||
|
||||
resp = buildSOAP(e, encoding = self.server.encoding,
|
||||
config = self.server.config)
|
||||
status = 500
|
||||
except Exception, e:
|
||||
# internal error, report as HTTP server error
|
||||
|
||||
if self.server.config.dumpFaultInfo:
|
||||
s = 'Internal exception %s' % e
|
||||
import traceback
|
||||
debugHeader(s)
|
||||
info = sys.exc_info()
|
||||
try:
|
||||
traceback.print_exception(info[0], info[1], info[2])
|
||||
finally:
|
||||
del info
|
||||
|
||||
debugFooter(s)
|
||||
|
||||
self.send_response(500)
|
||||
self.end_headers()
|
||||
|
||||
if self.server.config.dumpHeadersOut and \
|
||||
self.request_version != 'HTTP/0.9':
|
||||
s = 'Outgoing HTTP headers'
|
||||
debugHeader(s)
|
||||
if self.responses.has_key(status):
|
||||
s = ' ' + self.responses[status][0]
|
||||
else:
|
||||
s = ''
|
||||
print "%s %d%s" % (self.protocol_version, 500, s)
|
||||
print "Server:", self.version_string()
|
||||
print "Date:", self.__last_date_time_string
|
||||
debugFooter(s)
|
||||
else:
|
||||
# got a valid SOAP response
|
||||
self.send_response(status)
|
||||
|
||||
t = 'text/xml';
|
||||
if self.server.encoding != None:
|
||||
t += '; charset="%s"' % self.server.encoding
|
||||
self.send_header("Content-type", t)
|
||||
self.send_header("Content-length", str(len(resp)))
|
||||
self.end_headers()
|
||||
|
||||
if self.server.config.dumpHeadersOut and \
|
||||
self.request_version != 'HTTP/0.9':
|
||||
s = 'Outgoing HTTP headers'
|
||||
debugHeader(s)
|
||||
if self.responses.has_key(status):
|
||||
s = ' ' + self.responses[status][0]
|
||||
else:
|
||||
s = ''
|
||||
print "%s %d%s" % (self.protocol_version, status, s)
|
||||
print "Server:", self.version_string()
|
||||
print "Date:", self.__last_date_time_string
|
||||
print "Content-type:", t
|
||||
print "Content-length:", len(resp)
|
||||
debugFooter(s)
|
||||
|
||||
if self.server.config.dumpSOAPOut:
|
||||
s = 'Outgoing SOAP'
|
||||
debugHeader(s)
|
||||
print resp,
|
||||
if resp[-1] != '\n':
|
||||
print
|
||||
debugFooter(s)
|
||||
|
||||
self.wfile.write(resp)
|
||||
self.wfile.flush()
|
||||
|
||||
# We should be able to shut down both a regular and an SSL
|
||||
# connection, but under Python 2.1, calling shutdown on an
|
||||
# SSL connections drops the output, so this work-around.
|
||||
# This should be investigated more someday.
|
||||
|
||||
if self.server.config.SSLserver and \
|
||||
isinstance(self.connection, SSL.Connection):
|
||||
self.connection.set_shutdown(SSL.SSL_SENT_SHUTDOWN |
|
||||
SSL.SSL_RECEIVED_SHUTDOWN)
|
||||
else:
|
||||
self.connection.shutdown(1)
|
||||
|
||||
def do_GET(self):
|
||||
|
||||
#print 'command ', self.command
|
||||
#print 'path ', self.path
|
||||
#print 'request_version', self.request_version
|
||||
#print 'headers'
|
||||
#print ' type ', self.headers.type
|
||||
#print ' maintype', self.headers.maintype
|
||||
#print ' subtype ', self.headers.subtype
|
||||
#print ' params ', self.headers.plist
|
||||
|
||||
path = self.path.lower()
|
||||
if path.endswith('wsdl'):
|
||||
method = 'wsdl'
|
||||
function = namespace = None
|
||||
if self.server.funcmap.has_key(namespace) \
|
||||
and self.server.funcmap[namespace].has_key(method):
|
||||
function = self.server.funcmap[namespace][method]
|
||||
else:
|
||||
if namespace in self.server.objmap.keys():
|
||||
function = self.server.objmap[namespace]
|
||||
l = method.split(".")
|
||||
for i in l:
|
||||
function = getattr(function, i)
|
||||
|
||||
if function:
|
||||
self.send_response(200)
|
||||
self.send_header("Content-type", 'text/plain')
|
||||
self.end_headers()
|
||||
response = apply(function, ())
|
||||
self.wfile.write(str(response))
|
||||
return
|
||||
|
||||
# return error
|
||||
self.send_response(200)
|
||||
self.send_header("Content-type", 'text/html')
|
||||
self.end_headers()
|
||||
self.wfile.write('''\
|
||||
<title>
|
||||
<head>Error!</head>
|
||||
</title>
|
||||
|
||||
<body>
|
||||
<h1>Oops!</h1>
|
||||
|
||||
<p>
|
||||
This server supports HTTP GET requests only for the the purpose of
|
||||
obtaining Web Services Description Language (WSDL) for a specific
|
||||
service.
|
||||
|
||||
Either you requested an URL that does not end in "wsdl" or this
|
||||
server does not implement a wsdl method.
|
||||
</p>
|
||||
|
||||
|
||||
</body>''')
|
||||
|
||||
|
||||
def log_message(self, format, *args):
|
||||
if self.server.log:
|
||||
BaseHTTPServer.BaseHTTPRequestHandler.\
|
||||
log_message (self, format, *args)
|
||||
|
||||
|
||||
|
||||
class SOAPServer(SOAPServerBase, SocketServer.TCPServer):
|
||||
|
||||
def __init__(self, addr = ('localhost', 8000),
|
||||
RequestHandler = SOAPRequestHandler, log = 0, encoding = 'UTF-8',
|
||||
config = Config, namespace = None, ssl_context = None):
|
||||
|
||||
# Test the encoding, raising an exception if it's not known
|
||||
if encoding != None:
|
||||
''.encode(encoding)
|
||||
|
||||
if ssl_context != None and not config.SSLserver:
|
||||
raise AttributeError, \
|
||||
"SSL server not supported by this Python installation"
|
||||
|
||||
self.namespace = namespace
|
||||
self.objmap = {}
|
||||
self.funcmap = {}
|
||||
self.ssl_context = ssl_context
|
||||
self.encoding = encoding
|
||||
self.config = config
|
||||
self.log = log
|
||||
|
||||
self.allow_reuse_address= 1
|
||||
|
||||
SocketServer.TCPServer.__init__(self, addr, RequestHandler)
|
||||
|
||||
|
||||
class ThreadingSOAPServer(SOAPServerBase, SocketServer.ThreadingTCPServer):
|
||||
|
||||
def __init__(self, addr = ('localhost', 8000),
|
||||
RequestHandler = SOAPRequestHandler, log = 0, encoding = 'UTF-8',
|
||||
config = Config, namespace = None, ssl_context = None):
|
||||
|
||||
# Test the encoding, raising an exception if it's not known
|
||||
if encoding != None:
|
||||
''.encode(encoding)
|
||||
|
||||
if ssl_context != None and not config.SSLserver:
|
||||
raise AttributeError, \
|
||||
"SSL server not supported by this Python installation"
|
||||
|
||||
self.namespace = namespace
|
||||
self.objmap = {}
|
||||
self.funcmap = {}
|
||||
self.ssl_context = ssl_context
|
||||
self.encoding = encoding
|
||||
self.config = config
|
||||
self.log = log
|
||||
|
||||
self.allow_reuse_address= 1
|
||||
|
||||
SocketServer.ThreadingTCPServer.__init__(self, addr, RequestHandler)
|
||||
|
||||
# only define class if Unix domain sockets are available
|
||||
if hasattr(socket, "AF_UNIX"):
|
||||
|
||||
class SOAPUnixSocketServer(SOAPServerBase, SocketServer.UnixStreamServer):
|
||||
|
||||
def __init__(self, addr = 8000,
|
||||
RequestHandler = SOAPRequestHandler, log = 0, encoding = 'UTF-8',
|
||||
config = Config, namespace = None, ssl_context = None):
|
||||
|
||||
# Test the encoding, raising an exception if it's not known
|
||||
if encoding != None:
|
||||
''.encode(encoding)
|
||||
|
||||
if ssl_context != None and not config.SSLserver:
|
||||
raise AttributeError, \
|
||||
"SSL server not supported by this Python installation"
|
||||
|
||||
self.namespace = namespace
|
||||
self.objmap = {}
|
||||
self.funcmap = {}
|
||||
self.ssl_context = ssl_context
|
||||
self.encoding = encoding
|
||||
self.config = config
|
||||
self.log = log
|
||||
|
||||
self.allow_reuse_address= 1
|
||||
|
||||
SocketServer.UnixStreamServer.__init__(self, str(addr), RequestHandler)
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,23 +0,0 @@
|
||||
"""Provide a class for loading data from URL's that handles basic
|
||||
authentication"""
|
||||
|
||||
ident = '$Id$'
|
||||
from version import __version__
|
||||
|
||||
from Config import Config
|
||||
from urllib import FancyURLopener
|
||||
|
||||
class URLopener(FancyURLopener):
|
||||
|
||||
username = None
|
||||
passwd = None
|
||||
|
||||
|
||||
def __init__(self, username=None, passwd=None, *args, **kw):
|
||||
FancyURLopener.__init__( self, *args, **kw)
|
||||
self.username = username
|
||||
self.passwd = passwd
|
||||
|
||||
|
||||
def prompt_user_passwd(self, host, realm):
|
||||
return self.username, self.passwd
|
@ -1,178 +0,0 @@
|
||||
"""
|
||||
################################################################################
|
||||
# Copyright (c) 2003, Pfizer
|
||||
# Copyright (c) 2001, Cayce Ullman.
|
||||
# Copyright (c) 2001, Brian Matthews.
|
||||
#
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
# Redistributions of source code must retain the above copyright notice, this
|
||||
# list of conditions and the following disclaimer.
|
||||
#
|
||||
# Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
#
|
||||
# Neither the name of actzero, inc. nor the names of its contributors may
|
||||
# be used to endorse or promote products derived from this software without
|
||||
# specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
|
||||
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#
|
||||
################################################################################
|
||||
"""
|
||||
|
||||
ident = '$Id$'
|
||||
from version import __version__
|
||||
|
||||
import exceptions
|
||||
import copy
|
||||
import re
|
||||
import string
|
||||
import sys
|
||||
from types import *
|
||||
|
||||
# SOAPpy modules
|
||||
from Errors import *
|
||||
|
||||
################################################################################
|
||||
# Utility infielders
|
||||
################################################################################
|
||||
def collapseWhiteSpace(s):
|
||||
return re.sub('\s+', ' ', s).strip()
|
||||
|
||||
def decodeHexString(data):
|
||||
conv = {
|
||||
'0': 0x0, '1': 0x1, '2': 0x2, '3': 0x3, '4': 0x4,
|
||||
'5': 0x5, '6': 0x6, '7': 0x7, '8': 0x8, '9': 0x9,
|
||||
|
||||
'a': 0xa, 'b': 0xb, 'c': 0xc, 'd': 0xd, 'e': 0xe,
|
||||
'f': 0xf,
|
||||
|
||||
'A': 0xa, 'B': 0xb, 'C': 0xc, 'D': 0xd, 'E': 0xe,
|
||||
'F': 0xf,
|
||||
}
|
||||
|
||||
ws = string.whitespace
|
||||
|
||||
bin = ''
|
||||
|
||||
i = 0
|
||||
|
||||
while i < len(data):
|
||||
if data[i] not in ws:
|
||||
break
|
||||
i += 1
|
||||
|
||||
low = 0
|
||||
|
||||
while i < len(data):
|
||||
c = data[i]
|
||||
|
||||
if c in string.whitespace:
|
||||
break
|
||||
|
||||
try:
|
||||
c = conv[c]
|
||||
except KeyError:
|
||||
raise ValueError, \
|
||||
"invalid hex string character `%s'" % c
|
||||
|
||||
if low:
|
||||
bin += chr(high * 16 + c)
|
||||
low = 0
|
||||
else:
|
||||
high = c
|
||||
low = 1
|
||||
|
||||
i += 1
|
||||
|
||||
if low:
|
||||
raise ValueError, "invalid hex string length"
|
||||
|
||||
while i < len(data):
|
||||
if data[i] not in string.whitespace:
|
||||
raise ValueError, \
|
||||
"invalid hex string character `%s'" % c
|
||||
|
||||
i += 1
|
||||
|
||||
return bin
|
||||
|
||||
def encodeHexString(data):
|
||||
h = ''
|
||||
|
||||
for i in data:
|
||||
h += "%02X" % ord(i)
|
||||
|
||||
return h
|
||||
|
||||
def leapMonth(year, month):
|
||||
return month == 2 and \
|
||||
year % 4 == 0 and \
|
||||
(year % 100 != 0 or year % 400 == 0)
|
||||
|
||||
def cleanDate(d, first = 0):
|
||||
ranges = (None, (1, 12), (1, 31), (0, 23), (0, 59), (0, 61))
|
||||
months = (0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
|
||||
names = ('year', 'month', 'day', 'hours', 'minutes', 'seconds')
|
||||
|
||||
if len(d) != 6:
|
||||
raise ValueError, "date must have 6 elements"
|
||||
|
||||
for i in range(first, 6):
|
||||
s = d[i]
|
||||
|
||||
if type(s) == FloatType:
|
||||
if i < 5:
|
||||
try:
|
||||
s = int(s)
|
||||
except OverflowError:
|
||||
if i > 0:
|
||||
raise
|
||||
s = long(s)
|
||||
|
||||
if s != d[i]:
|
||||
raise ValueError, "%s must be integral" % names[i]
|
||||
|
||||
d[i] = s
|
||||
elif type(s) == LongType:
|
||||
try: s = int(s)
|
||||
except: pass
|
||||
elif type(s) != IntType:
|
||||
raise TypeError, "%s isn't a valid type" % names[i]
|
||||
|
||||
if i == first and s < 0:
|
||||
continue
|
||||
|
||||
if ranges[i] != None and \
|
||||
(s < ranges[i][0] or ranges[i][1] < s):
|
||||
raise ValueError, "%s out of range" % names[i]
|
||||
|
||||
if first < 6 and d[5] >= 61:
|
||||
raise ValueError, "seconds out of range"
|
||||
|
||||
if first < 2:
|
||||
leap = first < 1 and leapMonth(d[0], d[1])
|
||||
|
||||
if d[2] > months[d[1]] + leap:
|
||||
raise ValueError, "day out of range"
|
||||
|
||||
def debugHeader(title):
|
||||
s = '*** ' + title + ' '
|
||||
print s + ('*' * (72 - len(s)))
|
||||
|
||||
def debugFooter(title):
|
||||
print '*' * 72
|
||||
sys.stdout.flush()
|
@ -1,102 +0,0 @@
|
||||
"""Parse web services description language to get SOAP methods.
|
||||
|
||||
Rudimentary support."""
|
||||
|
||||
ident = '$Id$'
|
||||
from version import __version__
|
||||
|
||||
import wstools
|
||||
from Client import SOAPProxy, SOAPAddress
|
||||
from Config import Config
|
||||
import urllib
|
||||
|
||||
class Proxy:
|
||||
"""WSDL Proxy.
|
||||
|
||||
SOAPProxy wrapper that parses method names, namespaces, soap actions from
|
||||
the web service description language (WSDL) file passed into the
|
||||
constructor. The WSDL reference can be passed in as a stream, an url, a
|
||||
file name, or a string.
|
||||
|
||||
Loads info into self.methods, a dictionary with methodname keys and values
|
||||
of WSDLTools.SOAPCallinfo.
|
||||
|
||||
For example,
|
||||
|
||||
url = 'http://www.xmethods.org/sd/2001/TemperatureService.wsdl'
|
||||
wsdl = WSDL.Proxy(url)
|
||||
print len(wsdl.methods) # 1
|
||||
print wsdl.methods.keys() # getTemp
|
||||
|
||||
|
||||
See WSDLTools.SOAPCallinfo for more info on each method's attributes.
|
||||
"""
|
||||
|
||||
def __init__(self, wsdlsource, config=Config, **kw ):
|
||||
|
||||
reader = wstools.WSDLTools.WSDLReader()
|
||||
self.wsdl = None
|
||||
|
||||
# From Mark Pilgrim's "Dive Into Python" toolkit.py--open anything.
|
||||
if self.wsdl is None and hasattr(wsdlsource, "read"):
|
||||
#print 'stream'
|
||||
self.wsdl = reader.loadFromStream(wsdlsource)
|
||||
|
||||
# NOT TESTED (as of April 17, 2003)
|
||||
#if self.wsdl is None and wsdlsource == '-':
|
||||
# import sys
|
||||
# self.wsdl = reader.loadFromStream(sys.stdin)
|
||||
# print 'stdin'
|
||||
|
||||
if self.wsdl is None:
|
||||
try:
|
||||
file(wsdlsource)
|
||||
self.wsdl = reader.loadFromFile(wsdlsource)
|
||||
#print 'file'
|
||||
except (IOError, OSError):
|
||||
pass
|
||||
|
||||
if self.wsdl is None:
|
||||
try:
|
||||
stream = urllib.urlopen(wsdlsource)
|
||||
self.wsdl = reader.loadFromStream(stream, wsdlsource)
|
||||
except (IOError, OSError): pass
|
||||
|
||||
if self.wsdl is None:
|
||||
import StringIO
|
||||
self.wsdl = reader.loadFromString(str(wsdlsource))
|
||||
#print 'string'
|
||||
|
||||
# Package wsdl info as a dictionary of remote methods, with method name
|
||||
# as key (based on ServiceProxy.__init__ in ZSI library).
|
||||
self.methods = {}
|
||||
service = self.wsdl.services[0]
|
||||
port = service.ports[0]
|
||||
name = service.name
|
||||
binding = port.getBinding()
|
||||
portType = binding.getPortType()
|
||||
for operation in portType.operations:
|
||||
callinfo = wstools.WSDLTools.callInfoFromWSDL(port, operation.name)
|
||||
self.methods[callinfo.methodName] = callinfo
|
||||
|
||||
self.soapproxy = SOAPProxy('http://localhost/dummy.webservice',
|
||||
config=config, **kw)
|
||||
|
||||
def __str__(self):
|
||||
s = ''
|
||||
for method in self.methods.values():
|
||||
s += str(method)
|
||||
return s
|
||||
|
||||
def __getattr__(self, name):
|
||||
"""Set up environment then let parent class handle call.
|
||||
|
||||
Raises AttributeError is method name is not found."""
|
||||
|
||||
if not self.methods.has_key(name): raise AttributeError, name
|
||||
|
||||
callinfo = self.methods[name]
|
||||
self.soapproxy.proxy = SOAPAddress(callinfo.location)
|
||||
self.soapproxy.namespace = callinfo.namespace
|
||||
self.soapproxy.soapaction = callinfo.soapAction
|
||||
return self.soapproxy.__getattr__(name)
|
@ -1,15 +0,0 @@
|
||||
|
||||
ident = '$Id$'
|
||||
from version import __version__
|
||||
|
||||
from Client import *
|
||||
from Config import *
|
||||
from Errors import *
|
||||
from NS import *
|
||||
from Parser import *
|
||||
from SOAPBuilder import *
|
||||
from Server import *
|
||||
from Types import *
|
||||
from Utilities import *
|
||||
import wstools
|
||||
import WSDL
|
@ -1,2 +0,0 @@
|
||||
__version__="0.11.6"
|
||||
|
@ -1,91 +0,0 @@
|
||||
"""Namespace module, so you don't need PyXML
|
||||
"""
|
||||
|
||||
try:
|
||||
from xml.ns import SOAP, SCHEMA, WSDL, XMLNS, DSIG, ENCRYPTION
|
||||
except:
|
||||
class SOAP:
|
||||
ENV = "http://schemas.xmlsoap.org/soap/envelope/"
|
||||
ENC = "http://schemas.xmlsoap.org/soap/encoding/"
|
||||
ACTOR_NEXT = "http://schemas.xmlsoap.org/soap/actor/next"
|
||||
|
||||
class SCHEMA:
|
||||
XSD1 = "http://www.w3.org/1999/XMLSchema"
|
||||
XSD2 = "http://www.w3.org/2000/10/XMLSchema"
|
||||
XSD3 = "http://www.w3.org/2001/XMLSchema"
|
||||
XSD_LIST = [ XSD1, XSD2, XSD3 ]
|
||||
XSI1 = "http://www.w3.org/1999/XMLSchema-instance"
|
||||
XSI2 = "http://www.w3.org/2000/10/XMLSchema-instance"
|
||||
XSI3 = "http://www.w3.org/2001/XMLSchema-instance"
|
||||
XSI_LIST = [ XSI1, XSI2, XSI3 ]
|
||||
BASE = XSD3
|
||||
|
||||
class WSDL:
|
||||
BASE = "http://schemas.xmlsoap.org/wsdl/"
|
||||
BIND_HTTP = "http://schemas.xmlsoap.org/wsdl/http/"
|
||||
BIND_MIME = "http://schemas.xmlsoap.org/wsdl/mime/"
|
||||
BIND_SOAP = "http://schemas.xmlsoap.org/wsdl/soap/"
|
||||
|
||||
class XMLNS:
|
||||
BASE = "http://www.w3.org/2000/xmlns/"
|
||||
XML = "http://www.w3.org/XML/1998/namespace"
|
||||
HTML = "http://www.w3.org/TR/REC-html40"
|
||||
|
||||
class DSIG:
|
||||
BASE = "http://www.w3.org/2000/09/xmldsig#"
|
||||
C14N = "http://www.w3.org/TR/2000/CR-xml-c14n-20010315"
|
||||
C14N_COMM = "http://www.w3.org/TR/2000/CR-xml-c14n-20010315#WithComments"
|
||||
C14N_EXCL = "http://www.w3.org/2001/10/xml-exc-c14n#"
|
||||
DIGEST_MD2 = "http://www.w3.org/2000/09/xmldsig#md2"
|
||||
DIGEST_MD5 = "http://www.w3.org/2000/09/xmldsig#md5"
|
||||
DIGEST_SHA1 = "http://www.w3.org/2000/09/xmldsig#sha1"
|
||||
ENC_BASE64 = "http://www.w3.org/2000/09/xmldsig#base64"
|
||||
ENVELOPED = "http://www.w3.org/2000/09/xmldsig#enveloped-signature"
|
||||
HMAC_SHA1 = "http://www.w3.org/2000/09/xmldsig#hmac-sha1"
|
||||
SIG_DSA_SHA1 = "http://www.w3.org/2000/09/xmldsig#dsa-sha1"
|
||||
SIG_RSA_SHA1 = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"
|
||||
XPATH = "http://www.w3.org/TR/1999/REC-xpath-19991116"
|
||||
XSLT = "http://www.w3.org/TR/1999/REC-xslt-19991116"
|
||||
|
||||
class ENCRYPTION:
|
||||
BASE = "http://www.w3.org/2001/04/xmlenc#"
|
||||
BLOCK_3DES = "http://www.w3.org/2001/04/xmlenc#des-cbc"
|
||||
BLOCK_AES128 = "http://www.w3.org/2001/04/xmlenc#aes128-cbc"
|
||||
BLOCK_AES192 = "http://www.w3.org/2001/04/xmlenc#aes192-cbc"
|
||||
BLOCK_AES256 = "http://www.w3.org/2001/04/xmlenc#aes256-cbc"
|
||||
DIGEST_RIPEMD160 = "http://www.w3.org/2001/04/xmlenc#ripemd160"
|
||||
DIGEST_SHA256 = "http://www.w3.org/2001/04/xmlenc#sha256"
|
||||
DIGEST_SHA512 = "http://www.w3.org/2001/04/xmlenc#sha512"
|
||||
KA_DH = "http://www.w3.org/2001/04/xmlenc#dh"
|
||||
KT_RSA_1_5 = "http://www.w3.org/2001/04/xmlenc#rsa-1_5"
|
||||
KT_RSA_OAEP = "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p"
|
||||
STREAM_ARCFOUR = "http://www.w3.org/2001/04/xmlenc#arcfour"
|
||||
WRAP_3DES = "http://www.w3.org/2001/04/xmlenc#kw-3des"
|
||||
WRAP_AES128 = "http://www.w3.org/2001/04/xmlenc#kw-aes128"
|
||||
WRAP_AES192 = "http://www.w3.org/2001/04/xmlenc#kw-aes192"
|
||||
WRAP_AES256 = "http://www.w3.org/2001/04/xmlenc#kw-aes256"
|
||||
|
||||
|
||||
class WSSE:
|
||||
BASE = "http://schemas.xmlsoap.org/ws/2002/04/secext"
|
||||
|
||||
class WSU:
|
||||
BASE = "http://schemas.xmlsoap.org/ws/2002/04/utility"
|
||||
UTILITY = "http://schemas.xmlsoap.org/ws/2002/07/utility"
|
||||
|
||||
class WSR:
|
||||
PROPERTIES = "http://www.ibm.com/xmlns/stdwip/web-services/WS-ResourceProperties"
|
||||
LIFETIME = "http://www.ibm.com/xmlns/stdwip/web-services/WS-ResourceLifetime"
|
||||
|
||||
class WSA:
|
||||
ADDRESS = "http://schemas.xmlsoap.org/ws/2003/03/addressing"
|
||||
ADDRESS2004 = "http://schemas.xmlsoap.org/ws/2004/03/addressing"
|
||||
ANONYMOUS = "%s/role/anonymous" %ADDRESS
|
||||
ANONYMOUS2004 = "%s/role/anonymous" %ADDRESS2004
|
||||
FAULT = "http://schemas.xmlsoap.org/ws/2004/03/addressing/fault"
|
||||
|
||||
class WSP:
|
||||
POLICY = "http://schemas.xmlsoap.org/ws/2002/12/policy"
|
||||
|
||||
|
||||
|
@ -1,179 +0,0 @@
|
||||
"""Based on code from timeout_socket.py, with some tweaks for compatibility.
|
||||
These tweaks should really be rolled back into timeout_socket, but it's
|
||||
not totally clear who is maintaining it at this point. In the meantime,
|
||||
we'll use a different module name for our tweaked version to avoid any
|
||||
confusion.
|
||||
|
||||
The original timeout_socket is by:
|
||||
|
||||
Scott Cotton <scott@chronis.pobox.com>
|
||||
Lloyd Zusman <ljz@asfast.com>
|
||||
Phil Mayes <pmayes@olivebr.com>
|
||||
Piers Lauder <piers@cs.su.oz.au>
|
||||
Radovan Garabik <garabik@melkor.dnp.fmph.uniba.sk>
|
||||
"""
|
||||
|
||||
ident = "$Id$"
|
||||
|
||||
import string, socket, select, errno
|
||||
|
||||
WSAEINVAL = getattr(errno, 'WSAEINVAL', 10022)
|
||||
|
||||
|
||||
class TimeoutSocket:
|
||||
"""A socket imposter that supports timeout limits."""
|
||||
|
||||
def __init__(self, timeout=20, sock=None):
|
||||
self.timeout = float(timeout)
|
||||
self.inbuf = ''
|
||||
if sock is None:
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.sock = sock
|
||||
self.sock.setblocking(0)
|
||||
self._rbuf = ''
|
||||
self._wbuf = ''
|
||||
|
||||
def __getattr__(self, name):
|
||||
# Delegate to real socket attributes.
|
||||
return getattr(self.sock, name)
|
||||
|
||||
def connect(self, *addr):
|
||||
timeout = self.timeout
|
||||
sock = self.sock
|
||||
try:
|
||||
# Non-blocking mode
|
||||
sock.setblocking(0)
|
||||
apply(sock.connect, addr)
|
||||
sock.setblocking(timeout != 0)
|
||||
return 1
|
||||
except socket.error,why:
|
||||
if not timeout:
|
||||
raise
|
||||
sock.setblocking(1)
|
||||
if len(why.args) == 1:
|
||||
code = 0
|
||||
else:
|
||||
code, why = why
|
||||
if code not in (
|
||||
errno.EINPROGRESS, errno.EALREADY, errno.EWOULDBLOCK
|
||||
):
|
||||
raise
|
||||
r,w,e = select.select([],[sock],[],timeout)
|
||||
if w:
|
||||
try:
|
||||
apply(sock.connect, addr)
|
||||
return 1
|
||||
except socket.error,why:
|
||||
if len(why.args) == 1:
|
||||
code = 0
|
||||
else:
|
||||
code, why = why
|
||||
if code in (errno.EISCONN, WSAEINVAL):
|
||||
return 1
|
||||
raise
|
||||
raise TimeoutError('socket connect() timeout.')
|
||||
|
||||
def send(self, data, flags=0):
|
||||
total = len(data)
|
||||
next = 0
|
||||
while 1:
|
||||
r, w, e = select.select([],[self.sock], [], self.timeout)
|
||||
if w:
|
||||
buff = data[next:next + 8192]
|
||||
sent = self.sock.send(buff, flags)
|
||||
next = next + sent
|
||||
if next == total:
|
||||
return total
|
||||
continue
|
||||
raise TimeoutError('socket send() timeout.')
|
||||
|
||||
def recv(self, amt, flags=0):
|
||||
if select.select([self.sock], [], [], self.timeout)[0]:
|
||||
return self.sock.recv(amt, flags)
|
||||
raise TimeoutError('socket recv() timeout.')
|
||||
|
||||
buffsize = 4096
|
||||
handles = 1
|
||||
|
||||
def makefile(self, mode="r", buffsize=-1):
|
||||
self.handles = self.handles + 1
|
||||
self.mode = mode
|
||||
return self
|
||||
|
||||
def close(self):
|
||||
self.handles = self.handles - 1
|
||||
if self.handles == 0 and self.sock.fileno() >= 0:
|
||||
self.sock.close()
|
||||
|
||||
def read(self, n=-1):
|
||||
if not isinstance(n, type(1)):
|
||||
n = -1
|
||||
if n >= 0:
|
||||
k = len(self._rbuf)
|
||||
if n <= k:
|
||||
data = self._rbuf[:n]
|
||||
self._rbuf = self._rbuf[n:]
|
||||
return data
|
||||
n = n - k
|
||||
L = [self._rbuf]
|
||||
self._rbuf = ""
|
||||
while n > 0:
|
||||
new = self.recv(max(n, self.buffsize))
|
||||
if not new: break
|
||||
k = len(new)
|
||||
if k > n:
|
||||
L.append(new[:n])
|
||||
self._rbuf = new[n:]
|
||||
break
|
||||
L.append(new)
|
||||
n = n - k
|
||||
return "".join(L)
|
||||
k = max(4096, self.buffsize)
|
||||
L = [self._rbuf]
|
||||
self._rbuf = ""
|
||||
while 1:
|
||||
new = self.recv(k)
|
||||
if not new: break
|
||||
L.append(new)
|
||||
k = min(k*2, 1024**2)
|
||||
return "".join(L)
|
||||
|
||||
def readline(self, limit=-1):
|
||||
data = ""
|
||||
i = self._rbuf.find('\n')
|
||||
while i < 0 and not (0 < limit <= len(self._rbuf)):
|
||||
new = self.recv(self.buffsize)
|
||||
if not new: break
|
||||
i = new.find('\n')
|
||||
if i >= 0: i = i + len(self._rbuf)
|
||||
self._rbuf = self._rbuf + new
|
||||
if i < 0: i = len(self._rbuf)
|
||||
else: i = i+1
|
||||
if 0 <= limit < len(self._rbuf): i = limit
|
||||
data, self._rbuf = self._rbuf[:i], self._rbuf[i:]
|
||||
return data
|
||||
|
||||
def readlines(self, sizehint = 0):
|
||||
total = 0
|
||||
list = []
|
||||
while 1:
|
||||
line = self.readline()
|
||||
if not line: break
|
||||
list.append(line)
|
||||
total += len(line)
|
||||
if sizehint and total >= sizehint:
|
||||
break
|
||||
return list
|
||||
|
||||
def writelines(self, list):
|
||||
self.send(''.join(list))
|
||||
|
||||
def write(self, data):
|
||||
self.send(data)
|
||||
|
||||
def flush(self):
|
||||
pass
|
||||
|
||||
|
||||
class TimeoutError(Exception):
|
||||
pass
|
@ -1,99 +0,0 @@
|
||||
"""
|
||||
A more or less complete user-defined wrapper around tuple objects.
|
||||
Adapted version of the standard library's UserList.
|
||||
|
||||
Taken from Stefan Schwarzer's ftputil library, available at
|
||||
<http://www.ndh.net/home/sschwarzer/python/python_software.html>, and used under this license:
|
||||
|
||||
|
||||
|
||||
|
||||
Copyright (C) 1999, Stefan Schwarzer
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
- Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
- Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
- Neither the name of the above author nor the names of the
|
||||
contributors to the software may be used to endorse or promote
|
||||
products derived from this software without specific prior written
|
||||
permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
|
||||
|
||||
# $Id$
|
||||
|
||||
#XXX tuple instances (in Python 2.2) contain also:
|
||||
# __class__, __delattr__, __getattribute__, __hash__, __new__,
|
||||
# __reduce__, __setattr__, __str__
|
||||
# What about these?
|
||||
|
||||
class UserTuple:
|
||||
def __init__(self, inittuple=None):
|
||||
self.data = ()
|
||||
if inittuple is not None:
|
||||
# XXX should this accept an arbitrary sequence?
|
||||
if type(inittuple) == type(self.data):
|
||||
self.data = inittuple
|
||||
elif isinstance(inittuple, UserTuple):
|
||||
# this results in
|
||||
# self.data is inittuple.data
|
||||
# but that's ok for tuples because they are
|
||||
# immutable. (Builtin tuples behave the same.)
|
||||
self.data = inittuple.data[:]
|
||||
else:
|
||||
# the same applies here; (t is tuple(t)) == 1
|
||||
self.data = tuple(inittuple)
|
||||
def __repr__(self): return repr(self.data)
|
||||
def __lt__(self, other): return self.data < self.__cast(other)
|
||||
def __le__(self, other): return self.data <= self.__cast(other)
|
||||
def __eq__(self, other): return self.data == self.__cast(other)
|
||||
def __ne__(self, other): return self.data != self.__cast(other)
|
||||
def __gt__(self, other): return self.data > self.__cast(other)
|
||||
def __ge__(self, other): return self.data >= self.__cast(other)
|
||||
def __cast(self, other):
|
||||
if isinstance(other, UserTuple): return other.data
|
||||
else: return other
|
||||
def __cmp__(self, other):
|
||||
return cmp(self.data, self.__cast(other))
|
||||
def __contains__(self, item): return item in self.data
|
||||
def __len__(self): return len(self.data)
|
||||
def __getitem__(self, i): return self.data[i]
|
||||
def __getslice__(self, i, j):
|
||||
i = max(i, 0); j = max(j, 0)
|
||||
return self.__class__(self.data[i:j])
|
||||
def __add__(self, other):
|
||||
if isinstance(other, UserTuple):
|
||||
return self.__class__(self.data + other.data)
|
||||
elif isinstance(other, type(self.data)):
|
||||
return self.__class__(self.data + other)
|
||||
else:
|
||||
return self.__class__(self.data + tuple(other))
|
||||
# dir( () ) contains no __radd__ (at least in Python 2.2)
|
||||
def __mul__(self, n):
|
||||
return self.__class__(self.data*n)
|
||||
__rmul__ = __mul__
|
||||
|
@ -1,839 +0,0 @@
|
||||
# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
|
||||
#
|
||||
# This software is subject to the provisions of the Zope Public License,
|
||||
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
|
||||
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
|
||||
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
|
||||
# FOR A PARTICULAR PURPOSE.
|
||||
|
||||
ident = "$Id$"
|
||||
|
||||
import types
|
||||
import string, httplib, smtplib, urllib, socket, weakref
|
||||
import xml.dom.minidom
|
||||
from string import join, strip, split
|
||||
from UserDict import UserDict
|
||||
from StringIO import StringIO
|
||||
from TimeoutSocket import TimeoutSocket, TimeoutError
|
||||
from urlparse import urlparse
|
||||
from httplib import HTTPConnection, HTTPSConnection
|
||||
from exceptions import Exception
|
||||
|
||||
try:
|
||||
from xml.dom.ext import SplitQName
|
||||
except:
|
||||
def SplitQName(qname):
|
||||
'''SplitQName(qname) -> (string, string)
|
||||
|
||||
Split Qualified Name into a tuple of len 2, consisting
|
||||
of the prefix and the local name.
|
||||
|
||||
(prefix, localName)
|
||||
|
||||
Special Cases:
|
||||
xmlns -- (localName, 'xmlns')
|
||||
None -- (None, localName)
|
||||
'''
|
||||
|
||||
l = qname.split(':')
|
||||
if len(l) == 1:
|
||||
l.insert(0, None)
|
||||
elif len(l) == 2:
|
||||
if l[0] == 'xmlns':
|
||||
l.reverse()
|
||||
else:
|
||||
return
|
||||
return tuple(l)
|
||||
|
||||
class RecursionError(Exception):
|
||||
"""Used to indicate a HTTP redirect recursion."""
|
||||
pass
|
||||
|
||||
class HTTPResponse:
|
||||
"""Captures the information in an HTTP response message."""
|
||||
|
||||
def __init__(self, response):
|
||||
self.status = response.status
|
||||
self.reason = response.reason
|
||||
self.headers = response.msg
|
||||
self.body = response.read() or None
|
||||
response.close()
|
||||
|
||||
class TimeoutHTTP(HTTPConnection):
|
||||
"""A custom http connection object that supports socket timeout."""
|
||||
def __init__(self, host, port=None, timeout=20):
|
||||
HTTPConnection.__init__(self, host, port)
|
||||
self.timeout = timeout
|
||||
|
||||
def connect(self):
|
||||
self.sock = TimeoutSocket(self.timeout)
|
||||
self.sock.connect((self.host, self.port))
|
||||
|
||||
|
||||
class TimeoutHTTPS(HTTPSConnection):
|
||||
"""A custom https object that supports socket timeout. Note that this
|
||||
is not really complete. The builtin SSL support in the Python socket
|
||||
module requires a real socket (type) to be passed in to be hooked to
|
||||
SSL. That means our fake socket won't work and our timeout hacks are
|
||||
bypassed for send and recv calls. Since our hack _is_ in place at
|
||||
connect() time, it should at least provide some timeout protection."""
|
||||
def __init__(self, host, port=None, timeout=20, **kwargs):
|
||||
HTTPSConnection.__init__(self, str(host), port, **kwargs)
|
||||
self.timeout = timeout
|
||||
|
||||
def connect(self):
|
||||
sock = TimeoutSocket(self.timeout)
|
||||
sock.connect((self.host, self.port))
|
||||
realsock = getattr(sock.sock, '_sock', sock.sock)
|
||||
ssl = socket.ssl(realsock, self.key_file, self.cert_file)
|
||||
self.sock = httplib.FakeSocket(sock, ssl)
|
||||
|
||||
def urlopen(url, timeout=20, redirects=None):
|
||||
"""A minimal urlopen replacement hack that supports timeouts for http.
|
||||
Note that this supports GET only."""
|
||||
scheme, host, path, params, query, frag = urlparse(url)
|
||||
if not scheme in ('http', 'https'):
|
||||
return urllib.urlopen(url)
|
||||
if params: path = '%s;%s' % (path, params)
|
||||
if query: path = '%s?%s' % (path, query)
|
||||
if frag: path = '%s#%s' % (path, frag)
|
||||
|
||||
if scheme == 'https':
|
||||
# If ssl is not compiled into Python, you will not get an exception
|
||||
# until a conn.endheaders() call. We need to know sooner, so use
|
||||
# getattr.
|
||||
if hasattr(socket, 'ssl'):
|
||||
conn = TimeoutHTTPS(host, None, timeout)
|
||||
else:
|
||||
import M2Crypto
|
||||
ctx = M2Crypto.SSL.Context()
|
||||
ctx.set_session_timeout(timeout)
|
||||
conn = M2Crypto.httpslib.HTTPSConnection(host, ssl_context=ctx)
|
||||
#conn.set_debuglevel(1)
|
||||
else:
|
||||
conn = TimeoutHTTP(host, None, timeout)
|
||||
|
||||
conn.putrequest('GET', path)
|
||||
conn.putheader('Connection', 'close')
|
||||
conn.endheaders()
|
||||
response = None
|
||||
while 1:
|
||||
response = conn.getresponse()
|
||||
if response.status != 100:
|
||||
break
|
||||
conn._HTTPConnection__state = httplib._CS_REQ_SENT
|
||||
conn._HTTPConnection__response = None
|
||||
|
||||
status = response.status
|
||||
|
||||
# If we get an HTTP redirect, we will follow it automatically.
|
||||
if status >= 300 and status < 400:
|
||||
location = response.msg.getheader('location')
|
||||
if location is not None:
|
||||
response.close()
|
||||
if redirects is not None and redirects.has_key(location):
|
||||
raise RecursionError(
|
||||
'Circular HTTP redirection detected.'
|
||||
)
|
||||
if redirects is None:
|
||||
redirects = {}
|
||||
redirects[location] = 1
|
||||
return urlopen(location, timeout, redirects)
|
||||
raise HTTPResponse(response)
|
||||
|
||||
if not (status >= 200 and status < 300):
|
||||
raise HTTPResponse(response)
|
||||
|
||||
body = StringIO(response.read())
|
||||
response.close()
|
||||
return body
|
||||
|
||||
class DOM:
|
||||
"""The DOM singleton defines a number of XML related constants and
|
||||
provides a number of utility methods for DOM related tasks. It
|
||||
also provides some basic abstractions so that the rest of the
|
||||
package need not care about actual DOM implementation in use."""
|
||||
|
||||
# Namespace stuff related to the SOAP specification.
|
||||
|
||||
NS_SOAP_ENV_1_1 = 'http://schemas.xmlsoap.org/soap/envelope/'
|
||||
NS_SOAP_ENC_1_1 = 'http://schemas.xmlsoap.org/soap/encoding/'
|
||||
|
||||
NS_SOAP_ENV_1_2 = 'http://www.w3.org/2001/06/soap-envelope'
|
||||
NS_SOAP_ENC_1_2 = 'http://www.w3.org/2001/06/soap-encoding'
|
||||
|
||||
NS_SOAP_ENV_ALL = (NS_SOAP_ENV_1_1, NS_SOAP_ENV_1_2)
|
||||
NS_SOAP_ENC_ALL = (NS_SOAP_ENC_1_1, NS_SOAP_ENC_1_2)
|
||||
|
||||
NS_SOAP_ENV = NS_SOAP_ENV_1_1
|
||||
NS_SOAP_ENC = NS_SOAP_ENC_1_1
|
||||
|
||||
_soap_uri_mapping = {
|
||||
NS_SOAP_ENV_1_1 : '1.1',
|
||||
NS_SOAP_ENV_1_2 : '1.2',
|
||||
}
|
||||
|
||||
SOAP_ACTOR_NEXT_1_1 = 'http://schemas.xmlsoap.org/soap/actor/next'
|
||||
SOAP_ACTOR_NEXT_1_2 = 'http://www.w3.org/2001/06/soap-envelope/actor/next'
|
||||
SOAP_ACTOR_NEXT_ALL = (SOAP_ACTOR_NEXT_1_1, SOAP_ACTOR_NEXT_1_2)
|
||||
|
||||
def SOAPUriToVersion(self, uri):
|
||||
"""Return the SOAP version related to an envelope uri."""
|
||||
value = self._soap_uri_mapping.get(uri)
|
||||
if value is not None:
|
||||
return value
|
||||
raise ValueError(
|
||||
'Unsupported SOAP envelope uri: %s' % uri
|
||||
)
|
||||
|
||||
def GetSOAPEnvUri(self, version):
|
||||
"""Return the appropriate SOAP envelope uri for a given
|
||||
human-friendly SOAP version string (e.g. '1.1')."""
|
||||
attrname = 'NS_SOAP_ENV_%s' % join(split(version, '.'), '_')
|
||||
value = getattr(self, attrname, None)
|
||||
if value is not None:
|
||||
return value
|
||||
raise ValueError(
|
||||
'Unsupported SOAP version: %s' % version
|
||||
)
|
||||
|
||||
def GetSOAPEncUri(self, version):
|
||||
"""Return the appropriate SOAP encoding uri for a given
|
||||
human-friendly SOAP version string (e.g. '1.1')."""
|
||||
attrname = 'NS_SOAP_ENC_%s' % join(split(version, '.'), '_')
|
||||
value = getattr(self, attrname, None)
|
||||
if value is not None:
|
||||
return value
|
||||
raise ValueError(
|
||||
'Unsupported SOAP version: %s' % version
|
||||
)
|
||||
|
||||
def GetSOAPActorNextUri(self, version):
|
||||
"""Return the right special next-actor uri for a given
|
||||
human-friendly SOAP version string (e.g. '1.1')."""
|
||||
attrname = 'SOAP_ACTOR_NEXT_%s' % join(split(version, '.'), '_')
|
||||
value = getattr(self, attrname, None)
|
||||
if value is not None:
|
||||
return value
|
||||
raise ValueError(
|
||||
'Unsupported SOAP version: %s' % version
|
||||
)
|
||||
|
||||
|
||||
# Namespace stuff related to XML Schema.
|
||||
|
||||
NS_XSD_99 = 'http://www.w3.org/1999/XMLSchema'
|
||||
NS_XSI_99 = 'http://www.w3.org/1999/XMLSchema-instance'
|
||||
|
||||
NS_XSD_00 = 'http://www.w3.org/2000/10/XMLSchema'
|
||||
NS_XSI_00 = 'http://www.w3.org/2000/10/XMLSchema-instance'
|
||||
|
||||
NS_XSD_01 = 'http://www.w3.org/2001/XMLSchema'
|
||||
NS_XSI_01 = 'http://www.w3.org/2001/XMLSchema-instance'
|
||||
|
||||
NS_XSD_ALL = (NS_XSD_99, NS_XSD_00, NS_XSD_01)
|
||||
NS_XSI_ALL = (NS_XSI_99, NS_XSI_00, NS_XSI_01)
|
||||
|
||||
NS_XSD = NS_XSD_01
|
||||
NS_XSI = NS_XSI_01
|
||||
|
||||
_xsd_uri_mapping = {
|
||||
NS_XSD_99 : NS_XSI_99,
|
||||
NS_XSD_00 : NS_XSI_00,
|
||||
NS_XSD_01 : NS_XSI_01,
|
||||
}
|
||||
|
||||
for key, value in _xsd_uri_mapping.items():
|
||||
_xsd_uri_mapping[value] = key
|
||||
|
||||
|
||||
def InstanceUriForSchemaUri(self, uri):
|
||||
"""Return the appropriate matching XML Schema instance uri for
|
||||
the given XML Schema namespace uri."""
|
||||
return self._xsd_uri_mapping.get(uri)
|
||||
|
||||
def SchemaUriForInstanceUri(self, uri):
|
||||
"""Return the appropriate matching XML Schema namespace uri for
|
||||
the given XML Schema instance namespace uri."""
|
||||
return self._xsd_uri_mapping.get(uri)
|
||||
|
||||
|
||||
# Namespace stuff related to WSDL.
|
||||
|
||||
NS_WSDL_1_1 = 'http://schemas.xmlsoap.org/wsdl/'
|
||||
NS_WSDL_ALL = (NS_WSDL_1_1,)
|
||||
NS_WSDL = NS_WSDL_1_1
|
||||
|
||||
NS_SOAP_BINDING_1_1 = 'http://schemas.xmlsoap.org/wsdl/soap/'
|
||||
NS_HTTP_BINDING_1_1 = 'http://schemas.xmlsoap.org/wsdl/http/'
|
||||
NS_MIME_BINDING_1_1 = 'http://schemas.xmlsoap.org/wsdl/mime/'
|
||||
|
||||
NS_SOAP_BINDING_ALL = (NS_SOAP_BINDING_1_1,)
|
||||
NS_HTTP_BINDING_ALL = (NS_HTTP_BINDING_1_1,)
|
||||
NS_MIME_BINDING_ALL = (NS_MIME_BINDING_1_1,)
|
||||
|
||||
NS_SOAP_BINDING = NS_SOAP_BINDING_1_1
|
||||
NS_HTTP_BINDING = NS_HTTP_BINDING_1_1
|
||||
NS_MIME_BINDING = NS_MIME_BINDING_1_1
|
||||
|
||||
NS_SOAP_HTTP_1_1 = 'http://schemas.xmlsoap.org/soap/http'
|
||||
NS_SOAP_HTTP_ALL = (NS_SOAP_HTTP_1_1,)
|
||||
NS_SOAP_HTTP = NS_SOAP_HTTP_1_1
|
||||
|
||||
|
||||
_wsdl_uri_mapping = {
|
||||
NS_WSDL_1_1 : '1.1',
|
||||
}
|
||||
|
||||
def WSDLUriToVersion(self, uri):
|
||||
"""Return the WSDL version related to a WSDL namespace uri."""
|
||||
value = self._wsdl_uri_mapping.get(uri)
|
||||
if value is not None:
|
||||
return value
|
||||
raise ValueError(
|
||||
'Unsupported SOAP envelope uri: %s' % uri
|
||||
)
|
||||
|
||||
def GetWSDLUri(self, version):
|
||||
attr = 'NS_WSDL_%s' % join(split(version, '.'), '_')
|
||||
value = getattr(self, attr, None)
|
||||
if value is not None:
|
||||
return value
|
||||
raise ValueError(
|
||||
'Unsupported WSDL version: %s' % version
|
||||
)
|
||||
|
||||
def GetWSDLSoapBindingUri(self, version):
|
||||
attr = 'NS_SOAP_BINDING_%s' % join(split(version, '.'), '_')
|
||||
value = getattr(self, attr, None)
|
||||
if value is not None:
|
||||
return value
|
||||
raise ValueError(
|
||||
'Unsupported WSDL version: %s' % version
|
||||
)
|
||||
|
||||
def GetWSDLHttpBindingUri(self, version):
|
||||
attr = 'NS_HTTP_BINDING_%s' % join(split(version, '.'), '_')
|
||||
value = getattr(self, attr, None)
|
||||
if value is not None:
|
||||
return value
|
||||
raise ValueError(
|
||||
'Unsupported WSDL version: %s' % version
|
||||
)
|
||||
|
||||
def GetWSDLMimeBindingUri(self, version):
|
||||
attr = 'NS_MIME_BINDING_%s' % join(split(version, '.'), '_')
|
||||
value = getattr(self, attr, None)
|
||||
if value is not None:
|
||||
return value
|
||||
raise ValueError(
|
||||
'Unsupported WSDL version: %s' % version
|
||||
)
|
||||
|
||||
def GetWSDLHttpTransportUri(self, version):
|
||||
attr = 'NS_SOAP_HTTP_%s' % join(split(version, '.'), '_')
|
||||
value = getattr(self, attr, None)
|
||||
if value is not None:
|
||||
return value
|
||||
raise ValueError(
|
||||
'Unsupported WSDL version: %s' % version
|
||||
)
|
||||
|
||||
|
||||
# Other xml namespace constants.
|
||||
NS_XMLNS = 'http://www.w3.org/2000/xmlns/'
|
||||
|
||||
|
||||
|
||||
def isElement(self, node, name, nsuri=None):
|
||||
"""Return true if the given node is an element with the given
|
||||
name and optional namespace uri."""
|
||||
if node.nodeType != node.ELEMENT_NODE:
|
||||
return 0
|
||||
return node.localName == name and \
|
||||
(nsuri is None or self.nsUriMatch(node.namespaceURI, nsuri))
|
||||
|
||||
def getElement(self, node, name, nsuri=None, default=join):
|
||||
"""Return the first child of node with a matching name and
|
||||
namespace uri, or the default if one is provided."""
|
||||
nsmatch = self.nsUriMatch
|
||||
ELEMENT_NODE = node.ELEMENT_NODE
|
||||
for child in node.childNodes:
|
||||
if child.nodeType == ELEMENT_NODE:
|
||||
if ((child.localName == name or name is None) and
|
||||
(nsuri is None or nsmatch(child.namespaceURI, nsuri))
|
||||
):
|
||||
return child
|
||||
if default is not join:
|
||||
return default
|
||||
raise KeyError, name
|
||||
|
||||
def getElementById(self, node, id, default=join):
|
||||
"""Return the first child of node matching an id reference."""
|
||||
attrget = self.getAttr
|
||||
ELEMENT_NODE = node.ELEMENT_NODE
|
||||
for child in node.childNodes:
|
||||
if child.nodeType == ELEMENT_NODE:
|
||||
if attrget(child, 'id') == id:
|
||||
return child
|
||||
if default is not join:
|
||||
return default
|
||||
raise KeyError, name
|
||||
|
||||
def getMappingById(self, document, depth=None, element=None,
|
||||
mapping=None, level=1):
|
||||
"""Create an id -> element mapping of those elements within a
|
||||
document that define an id attribute. The depth of the search
|
||||
may be controlled by using the (1-based) depth argument."""
|
||||
if document is not None:
|
||||
element = document.documentElement
|
||||
mapping = {}
|
||||
attr = element._attrs.get('id', None)
|
||||
if attr is not None:
|
||||
mapping[attr.value] = element
|
||||
if depth is None or depth > level:
|
||||
level = level + 1
|
||||
ELEMENT_NODE = element.ELEMENT_NODE
|
||||
for child in element.childNodes:
|
||||
if child.nodeType == ELEMENT_NODE:
|
||||
self.getMappingById(None, depth, child, mapping, level)
|
||||
return mapping
|
||||
|
||||
def getElements(self, node, name, nsuri=None):
|
||||
"""Return a sequence of the child elements of the given node that
|
||||
match the given name and optional namespace uri."""
|
||||
nsmatch = self.nsUriMatch
|
||||
result = []
|
||||
ELEMENT_NODE = node.ELEMENT_NODE
|
||||
for child in node.childNodes:
|
||||
if child.nodeType == ELEMENT_NODE:
|
||||
if ((child.localName == name or name is None) and (
|
||||
(nsuri is None) or nsmatch(child.namespaceURI, nsuri))):
|
||||
result.append(child)
|
||||
return result
|
||||
|
||||
def hasAttr(self, node, name, nsuri=None):
|
||||
"""Return true if element has attribute with the given name and
|
||||
optional nsuri. If nsuri is not specified, returns true if an
|
||||
attribute exists with the given name with any namespace."""
|
||||
if nsuri is None:
|
||||
if node.hasAttribute(name):
|
||||
return True
|
||||
return False
|
||||
return node.hasAttributeNS(nsuri, name)
|
||||
|
||||
def getAttr(self, node, name, nsuri=None, default=join):
|
||||
"""Return the value of the attribute named 'name' with the
|
||||
optional nsuri, or the default if one is specified. If
|
||||
nsuri is not specified, an attribute that matches the
|
||||
given name will be returned regardless of namespace."""
|
||||
if nsuri is None:
|
||||
result = node._attrs.get(name, None)
|
||||
if result is None:
|
||||
for item in node._attrsNS.keys():
|
||||
if item[1] == name:
|
||||
result = node._attrsNS[item]
|
||||
break
|
||||
else:
|
||||
result = node._attrsNS.get((nsuri, name), None)
|
||||
if result is not None:
|
||||
return result.value
|
||||
if default is not join:
|
||||
return default
|
||||
return ''
|
||||
|
||||
def getAttrs(self, node):
|
||||
"""Return a Collection of all attributes
|
||||
"""
|
||||
attrs = {}
|
||||
for k,v in node._attrs.items():
|
||||
attrs[k] = v.value
|
||||
return attrs
|
||||
|
||||
def getElementText(self, node, preserve_ws=None):
|
||||
"""Return the text value of an xml element node. Leading and trailing
|
||||
whitespace is stripped from the value unless the preserve_ws flag
|
||||
is passed with a true value."""
|
||||
result = []
|
||||
for child in node.childNodes:
|
||||
nodetype = child.nodeType
|
||||
if nodetype == child.TEXT_NODE or \
|
||||
nodetype == child.CDATA_SECTION_NODE:
|
||||
result.append(child.nodeValue)
|
||||
value = join(result, '')
|
||||
if preserve_ws is None:
|
||||
value = strip(value)
|
||||
return value
|
||||
|
||||
def findNamespaceURI(self, prefix, node):
|
||||
"""Find a namespace uri given a prefix and a context node."""
|
||||
attrkey = (self.NS_XMLNS, prefix)
|
||||
DOCUMENT_NODE = node.DOCUMENT_NODE
|
||||
ELEMENT_NODE = node.ELEMENT_NODE
|
||||
while 1:
|
||||
if node.nodeType != ELEMENT_NODE:
|
||||
node = node.parentNode
|
||||
continue
|
||||
result = node._attrsNS.get(attrkey, None)
|
||||
if result is not None:
|
||||
return result.value
|
||||
if hasattr(node, '__imported__'):
|
||||
raise DOMException('Value for prefix %s not found.' % prefix)
|
||||
node = node.parentNode
|
||||
if node.nodeType == DOCUMENT_NODE:
|
||||
raise DOMException('Value for prefix %s not found.' % prefix)
|
||||
|
||||
def findDefaultNS(self, node):
|
||||
"""Return the current default namespace uri for the given node."""
|
||||
attrkey = (self.NS_XMLNS, 'xmlns')
|
||||
DOCUMENT_NODE = node.DOCUMENT_NODE
|
||||
ELEMENT_NODE = node.ELEMENT_NODE
|
||||
while 1:
|
||||
if node.nodeType != ELEMENT_NODE:
|
||||
node = node.parentNode
|
||||
continue
|
||||
result = node._attrsNS.get(attrkey, None)
|
||||
if result is not None:
|
||||
return result.value
|
||||
if hasattr(node, '__imported__'):
|
||||
raise DOMException('Cannot determine default namespace.')
|
||||
node = node.parentNode
|
||||
if node.nodeType == DOCUMENT_NODE:
|
||||
raise DOMException('Cannot determine default namespace.')
|
||||
|
||||
def findTargetNS(self, node):
|
||||
"""Return the defined target namespace uri for the given node."""
|
||||
attrget = self.getAttr
|
||||
attrkey = (self.NS_XMLNS, 'xmlns')
|
||||
DOCUMENT_NODE = node.DOCUMENT_NODE
|
||||
ELEMENT_NODE = node.ELEMENT_NODE
|
||||
while 1:
|
||||
if node.nodeType != ELEMENT_NODE:
|
||||
node = node.parentNode
|
||||
continue
|
||||
result = attrget(node, 'targetNamespace', default=None)
|
||||
if result is not None:
|
||||
return result
|
||||
node = node.parentNode
|
||||
if node.nodeType == DOCUMENT_NODE:
|
||||
raise DOMException('Cannot determine target namespace.')
|
||||
|
||||
def getTypeRef(self, element):
|
||||
"""Return (namespaceURI, name) for a type attribue of the given
|
||||
element, or None if the element does not have a type attribute."""
|
||||
typeattr = self.getAttr(element, 'type', default=None)
|
||||
if typeattr is None:
|
||||
return None
|
||||
parts = typeattr.split(':', 1)
|
||||
if len(parts) == 2:
|
||||
nsuri = self.findNamespaceURI(parts[0], element)
|
||||
else:
|
||||
nsuri = self.findDefaultNS(element)
|
||||
return (nsuri, parts[1])
|
||||
|
||||
def importNode(self, document, node, deep=0):
|
||||
"""Implements (well enough for our purposes) DOM node import."""
|
||||
nodetype = node.nodeType
|
||||
if nodetype in (node.DOCUMENT_NODE, node.DOCUMENT_TYPE_NODE):
|
||||
raise DOMException('Illegal node type for importNode')
|
||||
if nodetype == node.ENTITY_REFERENCE_NODE:
|
||||
deep = 0
|
||||
clone = node.cloneNode(deep)
|
||||
self._setOwnerDoc(document, clone)
|
||||
clone.__imported__ = 1
|
||||
return clone
|
||||
|
||||
def _setOwnerDoc(self, document, node):
|
||||
node.ownerDocument = document
|
||||
for child in node.childNodes:
|
||||
self._setOwnerDoc(document, child)
|
||||
|
||||
def nsUriMatch(self, value, wanted, strict=0, tt=type(())):
|
||||
"""Return a true value if two namespace uri values match."""
|
||||
if value == wanted or (type(wanted) is tt) and value in wanted:
|
||||
return 1
|
||||
if not strict:
|
||||
wanted = type(wanted) is tt and wanted or (wanted,)
|
||||
value = value[-1:] != '/' and value or value[:-1]
|
||||
for item in wanted:
|
||||
if item == value or item[:-1] == value:
|
||||
return 1
|
||||
return 0
|
||||
|
||||
def createDocument(self, nsuri, qname, doctype=None):
|
||||
"""Create a new writable DOM document object."""
|
||||
impl = xml.dom.minidom.getDOMImplementation()
|
||||
return impl.createDocument(nsuri, qname, doctype)
|
||||
|
||||
def loadDocument(self, data):
|
||||
"""Load an xml file from a file-like object and return a DOM
|
||||
document instance."""
|
||||
return xml.dom.minidom.parse(data)
|
||||
|
||||
def loadFromURL(self, url):
|
||||
"""Load an xml file from a URL and return a DOM document."""
|
||||
file = urlopen(url)
|
||||
try: result = self.loadDocument(file)
|
||||
finally: file.close()
|
||||
return result
|
||||
|
||||
|
||||
class DOMException(Exception):
|
||||
pass
|
||||
|
||||
DOM = DOM()
|
||||
|
||||
|
||||
class Collection(UserDict):
|
||||
"""Helper class for maintaining ordered named collections."""
|
||||
default = lambda self,k: k.name
|
||||
def __init__(self, parent, key=None):
|
||||
UserDict.__init__(self)
|
||||
self.parent = weakref.ref(parent)
|
||||
self.list = []
|
||||
self._func = key or self.default
|
||||
|
||||
def __getitem__(self, key):
|
||||
if type(key) is type(1):
|
||||
return self.list[key]
|
||||
return self.data[key]
|
||||
|
||||
def __setitem__(self, key, item):
|
||||
item.parent = weakref.ref(self)
|
||||
self.list.append(item)
|
||||
self.data[key] = item
|
||||
|
||||
def keys(self):
|
||||
return map(lambda i: self._func(i), self.list)
|
||||
|
||||
def items(self):
|
||||
return map(lambda i: (self._func(i), i), self.list)
|
||||
|
||||
def values(self):
|
||||
return self.list
|
||||
|
||||
|
||||
class CollectionNS(UserDict):
|
||||
"""Helper class for maintaining ordered named collections."""
|
||||
default = lambda self,k: k.name
|
||||
def __init__(self, parent, key=None):
|
||||
UserDict.__init__(self)
|
||||
self.parent = weakref.ref(parent)
|
||||
self.targetNamespace = None
|
||||
self.list = []
|
||||
self._func = key or self.default
|
||||
|
||||
def __getitem__(self, key):
|
||||
self.targetNamespace = self.parent().targetNamespace
|
||||
if type(key) is types.IntType:
|
||||
return self.list[key]
|
||||
elif self.__isSequence(key):
|
||||
nsuri,name = key
|
||||
return self.data[nsuri][name]
|
||||
return self.data[self.parent().targetNamespace][key]
|
||||
|
||||
def __setitem__(self, key, item):
|
||||
item.parent = weakref.ref(self)
|
||||
self.list.append(item)
|
||||
targetNamespace = getattr(item, 'targetNamespace', self.parent().targetNamespace)
|
||||
if not self.data.has_key(targetNamespace):
|
||||
self.data[targetNamespace] = {}
|
||||
self.data[targetNamespace][key] = item
|
||||
|
||||
def __isSequence(self, key):
|
||||
return (type(key) in (types.TupleType,types.ListType) and len(key) == 2)
|
||||
|
||||
def keys(self):
|
||||
keys = []
|
||||
for tns in self.data.keys():
|
||||
keys.append(map(lambda i: (tns,self._func(i)), self.data[tns].values()))
|
||||
return keys
|
||||
|
||||
def items(self):
|
||||
return map(lambda i: (self._func(i), i), self.list)
|
||||
|
||||
def values(self):
|
||||
return self.list
|
||||
|
||||
|
||||
|
||||
# This is a runtime guerilla patch for pulldom (used by minidom) so
|
||||
# that xml namespace declaration attributes are not lost in parsing.
|
||||
# We need them to do correct QName linking for XML Schema and WSDL.
|
||||
# The patch has been submitted to SF for the next Python version.
|
||||
|
||||
from xml.dom.pulldom import PullDOM, START_ELEMENT
|
||||
if 1:
|
||||
def startPrefixMapping(self, prefix, uri):
|
||||
if not hasattr(self, '_xmlns_attrs'):
|
||||
self._xmlns_attrs = []
|
||||
self._xmlns_attrs.append((prefix or 'xmlns', uri))
|
||||
self._ns_contexts.append(self._current_context.copy())
|
||||
self._current_context[uri] = prefix or ''
|
||||
|
||||
PullDOM.startPrefixMapping = startPrefixMapping
|
||||
|
||||
def startElementNS(self, name, tagName , attrs):
|
||||
# Retrieve xml namespace declaration attributes.
|
||||
xmlns_uri = 'http://www.w3.org/2000/xmlns/'
|
||||
xmlns_attrs = getattr(self, '_xmlns_attrs', None)
|
||||
if xmlns_attrs is not None:
|
||||
for aname, value in xmlns_attrs:
|
||||
attrs._attrs[(xmlns_uri, aname)] = value
|
||||
self._xmlns_attrs = []
|
||||
uri, localname = name
|
||||
if uri:
|
||||
# When using namespaces, the reader may or may not
|
||||
# provide us with the original name. If not, create
|
||||
# *a* valid tagName from the current context.
|
||||
if tagName is None:
|
||||
prefix = self._current_context[uri]
|
||||
if prefix:
|
||||
tagName = prefix + ":" + localname
|
||||
else:
|
||||
tagName = localname
|
||||
if self.document:
|
||||
node = self.document.createElementNS(uri, tagName)
|
||||
else:
|
||||
node = self.buildDocument(uri, tagName)
|
||||
else:
|
||||
# When the tagname is not prefixed, it just appears as
|
||||
# localname
|
||||
if self.document:
|
||||
node = self.document.createElement(localname)
|
||||
else:
|
||||
node = self.buildDocument(None, localname)
|
||||
|
||||
for aname,value in attrs.items():
|
||||
a_uri, a_localname = aname
|
||||
if a_uri == xmlns_uri:
|
||||
if a_localname == 'xmlns':
|
||||
qname = a_localname
|
||||
else:
|
||||
qname = 'xmlns:' + a_localname
|
||||
attr = self.document.createAttributeNS(a_uri, qname)
|
||||
node.setAttributeNodeNS(attr)
|
||||
elif a_uri:
|
||||
prefix = self._current_context[a_uri]
|
||||
if prefix:
|
||||
qname = prefix + ":" + a_localname
|
||||
else:
|
||||
qname = a_localname
|
||||
attr = self.document.createAttributeNS(a_uri, qname)
|
||||
node.setAttributeNodeNS(attr)
|
||||
else:
|
||||
attr = self.document.createAttribute(a_localname)
|
||||
node.setAttributeNode(attr)
|
||||
attr.value = value
|
||||
|
||||
self.lastEvent[1] = [(START_ELEMENT, node), None]
|
||||
self.lastEvent = self.lastEvent[1]
|
||||
self.push(node)
|
||||
|
||||
PullDOM.startElementNS = startElementNS
|
||||
|
||||
#
|
||||
# This is a runtime guerilla patch for minidom so
|
||||
# that xmlns prefixed attributes dont raise AttributeErrors
|
||||
# during cloning.
|
||||
#
|
||||
# Namespace declarations can appear in any start-tag, must look for xmlns
|
||||
# prefixed attribute names during cloning.
|
||||
#
|
||||
# key (attr.namespaceURI, tag)
|
||||
# ('http://www.w3.org/2000/xmlns/', u'xsd') <xml.dom.minidom.Attr instance at 0x82227c4>
|
||||
# ('http://www.w3.org/2000/xmlns/', 'xmlns') <xml.dom.minidom.Attr instance at 0x8414b3c>
|
||||
#
|
||||
# xml.dom.minidom.Attr.nodeName = xmlns:xsd
|
||||
# xml.dom.minidom.Attr.value = = http://www.w3.org/2001/XMLSchema
|
||||
|
||||
if 1:
|
||||
def _clone_node(node, deep, newOwnerDocument):
|
||||
"""
|
||||
Clone a node and give it the new owner document.
|
||||
Called by Node.cloneNode and Document.importNode
|
||||
"""
|
||||
if node.ownerDocument.isSameNode(newOwnerDocument):
|
||||
operation = xml.dom.UserDataHandler.NODE_CLONED
|
||||
else:
|
||||
operation = xml.dom.UserDataHandler.NODE_IMPORTED
|
||||
if node.nodeType == xml.dom.minidom.Node.ELEMENT_NODE:
|
||||
clone = newOwnerDocument.createElementNS(node.namespaceURI,
|
||||
node.nodeName)
|
||||
for attr in node.attributes.values():
|
||||
clone.setAttributeNS(attr.namespaceURI, attr.nodeName, attr.value)
|
||||
|
||||
prefix, tag = xml.dom.minidom._nssplit(attr.nodeName)
|
||||
if prefix == 'xmlns':
|
||||
a = clone.getAttributeNodeNS(attr.namespaceURI, tag)
|
||||
elif prefix:
|
||||
a = clone.getAttributeNodeNS(attr.namespaceURI, tag)
|
||||
else:
|
||||
a = clone.getAttributeNodeNS(attr.namespaceURI, attr.nodeName)
|
||||
a.specified = attr.specified
|
||||
|
||||
if deep:
|
||||
for child in node.childNodes:
|
||||
c = xml.dom.minidom._clone_node(child, deep, newOwnerDocument)
|
||||
clone.appendChild(c)
|
||||
elif node.nodeType == xml.dom.minidom.Node.DOCUMENT_FRAGMENT_NODE:
|
||||
clone = newOwnerDocument.createDocumentFragment()
|
||||
if deep:
|
||||
for child in node.childNodes:
|
||||
c = xml.dom.minidom._clone_node(child, deep, newOwnerDocument)
|
||||
clone.appendChild(c)
|
||||
|
||||
elif node.nodeType == xml.dom.minidom.Node.TEXT_NODE:
|
||||
clone = newOwnerDocument.createTextNode(node.data)
|
||||
elif node.nodeType == xml.dom.minidom.Node.CDATA_SECTION_NODE:
|
||||
clone = newOwnerDocument.createCDATASection(node.data)
|
||||
elif node.nodeType == xml.dom.minidom.Node.PROCESSING_INSTRUCTION_NODE:
|
||||
clone = newOwnerDocument.createProcessingInstruction(node.target,
|
||||
node.data)
|
||||
elif node.nodeType == xml.dom.minidom.Node.COMMENT_NODE:
|
||||
clone = newOwnerDocument.createComment(node.data)
|
||||
elif node.nodeType == xml.dom.minidom.Node.ATTRIBUTE_NODE:
|
||||
clone = newOwnerDocument.createAttributeNS(node.namespaceURI,
|
||||
node.nodeName)
|
||||
clone.specified = True
|
||||
clone.value = node.value
|
||||
elif node.nodeType == xml.dom.minidom.Node.DOCUMENT_TYPE_NODE:
|
||||
assert node.ownerDocument is not newOwnerDocument
|
||||
operation = xml.dom.UserDataHandler.NODE_IMPORTED
|
||||
clone = newOwnerDocument.implementation.createDocumentType(
|
||||
node.name, node.publicId, node.systemId)
|
||||
clone.ownerDocument = newOwnerDocument
|
||||
if deep:
|
||||
clone.entities._seq = []
|
||||
clone.notations._seq = []
|
||||
for n in node.notations._seq:
|
||||
notation = xml.dom.minidom.Notation(n.nodeName, n.publicId, n.systemId)
|
||||
notation.ownerDocument = newOwnerDocument
|
||||
clone.notations._seq.append(notation)
|
||||
if hasattr(n, '_call_user_data_handler'):
|
||||
n._call_user_data_handler(operation, n, notation)
|
||||
for e in node.entities._seq:
|
||||
entity = xml.dom.minidom.Entity(e.nodeName, e.publicId, e.systemId,
|
||||
e.notationName)
|
||||
entity.actualEncoding = e.actualEncoding
|
||||
entity.encoding = e.encoding
|
||||
entity.version = e.version
|
||||
entity.ownerDocument = newOwnerDocument
|
||||
clone.entities._seq.append(entity)
|
||||
if hasattr(e, '_call_user_data_handler'):
|
||||
e._call_user_data_handler(operation, n, entity)
|
||||
else:
|
||||
# Note the cloning of Document and DocumentType nodes is
|
||||
# implemenetation specific. minidom handles those cases
|
||||
# directly in the cloneNode() methods.
|
||||
raise xml.dom.NotSupportedErr("Cannot clone node %s" % repr(node))
|
||||
|
||||
# Check for _call_user_data_handler() since this could conceivably
|
||||
# used with other DOM implementations (one of the FourThought
|
||||
# DOMs, perhaps?).
|
||||
if hasattr(node, '_call_user_data_handler'):
|
||||
node._call_user_data_handler(operation, node, clone)
|
||||
return clone
|
||||
|
||||
xml.dom.minidom._clone_node = _clone_node
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,88 +0,0 @@
|
||||
"""Translate strings to and from SOAP 1.2 XML name encoding
|
||||
|
||||
Implements rules for mapping application defined name to XML names
|
||||
specified by the w3 SOAP working group for SOAP version 1.2 in
|
||||
Appendix A of "SOAP Version 1.2 Part 2: Adjuncts", W3C Working Draft
|
||||
17, December 2001, <http://www.w3.org/TR/soap12-part2/#namemap>
|
||||
|
||||
Also see <http://www.w3.org/2000/xp/Group/xmlp-issues>.
|
||||
|
||||
Author: Gregory R. Warnes <gregory_r_warnes@groton.pfizer.com>
|
||||
Date:: 2002-04-25
|
||||
Version 0.9.0
|
||||
|
||||
"""
|
||||
|
||||
ident = "$Id$"
|
||||
|
||||
from re import *
|
||||
|
||||
|
||||
def _NCNameChar(x):
|
||||
return x.isalpha() or x.isdigit() or x=="." or x=='-' or x=="_"
|
||||
|
||||
|
||||
def _NCNameStartChar(x):
|
||||
return x.isalpha() or x=="_"
|
||||
|
||||
|
||||
def _toUnicodeHex(x):
|
||||
hexval = hex(ord(x[0]))[2:]
|
||||
hexlen = len(hexval)
|
||||
# Make hexval have either 4 or 8 digits by prepending 0's
|
||||
if (hexlen==1): hexval = "000" + hexval
|
||||
elif (hexlen==2): hexval = "00" + hexval
|
||||
elif (hexlen==3): hexval = "0" + hexval
|
||||
elif (hexlen==4): hexval = "" + hexval
|
||||
elif (hexlen==5): hexval = "000" + hexval
|
||||
elif (hexlen==6): hexval = "00" + hexval
|
||||
elif (hexlen==7): hexval = "0" + hexval
|
||||
elif (hexlen==8): hexval = "" + hexval
|
||||
else: raise Exception, "Illegal Value returned from hex(ord(x))"
|
||||
|
||||
return "_x"+ hexval + "_"
|
||||
|
||||
|
||||
def _fromUnicodeHex(x):
|
||||
return eval( r'u"\u'+x[2:-1]+'"' )
|
||||
|
||||
|
||||
def toXMLname(string):
|
||||
"""Convert string to a XML name."""
|
||||
if string.find(':') != -1 :
|
||||
(prefix, localname) = string.split(':',1)
|
||||
else:
|
||||
prefix = None
|
||||
localname = string
|
||||
|
||||
T = unicode(localname)
|
||||
|
||||
N = len(localname)
|
||||
X = [];
|
||||
for i in range(N) :
|
||||
if i< N-1 and T[i]==u'_' and T[i+1]==u'x':
|
||||
X.append(u'_x005F_')
|
||||
elif i==0 and N >= 3 and \
|
||||
( T[0]==u'x' or T[0]==u'X' ) and \
|
||||
( T[1]==u'm' or T[1]==u'M' ) and \
|
||||
( T[2]==u'l' or T[2]==u'L' ):
|
||||
X.append(u'_xFFFF_' + T[0])
|
||||
elif (not _NCNameChar(T[i])) or (i==0 and not _NCNameStartChar(T[i])):
|
||||
X.append(_toUnicodeHex(T[i]))
|
||||
else:
|
||||
X.append(T[i])
|
||||
|
||||
return u''.join(X)
|
||||
|
||||
|
||||
def fromXMLname(string):
|
||||
"""Convert XML name to unicode string."""
|
||||
|
||||
retval = sub(r'_xFFFF_','', string )
|
||||
|
||||
def fun( matchobj ):
|
||||
return _fromUnicodeHex( matchobj.group(0) )
|
||||
|
||||
retval = sub(r'_x[0-9A-Za-z]+_', fun, retval )
|
||||
|
||||
return retval
|
@ -1,35 +0,0 @@
|
||||
"""WSDL parsing services package for Web Services for Python."""
|
||||
|
||||
ident = "$Id$"
|
||||
|
||||
import WSDLTools
|
||||
import XMLname
|
||||
from logging import getLogger as _getLogger
|
||||
import logging.config as _config
|
||||
|
||||
LOGGING = 'logging.txt'
|
||||
DEBUG = True
|
||||
|
||||
#
|
||||
# If LOGGING configuration file is not found, turn off logging
|
||||
# and use _noLogger class because logging module's performance
|
||||
# is terrible.
|
||||
#
|
||||
|
||||
try:
|
||||
_config.fileConfig(LOGGING)
|
||||
except:
|
||||
DEBUG = False
|
||||
|
||||
|
||||
class Base:
|
||||
def __init__(self, module=__name__):
|
||||
self.logger = _noLogger()
|
||||
if DEBUG is True:
|
||||
self.logger = _getLogger('%s-%s(%x)' %(module, self.__class__, id(self)))
|
||||
|
||||
class _noLogger:
|
||||
def __init__(self, *args): pass
|
||||
def warning(self, *args): pass
|
||||
def debug(self, *args): pass
|
||||
def error(self, *args): pass
|
364
others/amazon.py
364
others/amazon.py
@ -1,364 +0,0 @@
|
||||
"""Python wrapper
|
||||
|
||||
|
||||
for Amazon web APIs
|
||||
|
||||
This module allows you to access Amazon's web APIs,
|
||||
to do things like search Amazon and get the results programmatically.
|
||||
Described here:
|
||||
http://www.amazon.com/webservices
|
||||
|
||||
You need a Amazon-provided license key to use these services.
|
||||
Follow the link above to get one. These functions will look in
|
||||
several places (in this order) for the license key:
|
||||
- the "license_key" argument of each function
|
||||
- the module-level LICENSE_KEY variable (call setLicense once to set it)
|
||||
- an environment variable called AMAZON_LICENSE_KEY
|
||||
- a file called ".amazonkey" in the current directory
|
||||
- a file called "amazonkey.txt" in the current directory
|
||||
- a file called ".amazonkey" in your home directory
|
||||
- a file called "amazonkey.txt" in your home directory
|
||||
- a file called ".amazonkey" in the same directory as amazon.py
|
||||
- a file called "amazonkey.txt" in the same directory as amazon.py
|
||||
|
||||
Sample usage:
|
||||
>>> import amazon
|
||||
>>> amazon.setLicense('...') # must get your own key!
|
||||
>>> pythonBooks = amazon.searchByKeyword('Python')
|
||||
>>> pythonBooks[0].ProductName
|
||||
u'Learning Python (Help for Programmers)'
|
||||
>>> pythonBooks[0].URL
|
||||
...
|
||||
>>> pythonBooks[0].OurPrice
|
||||
...
|
||||
|
||||
Other available functions:
|
||||
- browseBestSellers
|
||||
- searchByASIN
|
||||
- searchByUPC
|
||||
- searchByAuthor
|
||||
- searchByArtist
|
||||
- searchByActor
|
||||
- searchByDirector
|
||||
- searchByManufacturer
|
||||
- searchByListMania
|
||||
- searchSimilar
|
||||
- searchByWishlist
|
||||
|
||||
Other usage notes:
|
||||
- Most functions can take product_line as well, see source for possible values
|
||||
- All functions can take type="lite" to get less detail in results
|
||||
- All functions can take page=N to get second, third, fourth page of results
|
||||
- All functions can take license_key="XYZ", instead of setting it globally
|
||||
- All functions can take http_proxy="http://x/y/z" which overrides your system setting
|
||||
"""
|
||||
|
||||
__author__ = "Mark Pilgrim (f8dy@diveintomark.org)"
|
||||
__version__ = "0.64.1"
|
||||
__cvsversion__ = "$Revision$"[11:-2]
|
||||
__date__ = "$Date$"[7:-2]
|
||||
__copyright__ = "Copyright (c) 2002 Mark Pilgrim"
|
||||
__license__ = "Python"
|
||||
# Powersearch and return object type fix by Joseph Reagle <geek@goatee.net>
|
||||
|
||||
# Locale support by Michael Josephson <mike@josephson.org>
|
||||
|
||||
# Modification to _contentsOf to strip trailing whitespace when loading Amazon key
|
||||
# from a file submitted by Patrick Phalen.
|
||||
|
||||
# Support for specifying locale and associates ID as search parameters and
|
||||
# internationalisation fix for the SalesRank integer conversion by
|
||||
# Christian Theune <ct@gocept.com>, gocept gmbh & co. kg
|
||||
|
||||
# Support for BlendedSearch contributed by Alex Choo
|
||||
|
||||
from xml.dom import minidom
|
||||
import os, sys, getopt, cgi, urllib, string
|
||||
try:
|
||||
import timeoutsocket # http://www.timo-tasi.org/python/timeoutsocket.py
|
||||
timeoutsocket.setDefaultSocketTimeout(10)
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
LICENSE_KEY = None
|
||||
ASSOCIATE = "webservices-20"
|
||||
HTTP_PROXY = None
|
||||
LOCALE = "us"
|
||||
|
||||
# don't touch the rest of these constants
|
||||
class AmazonError(Exception): pass
|
||||
class NoLicenseKey(Exception): pass
|
||||
_amazonfile1 = ".amazonkey"
|
||||
_amazonfile2 = "amazonkey.txt"
|
||||
_licenseLocations = (
|
||||
(lambda key: key, 'passed to the function in license_key variable'),
|
||||
(lambda key: LICENSE_KEY, 'module-level LICENSE_KEY variable (call setLicense to set it)'),
|
||||
(lambda key: os.environ.get('AMAZON_LICENSE_KEY', None), 'an environment variable called AMAZON_LICENSE_KEY'),
|
||||
(lambda key: _contentsOf(os.getcwd(), _amazonfile1), '%s in the current directory' % _amazonfile1),
|
||||
(lambda key: _contentsOf(os.getcwd(), _amazonfile2), '%s in the current directory' % _amazonfile2),
|
||||
(lambda key: _contentsOf(os.environ.get('HOME', ''), _amazonfile1), '%s in your home directory' % _amazonfile1),
|
||||
(lambda key: _contentsOf(os.environ.get('HOME', ''), _amazonfile2), '%s in your home directory' % _amazonfile2),
|
||||
(lambda key: _contentsOf(_getScriptDir(), _amazonfile1), '%s in the amazon.py directory' % _amazonfile1),
|
||||
(lambda key: _contentsOf(_getScriptDir(), _amazonfile2), '%s in the amazon.py directory' % _amazonfile2)
|
||||
)
|
||||
_supportedLocales = {
|
||||
"us" : (None, "xml.amazon.com"),
|
||||
"uk" : ("uk", "xml-eu.amazon.com"),
|
||||
"de" : ("de", "xml-eu.amazon.com"),
|
||||
"jp" : ("jp", "xml.amazon.co.jp")
|
||||
}
|
||||
|
||||
## administrative functions
|
||||
def version():
|
||||
print """PyAmazon %(__version__)s
|
||||
%(__copyright__)s
|
||||
released %(__date__)s
|
||||
""" % globals()
|
||||
|
||||
def setAssociate(associate):
|
||||
global ASSOCIATE
|
||||
ASSOCIATE=associate
|
||||
|
||||
def getAssociate(override=None):
|
||||
return override or ASSOCIATE
|
||||
|
||||
## utility functions
|
||||
|
||||
def _checkLocaleSupported(locale):
|
||||
if not _supportedLocales.has_key(locale):
|
||||
raise AmazonError, ("Unsupported locale. Locale must be one of: %s" %
|
||||
string.join(_supportedLocales, ", "))
|
||||
|
||||
def setLocale(locale):
|
||||
"""set locale"""
|
||||
global LOCALE
|
||||
_checkLocaleSupported(locale)
|
||||
LOCALE = locale
|
||||
|
||||
def getLocale(locale=None):
|
||||
"""get locale"""
|
||||
return locale or LOCALE
|
||||
|
||||
def setLicense(license_key):
|
||||
"""set license key"""
|
||||
global LICENSE_KEY
|
||||
LICENSE_KEY = license_key
|
||||
|
||||
def getLicense(license_key = None):
|
||||
"""get license key
|
||||
|
||||
license key can come from any number of locations;
|
||||
see module docs for search order"""
|
||||
for get, location in _licenseLocations:
|
||||
rc = get(license_key)
|
||||
if rc: return rc
|
||||
raise NoLicenseKey, 'get a license key at http://www.amazon.com/webservices'
|
||||
|
||||
def setProxy(http_proxy):
|
||||
"""set HTTP proxy"""
|
||||
global HTTP_PROXY
|
||||
HTTP_PROXY = http_proxy
|
||||
|
||||
def getProxy(http_proxy = None):
|
||||
"""get HTTP proxy"""
|
||||
return http_proxy or HTTP_PROXY
|
||||
|
||||
def getProxies(http_proxy = None):
|
||||
http_proxy = getProxy(http_proxy)
|
||||
if http_proxy:
|
||||
proxies = {"http": http_proxy}
|
||||
else:
|
||||
proxies = None
|
||||
return proxies
|
||||
|
||||
def _contentsOf(dirname, filename):
|
||||
filename = os.path.join(dirname, filename)
|
||||
if not os.path.exists(filename): return None
|
||||
fsock = open(filename)
|
||||
contents = fsock.read().strip()
|
||||
fsock.close()
|
||||
return contents
|
||||
|
||||
def _getScriptDir():
|
||||
if __name__ == '__main__':
|
||||
return os.path.abspath(os.path.dirname(sys.argv[0]))
|
||||
else:
|
||||
return os.path.abspath(os.path.dirname(sys.modules[__name__].__file__))
|
||||
|
||||
class Bag: pass
|
||||
|
||||
def unmarshal(element):
|
||||
rc = Bag()
|
||||
if isinstance(element, minidom.Element) and (element.tagName == 'Details'):
|
||||
rc.URL = element.attributes["url"].value
|
||||
childElements = [e for e in element.childNodes if isinstance(e, minidom.Element)]
|
||||
if childElements:
|
||||
for child in childElements:
|
||||
key = child.tagName
|
||||
if hasattr(rc, key):
|
||||
if type(getattr(rc, key)) <> type([]):
|
||||
setattr(rc, key, [getattr(rc, key)])
|
||||
setattr(rc, key, getattr(rc, key) + [unmarshal(child)])
|
||||
elif isinstance(child, minidom.Element) and (child.tagName == 'Details'):
|
||||
# make the first Details element a key
|
||||
setattr(rc,key,[unmarshal(child)])
|
||||
#dbg: because otherwise 'hasattr' only tests
|
||||
#dbg: on the second occurence: if there's a
|
||||
#dbg: single return to a query, it's not a
|
||||
#dbg: list. This module should always
|
||||
#dbg: return a list of Details objects.
|
||||
else:
|
||||
setattr(rc, key, unmarshal(child))
|
||||
else:
|
||||
rc = "".join([e.data for e in element.childNodes if isinstance(e, minidom.Text)])
|
||||
if element.tagName == 'SalesRank':
|
||||
rc = rc.replace('.', '')
|
||||
rc = rc.replace(',', '')
|
||||
rc = int(rc)
|
||||
return rc
|
||||
|
||||
def buildURL(search_type, keyword, product_line, type, page, license_key, locale, associate):
|
||||
_checkLocaleSupported(locale)
|
||||
url = "http://" + _supportedLocales[locale][1] + "/onca/xml3?f=xml"
|
||||
url += "&t=%s" % associate
|
||||
url += "&dev-t=%s" % license_key.strip()
|
||||
url += "&type=%s" % type
|
||||
if _supportedLocales[locale][0]:
|
||||
url += "&locale=%s" % _supportedLocales[locale][0]
|
||||
if page:
|
||||
url += "&page=%s" % page
|
||||
if product_line:
|
||||
url += "&mode=%s" % product_line
|
||||
url += "&%s=%s" % (search_type, urllib.quote(keyword))
|
||||
return url
|
||||
|
||||
|
||||
## main functions
|
||||
|
||||
|
||||
def search(search_type, keyword, product_line, type = "heavy", page = None,
|
||||
license_key=None, http_proxy = None, locale = None, associate = None):
|
||||
"""search Amazon
|
||||
|
||||
You need a license key to call this function; see
|
||||
http://www.amazon.com/webservices
|
||||
to get one. Then you can either pass it to
|
||||
this function every time, or set it globally; see the module docs for details.
|
||||
|
||||
Parameters:
|
||||
keyword - keyword to search
|
||||
search_type - in (KeywordSearch, BrowseNodeSearch, AsinSearch, UpcSearch, AuthorSearch, ArtistSearch, ActorSearch, DirectorSearch, ManufacturerSearch, ListManiaSearch, SimilaritySearch)
|
||||
product_line - type of product to search for. restrictions based on search_type
|
||||
UpcSearch - in (music, classical)
|
||||
AuthorSearch - must be "books"
|
||||
ArtistSearch - in (music, classical)
|
||||
ActorSearch - in (dvd, vhs, video)
|
||||
DirectorSearch - in (dvd, vhs, video)
|
||||
ManufacturerSearch - in (electronics, kitchen, videogames, software, photo, pc-hardware)
|
||||
http_proxy (optional) - address of HTTP proxy to use for sending and receiving SOAP messages
|
||||
|
||||
Returns: list of Bags, each Bag may contain the following attributes:
|
||||
Asin - Amazon ID ("ASIN" number) of this item
|
||||
Authors - list of authors
|
||||
Availability - "available", etc.
|
||||
BrowseList - list of related categories
|
||||
Catalog - catalog type ("Book", etc)
|
||||
CollectiblePrice - ?, format "$34.95"
|
||||
ImageUrlLarge - URL of large image of this item
|
||||
ImageUrlMedium - URL of medium image of this item
|
||||
ImageUrlSmall - URL of small image of this item
|
||||
Isbn - ISBN number
|
||||
ListPrice - list price, format "$34.95"
|
||||
Lists - list of ListMania lists that include this item
|
||||
Manufacturer - manufacturer
|
||||
Media - media ("Paperback", "Audio CD", etc)
|
||||
NumMedia - number of different media types in which this item is available
|
||||
OurPrice - Amazon price, format "$24.47"
|
||||
ProductName - name of this item
|
||||
ReleaseDate - release date, format "09 April, 1999"
|
||||
Reviews - reviews (AvgCustomerRating, plus list of CustomerReview with Rating, Summary, Content)
|
||||
SalesRank - sales rank (integer)
|
||||
SimilarProducts - list of Product, which is ASIN number
|
||||
ThirdPartyNewPrice - ?, format "$34.95"
|
||||
URL - URL of this item
|
||||
"""
|
||||
license_key = getLicense(license_key)
|
||||
locale = getLocale(locale)
|
||||
associate = getAssociate(associate)
|
||||
url = buildURL(search_type, keyword, product_line, type, page,
|
||||
license_key, locale, associate)
|
||||
proxies = getProxies(http_proxy)
|
||||
u = urllib.FancyURLopener(proxies)
|
||||
usock = u.open(url)
|
||||
xmldoc = minidom.parse(usock)
|
||||
|
||||
# from xml.dom.ext import PrettyPrint
|
||||
# PrettyPrint(xmldoc)
|
||||
|
||||
usock.close()
|
||||
if search_type == "BlendedSearch":
|
||||
data = unmarshal(xmldoc).BlendedSearch
|
||||
else:
|
||||
data = unmarshal(xmldoc).ProductInfo
|
||||
|
||||
if hasattr(data, 'ErrorMsg'):
|
||||
raise AmazonError, data.ErrorMsg
|
||||
else:
|
||||
if search_type == "BlendedSearch":
|
||||
# a list of ProductLine containing a list of ProductInfo
|
||||
# containing a list of Details.
|
||||
return data
|
||||
else:
|
||||
return data.Details
|
||||
|
||||
def searchByKeyword(keyword, product_line="books", type="heavy", page=1, license_key=None, http_proxy=None, locale=None, associate=None):
|
||||
return search("KeywordSearch", keyword, product_line, type, page, license_key, http_proxy, locale, associate)
|
||||
|
||||
def browseBestSellers(browse_node, product_line="books", type="heavy", page=1, license_key=None, http_proxy=None, locale=None, associate=None):
|
||||
return search("BrowseNodeSearch", browse_node, product_line, type, page, license_key, http_proxy, locale, associate)
|
||||
|
||||
def searchByASIN(ASIN, type="heavy", license_key=None, http_proxy=None, locale=None, associate=None):
|
||||
return search("AsinSearch", ASIN, None, type, None, license_key, http_proxy, locale, associate)
|
||||
|
||||
def searchByUPC(UPC, type="heavy", license_key=None, http_proxy=None, locale=None, associate=None):
|
||||
return search("UpcSearch", UPC, None, type, None, license_key, http_proxy, locale, associate)
|
||||
|
||||
def searchByAuthor(author, type="heavy", page=1, license_key=None, http_proxy=None, locale=None, associate=None):
|
||||
return search("AuthorSearch", author, "books", type, page, license_key, http_proxy, locale, associate)
|
||||
|
||||
def searchByArtist(artist, product_line="music", type="heavy", page=1, license_key=None, http_proxy=None, locale=None, associate=None):
|
||||
if product_line not in ("music", "classical"):
|
||||
raise AmazonError, "product_line must be in ('music', 'classical')"
|
||||
return search("ArtistSearch", artist, product_line, type, page, license_key, http_proxy, locale, associate)
|
||||
|
||||
def searchByActor(actor, product_line="dvd", type="heavy", page=1, license_key=None, http_proxy=None, locale=None, associate=None):
|
||||
if product_line not in ("dvd", "vhs", "video"):
|
||||
raise AmazonError, "product_line must be in ('dvd', 'vhs', 'video')"
|
||||
return search("ActorSearch", actor, product_line, type, page, license_key, http_proxy, locale, associate)
|
||||
|
||||
def searchByDirector(director, product_line="dvd", type="heavy", page=1, license_key=None, http_proxy=None, locale=None, associate=None):
|
||||
if product_line not in ("dvd", "vhs", "video"):
|
||||
raise AmazonError, "product_line must be in ('dvd', 'vhs', 'video')"
|
||||
return search("DirectorSearch", director, product_line, type, page, license_key, http_proxy, locale, associate)
|
||||
|
||||
def searchByManufacturer(manufacturer, product_line="pc-hardware", type="heavy", page=1, license_key=None, http_proxy=None, locale=None, associate=None):
|
||||
if product_line not in ("electronics", "kitchen", "videogames", "software", "photo", "pc-hardware"):
|
||||
raise AmazonError, "product_line must be in ('electronics', 'kitchen', 'videogames', 'software', 'photo', 'pc-hardware')"
|
||||
return search("ManufacturerSearch", manufacturer, product_line, type, page, license_key, http_proxy, locale, associate)
|
||||
|
||||
def searchByListMania(listManiaID, type="heavy", page=1, license_key=None, http_proxy=None, locale=None, associate=None):
|
||||
return search("ListManiaSearch", listManiaID, None, type, page, license_key, http_proxy, locale, associate)
|
||||
|
||||
def searchSimilar(ASIN, type="heavy", page=1, license_key=None, http_proxy=None, locale=None, associate=None):
|
||||
return search("SimilaritySearch", ASIN, None, type, page, license_key, http_proxy, locale, associate)
|
||||
|
||||
def searchByWishlist(wishlistID, type="heavy", page=1, license_key=None, http_proxy=None, locale=None, associate=None):
|
||||
return search("WishlistSearch", wishlistID, None, type, page, license_key, http_proxy, locale, associate)
|
||||
|
||||
def searchByPower(keyword, product_line="books", type="heavy", page=1, license_key=None, http_proxy=None, locale=None, associate=None):
|
||||
return search("PowerSearch", keyword, product_line, type, page, license_key, http_proxy, locale, associate)
|
||||
# >>> RecentKing = amazon.searchByPower('author:Stephen King and pubdate:2003')
|
||||
# >>> SnowCrash = amazon.searchByPower('title:Snow Crash')
|
||||
|
||||
def searchByBlended(keyword, type="heavy", page=1, license_key=None, http_proxy=None, locale=None, associate=None):
|
||||
return search("BlendedSearch", keyword, None, type, page, license_key, http_proxy, locale, associate)
|
@ -1,294 +0,0 @@
|
||||
# -*- Mode: Python; tab-width: 4 -*-
|
||||
# Id: asynchat.py,v 2.26 2000/09/07 22:29:26 rushing Exp
|
||||
# Author: Sam Rushing <rushing@nightmare.com>
|
||||
|
||||
# ======================================================================
|
||||
# Copyright 1996 by Sam Rushing
|
||||
#
|
||||
# All Rights Reserved
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software and
|
||||
# its documentation for any purpose and without fee is hereby
|
||||
# granted, provided that the above copyright notice appear in all
|
||||
# copies and that both that copyright notice and this permission
|
||||
# notice appear in supporting documentation, and that the name of Sam
|
||||
# Rushing not be used in advertising or publicity pertaining to
|
||||
# distribution of the software without specific, written prior
|
||||
# permission.
|
||||
#
|
||||
# SAM RUSHING DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
|
||||
# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
|
||||
# NO EVENT SHALL SAM RUSHING BE LIABLE FOR ANY SPECIAL, INDIRECT OR
|
||||
# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
# ======================================================================
|
||||
|
||||
r"""A class supporting chat-style (command/response) protocols.
|
||||
|
||||
This class adds support for 'chat' style protocols - where one side
|
||||
sends a 'command', and the other sends a response (examples would be
|
||||
the common internet protocols - smtp, nntp, ftp, etc..).
|
||||
|
||||
The handle_read() method looks at the input stream for the current
|
||||
'terminator' (usually '\r\n' for single-line responses, '\r\n.\r\n'
|
||||
for multi-line output), calling self.found_terminator() on its
|
||||
receipt.
|
||||
|
||||
for example:
|
||||
Say you build an async nntp client using this class. At the start
|
||||
of the connection, you'll have self.terminator set to '\r\n', in
|
||||
order to process the single-line greeting. Just before issuing a
|
||||
'LIST' command you'll set it to '\r\n.\r\n'. The output of the LIST
|
||||
command will be accumulated (using your own 'collect_incoming_data'
|
||||
method) up to the terminator, and then control will be returned to
|
||||
you - by calling your self.found_terminator() method.
|
||||
"""
|
||||
|
||||
import socket
|
||||
import asyncore
|
||||
|
||||
class async_chat (asyncore.dispatcher):
|
||||
"""This is an abstract class. You must derive from this class, and add
|
||||
the two methods collect_incoming_data() and found_terminator()"""
|
||||
|
||||
# these are overridable defaults
|
||||
|
||||
ac_in_buffer_size = 4096
|
||||
ac_out_buffer_size = 4096
|
||||
|
||||
def __init__ (self, conn=None):
|
||||
self.ac_in_buffer = ''
|
||||
self.ac_out_buffer = ''
|
||||
self.producer_fifo = fifo()
|
||||
asyncore.dispatcher.__init__ (self, conn)
|
||||
|
||||
def set_terminator (self, term):
|
||||
"Set the input delimiter. Can be a fixed string of any length, an integer, or None"
|
||||
self.terminator = term
|
||||
|
||||
def get_terminator (self):
|
||||
return self.terminator
|
||||
|
||||
# grab some more data from the socket,
|
||||
# throw it to the collector method,
|
||||
# check for the terminator,
|
||||
# if found, transition to the next state.
|
||||
|
||||
def handle_read (self):
|
||||
|
||||
try:
|
||||
data = self.recv (self.ac_in_buffer_size)
|
||||
except socket.error, _:
|
||||
self.handle_error()
|
||||
return
|
||||
|
||||
self.ac_in_buffer = self.ac_in_buffer + data
|
||||
|
||||
# Continue to search for self.terminator in self.ac_in_buffer,
|
||||
# while calling self.collect_incoming_data. The while loop
|
||||
# is necessary because we might read several data+terminator
|
||||
# combos with a single recv(1024).
|
||||
|
||||
while self.ac_in_buffer:
|
||||
lb = len(self.ac_in_buffer)
|
||||
terminator = self.get_terminator()
|
||||
if terminator is None:
|
||||
# no terminator, collect it all
|
||||
self.collect_incoming_data (self.ac_in_buffer)
|
||||
self.ac_in_buffer = ''
|
||||
elif type(terminator) == type(0):
|
||||
# numeric terminator
|
||||
n = terminator
|
||||
if lb < n:
|
||||
self.collect_incoming_data (self.ac_in_buffer)
|
||||
self.ac_in_buffer = ''
|
||||
self.terminator = self.terminator - lb
|
||||
else:
|
||||
self.collect_incoming_data (self.ac_in_buffer[:n])
|
||||
self.ac_in_buffer = self.ac_in_buffer[n:]
|
||||
self.terminator = 0
|
||||
self.found_terminator()
|
||||
else:
|
||||
# 3 cases:
|
||||
# 1) end of buffer matches terminator exactly:
|
||||
# collect data, transition
|
||||
# 2) end of buffer matches some prefix:
|
||||
# collect data to the prefix
|
||||
# 3) end of buffer does not match any prefix:
|
||||
# collect data
|
||||
terminator_len = len(terminator)
|
||||
index = self.ac_in_buffer.find(terminator)
|
||||
if index != -1:
|
||||
# we found the terminator
|
||||
if index > 0:
|
||||
# don't bother reporting the empty string (source of subtle bugs)
|
||||
self.collect_incoming_data (self.ac_in_buffer[:index])
|
||||
self.ac_in_buffer = self.ac_in_buffer[index+terminator_len:]
|
||||
# This does the Right Thing if the terminator is changed here.
|
||||
self.found_terminator()
|
||||
else:
|
||||
# check for a prefix of the terminator
|
||||
index = find_prefix_at_end (self.ac_in_buffer, terminator)
|
||||
if index:
|
||||
if index != lb:
|
||||
# we found a prefix, collect up to the prefix
|
||||
self.collect_incoming_data (self.ac_in_buffer[:-index])
|
||||
self.ac_in_buffer = self.ac_in_buffer[-index:]
|
||||
break
|
||||
else:
|
||||
# no prefix, collect it all
|
||||
self.collect_incoming_data (self.ac_in_buffer)
|
||||
self.ac_in_buffer = ''
|
||||
|
||||
def handle_write (self):
|
||||
self.initiate_send ()
|
||||
|
||||
def handle_close (self):
|
||||
self.close()
|
||||
|
||||
def push (self, data):
|
||||
self.producer_fifo.push (simple_producer (data))
|
||||
self.initiate_send()
|
||||
|
||||
def push_with_producer (self, producer):
|
||||
self.producer_fifo.push (producer)
|
||||
self.initiate_send()
|
||||
|
||||
def readable (self):
|
||||
"predicate for inclusion in the readable for select()"
|
||||
return (len(self.ac_in_buffer) <= self.ac_in_buffer_size)
|
||||
|
||||
def writable (self):
|
||||
"predicate for inclusion in the writable for select()"
|
||||
# return len(self.ac_out_buffer) or len(self.producer_fifo) or (not self.connected)
|
||||
# this is about twice as fast, though not as clear.
|
||||
return not (
|
||||
(self.ac_out_buffer == '') and
|
||||
self.producer_fifo.is_empty() and
|
||||
self.connected
|
||||
)
|
||||
|
||||
def close_when_done (self):
|
||||
"automatically close this channel once the outgoing queue is empty"
|
||||
self.producer_fifo.push (None)
|
||||
|
||||
# refill the outgoing buffer by calling the more() method
|
||||
# of the first producer in the queue
|
||||
def refill_buffer (self):
|
||||
_string_type = type('')
|
||||
while 1:
|
||||
if len(self.producer_fifo):
|
||||
p = self.producer_fifo.first()
|
||||
# a 'None' in the producer fifo is a sentinel,
|
||||
# telling us to close the channel.
|
||||
if p is None:
|
||||
if not self.ac_out_buffer:
|
||||
self.producer_fifo.pop()
|
||||
self.close()
|
||||
return
|
||||
elif type(p) is _string_type:
|
||||
self.producer_fifo.pop()
|
||||
self.ac_out_buffer = self.ac_out_buffer + p
|
||||
return
|
||||
data = p.more()
|
||||
if data:
|
||||
self.ac_out_buffer = self.ac_out_buffer + data
|
||||
return
|
||||
else:
|
||||
self.producer_fifo.pop()
|
||||
else:
|
||||
return
|
||||
|
||||
def initiate_send (self):
|
||||
obs = self.ac_out_buffer_size
|
||||
# try to refill the buffer
|
||||
if (len (self.ac_out_buffer) < obs):
|
||||
self.refill_buffer()
|
||||
|
||||
if self.ac_out_buffer and self.connected:
|
||||
# try to send the buffer
|
||||
try:
|
||||
num_sent = self.send (self.ac_out_buffer[:obs])
|
||||
if num_sent:
|
||||
self.ac_out_buffer = self.ac_out_buffer[num_sent:]
|
||||
|
||||
except socket.error, _:
|
||||
self.handle_error()
|
||||
return
|
||||
|
||||
def discard_buffers (self):
|
||||
# Emergencies only!
|
||||
self.ac_in_buffer = ''
|
||||
self.ac_out_buffer = ''
|
||||
while self.producer_fifo:
|
||||
self.producer_fifo.pop()
|
||||
|
||||
|
||||
class simple_producer:
|
||||
|
||||
def __init__ (self, data, buffer_size=512):
|
||||
self.data = data
|
||||
self.buffer_size = buffer_size
|
||||
|
||||
def more (self):
|
||||
if len (self.data) > self.buffer_size:
|
||||
result = self.data[:self.buffer_size]
|
||||
self.data = self.data[self.buffer_size:]
|
||||
return result
|
||||
else:
|
||||
result = self.data
|
||||
self.data = ''
|
||||
return result
|
||||
|
||||
class fifo:
|
||||
def __init__ (self, list=None):
|
||||
if not list:
|
||||
self.list = []
|
||||
else:
|
||||
self.list = list
|
||||
|
||||
def __len__ (self):
|
||||
return len(self.list)
|
||||
|
||||
def is_empty (self):
|
||||
return self.list == []
|
||||
|
||||
def first (self):
|
||||
return self.list[0]
|
||||
|
||||
def push (self, data):
|
||||
self.list.append (data)
|
||||
|
||||
def pop (self):
|
||||
if self.list:
|
||||
result = self.list[0]
|
||||
del self.list[0]
|
||||
return (1, result)
|
||||
else:
|
||||
return (0, None)
|
||||
|
||||
# Given 'haystack', see if any prefix of 'needle' is at its end. This
|
||||
# assumes an exact match has already been checked. Return the number of
|
||||
# characters matched.
|
||||
# for example:
|
||||
# f_p_a_e ("qwerty\r", "\r\n") => 1
|
||||
# f_p_a_e ("qwerty\r\n", "\r\n") => 2
|
||||
# f_p_a_e ("qwertydkjf", "\r\n") => 0
|
||||
|
||||
# this could maybe be made faster with a computed regex?
|
||||
# [answer: no; circa Python-2.0, Jan 2001]
|
||||
# python: 18307/s
|
||||
# re: 12820/s
|
||||
# regex: 14035/s
|
||||
|
||||
def find_prefix_at_end (haystack, needle):
|
||||
nl = len(needle)
|
||||
result = 0
|
||||
for i in range (1,nl):
|
||||
if haystack[-(nl-i):] == needle[:(nl-i)]:
|
||||
result = nl-i
|
||||
break
|
||||
return result
|
||||
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78:
|
@ -1,494 +0,0 @@
|
||||
# -*- Mode: Python -*-
|
||||
# Id: asyncore.py,v 2.51 2000/09/07 22:29:26 rushing Exp
|
||||
# Author: Sam Rushing <rushing@nightmare.com>
|
||||
|
||||
# ======================================================================
|
||||
# Copyright 1996 by Sam Rushing
|
||||
#
|
||||
# All Rights Reserved
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software and
|
||||
# its documentation for any purpose and without fee is hereby
|
||||
# granted, provided that the above copyright notice appear in all
|
||||
# copies and that both that copyright notice and this permission
|
||||
# notice appear in supporting documentation, and that the name of Sam
|
||||
# Rushing not be used in advertising or publicity pertaining to
|
||||
# distribution of the software without specific, written prior
|
||||
# permission.
|
||||
#
|
||||
# SAM RUSHING DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
|
||||
# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
|
||||
# NO EVENT SHALL SAM RUSHING BE LIABLE FOR ANY SPECIAL, INDIRECT OR
|
||||
# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
# ======================================================================
|
||||
|
||||
"""Basic infrastructure for asynchronous socket service clients and servers.
|
||||
|
||||
There are only two ways to have a program on a single processor do "more
|
||||
than one thing at a time". Multi-threaded programming is the simplest and
|
||||
most popular way to do it, but there is another very different technique,
|
||||
that lets you have nearly all the advantages of multi-threading, without
|
||||
actually using multiple threads. it's really only practical if your program
|
||||
is largely I/O bound. If your program is CPU bound, then pre-emptive
|
||||
scheduled threads are probably what you really need. Network servers are
|
||||
rarely CPU-bound, however.
|
||||
|
||||
If your operating system supports the select() system call in its I/O
|
||||
library (and nearly all do), then you can use it to juggle multiple
|
||||
communication channels at once; doing other work while your I/O is taking
|
||||
place in the "background." Although this strategy can seem strange and
|
||||
complex, especially at first, it is in many ways easier to understand and
|
||||
control than multi-threaded programming. The module documented here solves
|
||||
many of the difficult problems for you, making the task of building
|
||||
sophisticated high-performance network servers and clients a snap.
|
||||
"""
|
||||
|
||||
import exceptions
|
||||
import select
|
||||
import socket
|
||||
import sys
|
||||
import time
|
||||
|
||||
import os
|
||||
from errno import EALREADY, EINPROGRESS, EWOULDBLOCK, ECONNRESET, \
|
||||
ENOTCONN, ESHUTDOWN, EINTR, EISCONN
|
||||
|
||||
try:
|
||||
socket_map
|
||||
except NameError:
|
||||
socket_map = {}
|
||||
|
||||
class ExitNow(exceptions.Exception):
|
||||
pass
|
||||
|
||||
def read(obj):
|
||||
try:
|
||||
obj.handle_read_event()
|
||||
except ExitNow:
|
||||
raise
|
||||
except:
|
||||
obj.handle_error()
|
||||
|
||||
def write(obj):
|
||||
try:
|
||||
obj.handle_write_event()
|
||||
except ExitNow:
|
||||
raise
|
||||
except:
|
||||
obj.handle_error()
|
||||
|
||||
def readwrite(obj, flags):
|
||||
try:
|
||||
if flags & select.POLLIN:
|
||||
obj.handle_read_event()
|
||||
if flags & select.POLLOUT:
|
||||
obj.handle_write_event()
|
||||
except ExitNow:
|
||||
raise
|
||||
except:
|
||||
obj.handle_error()
|
||||
|
||||
def poll(timeout=0.0, map=None):
|
||||
if map is None:
|
||||
map = socket_map
|
||||
if map:
|
||||
r = []; w = []; e = []
|
||||
for fd, obj in map.items():
|
||||
if obj.readable():
|
||||
r.append(fd)
|
||||
if obj.writable():
|
||||
w.append(fd)
|
||||
if [] == r == w == e:
|
||||
time.sleep(timeout)
|
||||
else:
|
||||
try:
|
||||
r, w, e = select.select(r, w, e, timeout)
|
||||
except select.error, err:
|
||||
if err[0] != EINTR:
|
||||
raise
|
||||
else:
|
||||
return
|
||||
|
||||
for fd in r:
|
||||
obj = map.get(fd)
|
||||
if obj is None:
|
||||
continue
|
||||
read(obj)
|
||||
|
||||
for fd in w:
|
||||
obj = map.get(fd)
|
||||
if obj is None:
|
||||
continue
|
||||
write(obj)
|
||||
|
||||
def poll2(timeout=0.0, map=None):
|
||||
import poll
|
||||
if map is None:
|
||||
map = socket_map
|
||||
if timeout is not None:
|
||||
# timeout is in milliseconds
|
||||
timeout = int(timeout*1000)
|
||||
if map:
|
||||
l = []
|
||||
for fd, obj in map.items():
|
||||
flags = 0
|
||||
if obj.readable():
|
||||
flags = poll.POLLIN
|
||||
if obj.writable():
|
||||
flags = flags | poll.POLLOUT
|
||||
if flags:
|
||||
l.append((fd, flags))
|
||||
r = poll.poll(l, timeout)
|
||||
for fd, flags in r:
|
||||
obj = map.get(fd)
|
||||
if obj is None:
|
||||
continue
|
||||
readwrite(obj, flags)
|
||||
|
||||
def poll3(timeout=0.0, map=None):
|
||||
# Use the poll() support added to the select module in Python 2.0
|
||||
if map is None:
|
||||
map = socket_map
|
||||
if timeout is not None:
|
||||
# timeout is in milliseconds
|
||||
timeout = int(timeout*1000)
|
||||
pollster = select.poll()
|
||||
if map:
|
||||
for fd, obj in map.items():
|
||||
flags = 0
|
||||
if obj.readable():
|
||||
flags = select.POLLIN
|
||||
if obj.writable():
|
||||
flags = flags | select.POLLOUT
|
||||
if flags:
|
||||
pollster.register(fd, flags)
|
||||
try:
|
||||
r = pollster.poll(timeout)
|
||||
except select.error, err:
|
||||
if err[0] != EINTR:
|
||||
raise
|
||||
r = []
|
||||
for fd, flags in r:
|
||||
obj = map.get(fd)
|
||||
if obj is None:
|
||||
continue
|
||||
readwrite(obj, flags)
|
||||
|
||||
def loop(timeout=30.0, use_poll=0, map=None):
|
||||
if map is None:
|
||||
map = socket_map
|
||||
|
||||
if use_poll:
|
||||
if hasattr(select, 'poll'):
|
||||
poll_fun = poll3
|
||||
else:
|
||||
poll_fun = poll2
|
||||
else:
|
||||
poll_fun = poll
|
||||
|
||||
while map:
|
||||
poll_fun(timeout, map)
|
||||
|
||||
class dispatcher:
|
||||
|
||||
debug = 0
|
||||
connected = 0
|
||||
accepting = 0
|
||||
closing = 0
|
||||
addr = None
|
||||
|
||||
def __init__(self, sock=None, map=None):
|
||||
if sock:
|
||||
self.set_socket(sock, map)
|
||||
# I think it should inherit this anyway
|
||||
self.socket.setblocking(0)
|
||||
self.connected = 1
|
||||
# XXX Does the constructor require that the socket passed
|
||||
# be connected?
|
||||
try:
|
||||
self.addr = sock.getpeername()
|
||||
except socket.error:
|
||||
# The addr isn't crucial
|
||||
pass
|
||||
else:
|
||||
self.socket = None
|
||||
|
||||
def __repr__(self):
|
||||
status = [self.__class__.__module__+"."+self.__class__.__name__]
|
||||
if self.accepting and self.addr:
|
||||
status.append('listening')
|
||||
elif self.connected:
|
||||
status.append('connected')
|
||||
if self.addr is not None:
|
||||
try:
|
||||
status.append('%s:%d' % self.addr)
|
||||
except TypeError:
|
||||
status.append(repr(self.addr))
|
||||
return '<%s at %#x>' % (' '.join(status), id(self))
|
||||
|
||||
def add_channel(self, map=None):
|
||||
#self.log_info('adding channel %s' % self)
|
||||
if map is None:
|
||||
map = socket_map
|
||||
map[self._fileno] = self
|
||||
|
||||
def del_channel(self, map=None):
|
||||
fd = self._fileno
|
||||
if map is None:
|
||||
map = socket_map
|
||||
if map.has_key(fd):
|
||||
#self.log_info('closing channel %d:%s' % (fd, self))
|
||||
del map[fd]
|
||||
|
||||
def create_socket(self, family, type):
|
||||
self.family_and_type = family, type
|
||||
self.socket = socket.socket(family, type)
|
||||
self.socket.setblocking(0)
|
||||
self._fileno = self.socket.fileno()
|
||||
self.add_channel()
|
||||
|
||||
def set_socket(self, sock, map=None):
|
||||
self.socket = sock
|
||||
## self.__dict__['socket'] = sock
|
||||
self._fileno = sock.fileno()
|
||||
self.add_channel(map)
|
||||
|
||||
def set_reuse_addr(self):
|
||||
# try to re-use a server port if possible
|
||||
try:
|
||||
self.socket.setsockopt(
|
||||
socket.SOL_SOCKET, socket.SO_REUSEADDR,
|
||||
self.socket.getsockopt(socket.SOL_SOCKET,
|
||||
socket.SO_REUSEADDR) | 1
|
||||
)
|
||||
except socket.error:
|
||||
pass
|
||||
|
||||
# ==================================================
|
||||
# predicates for select()
|
||||
# these are used as filters for the lists of sockets
|
||||
# to pass to select().
|
||||
# ==================================================
|
||||
|
||||
def readable(self):
|
||||
return True
|
||||
|
||||
if os.name == 'mac':
|
||||
# The macintosh will select a listening socket for
|
||||
# write if you let it. What might this mean?
|
||||
def writable(self):
|
||||
return not self.accepting
|
||||
else:
|
||||
def writable(self):
|
||||
return True
|
||||
|
||||
# ==================================================
|
||||
# socket object methods.
|
||||
# ==================================================
|
||||
|
||||
def listen(self, num):
|
||||
self.accepting = 1
|
||||
if os.name == 'nt' and num > 5:
|
||||
num = 1
|
||||
return self.socket.listen(num)
|
||||
|
||||
def bind(self, addr):
|
||||
self.addr = addr
|
||||
return self.socket.bind(addr)
|
||||
|
||||
def connect(self, address):
|
||||
self.connected = 0
|
||||
err = self.socket.connect_ex(address)
|
||||
# XXX Should interpret Winsock return values
|
||||
if err in (EINPROGRESS, EALREADY, EWOULDBLOCK):
|
||||
return
|
||||
if err in (0, EISCONN):
|
||||
self.addr = address
|
||||
self.connected = 1
|
||||
self.handle_connect()
|
||||
else:
|
||||
raise socket.error, err
|
||||
|
||||
def accept(self):
|
||||
# XXX can return either an address pair or None
|
||||
try:
|
||||
conn, addr = self.socket.accept()
|
||||
return conn, addr
|
||||
except socket.error, why:
|
||||
if why[0] == EWOULDBLOCK:
|
||||
pass
|
||||
else:
|
||||
raise socket.error, why
|
||||
|
||||
def send(self, data):
|
||||
try:
|
||||
result = self.socket.send(data)
|
||||
return result
|
||||
except socket.error, why:
|
||||
if why[0] == EWOULDBLOCK:
|
||||
return 0
|
||||
else:
|
||||
raise socket.error, why
|
||||
return 0
|
||||
|
||||
def recv(self, buffer_size):
|
||||
try:
|
||||
data = self.socket.recv(buffer_size)
|
||||
if not data:
|
||||
# a closed connection is indicated by signaling
|
||||
# a read condition, and having recv() return 0.
|
||||
self.handle_close()
|
||||
return ''
|
||||
else:
|
||||
return data
|
||||
except socket.error, why:
|
||||
# winsock sometimes throws ENOTCONN
|
||||
if why[0] in [ECONNRESET, ENOTCONN, ESHUTDOWN]:
|
||||
self.handle_close()
|
||||
return ''
|
||||
else:
|
||||
raise socket.error, why
|
||||
|
||||
def close(self):
|
||||
self.del_channel()
|
||||
self.socket.close()
|
||||
|
||||
# cheap inheritance, used to pass all other attribute
|
||||
# references to the underlying socket object.
|
||||
def __getattr__(self, attr):
|
||||
return getattr(self.socket, attr)
|
||||
|
||||
# log and log_info may be overridden to provide more sophisticated
|
||||
# logging and warning methods. In general, log is for 'hit' logging
|
||||
# and 'log_info' is for informational, warning and error logging.
|
||||
|
||||
def log(self, message):
|
||||
sys.stderr.write('log: %s\n' % str(message))
|
||||
|
||||
def log_info(self, message, type='info'):
|
||||
if __debug__ or type != 'info':
|
||||
print '%s: %s' % (type, message)
|
||||
|
||||
def handle_read_event(self):
|
||||
if self.accepting:
|
||||
# for an accepting socket, getting a read implies
|
||||
# that we are connected
|
||||
if not self.connected:
|
||||
self.connected = 1
|
||||
self.handle_accept()
|
||||
elif not self.connected:
|
||||
self.handle_connect()
|
||||
self.connected = 1
|
||||
self.handle_read()
|
||||
else:
|
||||
self.handle_read()
|
||||
|
||||
def handle_write_event(self):
|
||||
# getting a write implies that we are connected
|
||||
if not self.connected:
|
||||
self.handle_connect()
|
||||
self.connected = 1
|
||||
self.handle_write()
|
||||
|
||||
def handle_expt_event(self):
|
||||
self.handle_expt()
|
||||
|
||||
def handle_error(self):
|
||||
nil, t, v, tbinfo = compact_traceback()
|
||||
|
||||
# sometimes a user repr method will crash.
|
||||
try:
|
||||
self_repr = repr(self)
|
||||
except:
|
||||
self_repr = '<__repr__(self) failed for object at %0x>' % id(self)
|
||||
|
||||
self.log_info(
|
||||
'uncaptured python exception, closing channel %s (%s:%s %s)' % (
|
||||
self_repr,
|
||||
t,
|
||||
v,
|
||||
tbinfo
|
||||
),
|
||||
'error'
|
||||
)
|
||||
self.close()
|
||||
|
||||
def handle_expt(self):
|
||||
self.log_info('unhandled exception', 'warning')
|
||||
|
||||
def handle_read(self):
|
||||
self.log_info('unhandled read event', 'warning')
|
||||
|
||||
def handle_write(self):
|
||||
self.log_info('unhandled write event', 'warning')
|
||||
|
||||
def handle_connect(self):
|
||||
self.log_info('unhandled connect event', 'warning')
|
||||
|
||||
def handle_accept(self):
|
||||
self.log_info('unhandled accept event', 'warning')
|
||||
|
||||
def handle_close(self):
|
||||
self.log_info('unhandled close event', 'warning')
|
||||
self.close()
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# adds simple buffered output capability, useful for simple clients.
|
||||
# [for more sophisticated usage use asynchat.async_chat]
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class dispatcher_with_send(dispatcher):
|
||||
|
||||
def __init__(self, sock=None):
|
||||
dispatcher.__init__(self, sock)
|
||||
self.out_buffer = ''
|
||||
|
||||
def initiate_send(self):
|
||||
num_sent = 0
|
||||
num_sent = dispatcher.send(self, self.out_buffer[:512])
|
||||
self.out_buffer = self.out_buffer[num_sent:]
|
||||
|
||||
def handle_write(self):
|
||||
self.initiate_send()
|
||||
|
||||
def writable(self):
|
||||
return (not self.connected) or len(self.out_buffer)
|
||||
|
||||
def send(self, data):
|
||||
if self.debug:
|
||||
self.log_info('sending %s' % repr(data))
|
||||
self.out_buffer = self.out_buffer + data
|
||||
self.initiate_send()
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# used for debugging.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def compact_traceback():
|
||||
t, v, tb = sys.exc_info()
|
||||
tbinfo = []
|
||||
assert tb # Must have a traceback
|
||||
while tb:
|
||||
tbinfo.append((
|
||||
tb.tb_frame.f_code.co_filename,
|
||||
tb.tb_frame.f_code.co_name,
|
||||
str(tb.tb_lineno)
|
||||
))
|
||||
tb = tb.tb_next
|
||||
|
||||
# just to be safe
|
||||
del tb
|
||||
|
||||
file, function, line = tbinfo[-1]
|
||||
info = ' '.join(['[%s|%s|%s]' % x for x in tbinfo])
|
||||
return (file, function, line), t, v, info
|
||||
|
||||
def close_all(map=None):
|
||||
if map is None:
|
||||
map = socket_map
|
||||
for x in map.values():
|
||||
x.socket.close()
|
||||
map.clear()
|
@ -1,179 +0,0 @@
|
||||
# babelizer.py - API for simple access to babelfish.altavista.com.
|
||||
# Requires python 2.0 or better.
|
||||
#
|
||||
# See it in use at http://babel.MrFeinberg.com/
|
||||
|
||||
"""API for simple access to babelfish.altavista.com.
|
||||
|
||||
Summary:
|
||||
|
||||
import babelizer
|
||||
|
||||
print ' '.join(babelizer.available_languages)
|
||||
|
||||
print babelizer.translate( 'How much is that doggie in the window?',
|
||||
'English', 'French' )
|
||||
|
||||
def babel_callback(phrase):
|
||||
print phrase
|
||||
sys.stdout.flush()
|
||||
|
||||
babelizer.babelize( 'I love a reigning knight.',
|
||||
'English', 'German',
|
||||
callback = babel_callback )
|
||||
|
||||
available_languages
|
||||
A list of languages available for use with babelfish.
|
||||
|
||||
translate( phrase, from_lang, to_lang )
|
||||
Uses babelfish to translate phrase from from_lang to to_lang.
|
||||
|
||||
babelize(phrase, from_lang, through_lang, limit = 12, callback = None)
|
||||
Uses babelfish to translate back and forth between from_lang and
|
||||
through_lang until either no more changes occur in translation or
|
||||
limit iterations have been reached, whichever comes first. Takes
|
||||
an optional callback function which should receive a single
|
||||
parameter, being the next translation. Without the callback
|
||||
returns a list of successive translations.
|
||||
|
||||
It's only guaranteed to work if 'english' is one of the two languages
|
||||
given to either of the translation methods.
|
||||
|
||||
Both translation methods throw exceptions which are all subclasses of
|
||||
BabelizerError. They include
|
||||
|
||||
LanguageNotAvailableError
|
||||
Thrown on an attempt to use an unknown language.
|
||||
|
||||
BabelfishChangedError
|
||||
Thrown when babelfish.altavista.com changes some detail of their
|
||||
layout, and babelizer can no longer parse the results or submit
|
||||
the correct form (a not infrequent occurance).
|
||||
|
||||
BabelizerIOError
|
||||
Thrown for various networking and IO errors.
|
||||
|
||||
Version: $Id$
|
||||
Author: Jonathan Feinberg <jdf@pobox.com>
|
||||
"""
|
||||
import re, string, urllib
|
||||
|
||||
"""
|
||||
Various patterns I have encountered in looking for the babelfish result.
|
||||
We try each of them in turn, based on the relative number of times I've
|
||||
seen each of these patterns. $1.00 to anyone who can provide a heuristic
|
||||
for knowing which one to use. This includes AltaVista employees.
|
||||
"""
|
||||
__where = [ re.compile(r'lang=..>([^<]*)</div'),
|
||||
re.compile(r'name=\"q\" value=\"([^\"]*)\">'),
|
||||
re.compile(r'div style=padding:10px;>([^<]+)</div'),
|
||||
]
|
||||
|
||||
__languages = { 'english' : 'en',
|
||||
'chinese_simple' : 'zh',
|
||||
'chinese_traditional' : 'zt',
|
||||
'french' : 'fr',
|
||||
'german' : 'de',
|
||||
'italian' : 'it',
|
||||
'japanese' : 'ja',
|
||||
'korean' : 'ko',
|
||||
'spanish' : 'es',
|
||||
'portuguese' : 'pt',
|
||||
'russian' : 'ru',
|
||||
'greek' : 'el',
|
||||
'dutch' : 'nl',
|
||||
}
|
||||
|
||||
"""
|
||||
All of the available language names.
|
||||
"""
|
||||
available_languages = [ x.title() for x in __languages.keys() ]
|
||||
|
||||
"""
|
||||
Calling translate() or babelize() can raise a BabelizerError
|
||||
"""
|
||||
class BabelizerError(Exception):
|
||||
pass
|
||||
|
||||
class LanguageNotAvailableError(BabelizerError):
|
||||
pass
|
||||
class BabelfishChangedError(BabelizerError):
|
||||
pass
|
||||
class BabelizerIOError(BabelizerError):
|
||||
pass
|
||||
|
||||
def clean(text):
|
||||
return ' '.join(string.replace(text.strip(), "\n", ' ').split())
|
||||
|
||||
def translate(phrase, from_lang, to_lang):
|
||||
phrase = clean(phrase)
|
||||
try:
|
||||
from_code = __languages[from_lang.lower()]
|
||||
to_code = __languages[to_lang.lower()]
|
||||
except KeyError, lang:
|
||||
raise LanguageNotAvailableError(lang)
|
||||
|
||||
params = urllib.urlencode( { 'BabelFishFrontPage' : 'yes',
|
||||
'doit' : 'done',
|
||||
'tt' : 'urltext',
|
||||
'intl' : '1',
|
||||
'urltext' : phrase,
|
||||
'lp' : from_code + '_' + to_code } )
|
||||
try:
|
||||
response = urllib.urlopen('http://babelfish.altavista.com/babelfish/tr', params)
|
||||
except IOError, what:
|
||||
raise BabelizerIOError("Couldn't talk to server: %s" % what)
|
||||
except:
|
||||
print "Unexpected error:", sys.exc_info()[0]
|
||||
|
||||
html = response.read()
|
||||
try:
|
||||
begin = html.index('<!-- Target text (content) -->')
|
||||
end = html.index('<!-- end: Target text (content) -->')
|
||||
html = html[begin:end]
|
||||
except ValueError:
|
||||
pass
|
||||
for regex in __where:
|
||||
match = regex.search(html)
|
||||
if match:
|
||||
break
|
||||
if not match:
|
||||
raise BabelfishChangedError("Can't recognize translated string.")
|
||||
return clean(match.group(1))
|
||||
|
||||
def babelize(phrase, from_language, through_language, limit = 12, callback = None):
|
||||
phrase = clean(phrase)
|
||||
seen = { phrase: 1 }
|
||||
results = []
|
||||
if callback:
|
||||
def_callback = callback
|
||||
else:
|
||||
def_callback = results.append
|
||||
def_callback(phrase)
|
||||
flip = { from_language: through_language, through_language: from_language }
|
||||
next = from_language
|
||||
for i in range(limit):
|
||||
phrase = translate(phrase, next, flip[next])
|
||||
if seen.has_key(phrase):
|
||||
break
|
||||
seen[phrase] = 1
|
||||
def_callback(phrase)
|
||||
next = flip[next]
|
||||
# next is set to the language of the last entry. this should be the same
|
||||
# as the language we are translating to
|
||||
if next != through_language:
|
||||
phrase = translate(phrase, next, flip[next])
|
||||
def_callback(phrase)
|
||||
if not callback:
|
||||
return results
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
def printer(x):
|
||||
print x
|
||||
sys.stdout.flush();
|
||||
|
||||
|
||||
babelize("I won't take that sort of treatment from you, or from your doggie!",
|
||||
'english', 'french', callback = printer)
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,8 +0,0 @@
|
||||
"""
|
||||
Copyright (c) 2003 Gustavo Niemeyer <niemeyer@conectiva.com>
|
||||
|
||||
This module offers extensions to the standard python 2.3+
|
||||
datetime module.
|
||||
"""
|
||||
__author__ = "Gustavo Niemeyer <niemeyer@conectiva.com>"
|
||||
__license__ = "PSF License"
|
@ -1,869 +0,0 @@
|
||||
# -*- coding:iso-8859-1 -*-
|
||||
# Gotten from: http://moin.conectiva.com.br/DateUtil
|
||||
|
||||
"""
|
||||
Copyright (c) 2003 Gustavo Niemeyer <niemeyer@conectiva.com>
|
||||
|
||||
This module offers extensions to the standard python 2.3+
|
||||
datetime module.
|
||||
"""
|
||||
__author__ = "Gustavo Niemeyer <niemeyer@conectiva.com>"
|
||||
__license__ = "PSF License"
|
||||
|
||||
import os.path
|
||||
import string
|
||||
import sys
|
||||
import time
|
||||
|
||||
import datetime
|
||||
import relativedelta
|
||||
import tz
|
||||
|
||||
__all__ = ["parse", "parserinfo"]
|
||||
|
||||
# Some pointers:
|
||||
#
|
||||
# http://www.cl.cam.ac.uk/~mgk25/iso-time.html
|
||||
# http://www.iso.ch/iso/en/prods-services/popstds/datesandtime.html
|
||||
# http://www.w3.org/TR/NOTE-datetime
|
||||
# http://ringmaster.arc.nasa.gov/tools/time_formats.html
|
||||
# http://search.cpan.org/author/MUIR/Time-modules-2003.0211/lib/Time/ParseDate.pm
|
||||
# http://stein.cshl.org/jade/distrib/docs/java.text.SimpleDateFormat.html
|
||||
|
||||
try:
|
||||
from cStringIO import StringIO
|
||||
except ImportError:
|
||||
from StringIO import StringIO
|
||||
|
||||
class _timelex:
|
||||
def __init__(self, instream):
|
||||
if isinstance(instream, basestring):
|
||||
instream = StringIO(instream)
|
||||
self.instream = instream
|
||||
self.wordchars = ('abcdfeghijklmnopqrstuvwxyz'
|
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZ_'
|
||||
'ßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿ'
|
||||
'ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞ')
|
||||
self.numchars = '0123456789'
|
||||
self.whitespace = ' \t\r\n'
|
||||
self.charstack = []
|
||||
self.tokenstack = []
|
||||
self.eof = False
|
||||
|
||||
def get_token(self):
|
||||
if self.tokenstack:
|
||||
return self.tokenstack.pop(0)
|
||||
seenletters = False
|
||||
token = None
|
||||
state = None
|
||||
wordchars = self.wordchars
|
||||
numchars = self.numchars
|
||||
whitespace = self.whitespace
|
||||
while not self.eof:
|
||||
if self.charstack:
|
||||
nextchar = self.charstack.pop(0)
|
||||
else:
|
||||
nextchar = self.instream.read(1)
|
||||
if not nextchar:
|
||||
self.eof = True
|
||||
break
|
||||
elif not state:
|
||||
token = nextchar
|
||||
if nextchar in wordchars:
|
||||
state = 'a'
|
||||
elif nextchar in numchars:
|
||||
state = '0'
|
||||
elif nextchar in whitespace:
|
||||
token = ' '
|
||||
break # emit token
|
||||
else:
|
||||
break # emit token
|
||||
elif state == 'a':
|
||||
seenletters = True
|
||||
if nextchar in wordchars:
|
||||
token += nextchar
|
||||
elif nextchar == '.':
|
||||
token += nextchar
|
||||
state = 'a.'
|
||||
else:
|
||||
self.charstack.append(nextchar)
|
||||
break # emit token
|
||||
elif state == '0':
|
||||
if nextchar in numchars:
|
||||
token += nextchar
|
||||
elif nextchar == '.':
|
||||
token += nextchar
|
||||
state = '0.'
|
||||
else:
|
||||
self.charstack.append(nextchar)
|
||||
break # emit token
|
||||
elif state == 'a.':
|
||||
seenletters = True
|
||||
if nextchar == '.' or nextchar in wordchars:
|
||||
token += nextchar
|
||||
elif nextchar in numchars and token[-1] == '.':
|
||||
token += nextchar
|
||||
state = '0.'
|
||||
else:
|
||||
self.charstack.append(nextchar)
|
||||
break # emit token
|
||||
elif state == '0.':
|
||||
if nextchar == '.' or nextchar in numchars:
|
||||
token += nextchar
|
||||
elif nextchar in wordchars and token[-1] == '.':
|
||||
token += nextchar
|
||||
state = 'a.'
|
||||
else:
|
||||
self.charstack.append(nextchar)
|
||||
break # emit token
|
||||
if (state in ('a.', '0.') and
|
||||
(seenletters or token.count('.') > 1 or token[-1] == '.')):
|
||||
l = token.split('.')
|
||||
token = l[0]
|
||||
for tok in l[1:]:
|
||||
self.tokenstack.append('.')
|
||||
if tok:
|
||||
self.tokenstack.append(tok)
|
||||
return token
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
def next(self):
|
||||
token = self.get_token()
|
||||
if token is None:
|
||||
raise StopIteration
|
||||
return token
|
||||
|
||||
def split(cls, s):
|
||||
return list(cls(s))
|
||||
split = classmethod(split)
|
||||
|
||||
class _resultbase(object):
|
||||
|
||||
def __init__(self):
|
||||
for attr in self.__slots__:
|
||||
setattr(self, attr, None)
|
||||
|
||||
def _repr(self, classname):
|
||||
l = []
|
||||
for attr in self.__slots__:
|
||||
value = getattr(self, attr)
|
||||
if value is not None:
|
||||
l.append("%s=%s" % (attr, `value`))
|
||||
return "%s(%s)" % (classname, ", ".join(l))
|
||||
|
||||
def __repr__(self):
|
||||
return self._repr(self.__class__.__name__)
|
||||
|
||||
class parserinfo:
|
||||
|
||||
# m from a.m/p.m, t from ISO T separator
|
||||
JUMP = [" ", ".", ",", ";", "-", "/", "'",
|
||||
"at", "on", "and", "ad", "m", "t", "of",
|
||||
"st", "nd", "rd", "th"]
|
||||
|
||||
WEEKDAYS = [("Mon", "Monday"),
|
||||
("Tue", "Tuesday"),
|
||||
("Wed", "Wednesday"),
|
||||
("Thu", "Thursday"),
|
||||
("Fri", "Friday"),
|
||||
("Sat", "Saturday"),
|
||||
("Sun", "Sunday")]
|
||||
MONTHS = [("Jan", "January"),
|
||||
("Feb", "February"),
|
||||
("Mar", "March"),
|
||||
("Apr", "April"),
|
||||
("May", "May"),
|
||||
("Jun", "June"),
|
||||
("Jul", "July"),
|
||||
("Aug", "August"),
|
||||
("Sep", "September"),
|
||||
("Oct", "October"),
|
||||
("Nov", "November"),
|
||||
("Dec", "December")]
|
||||
HMS = [("h", "hour", "hours"),
|
||||
("m", "minute", "minutes"),
|
||||
("s", "second", "seconds")]
|
||||
AMPM = [("am", "a"),
|
||||
("pm", "p")]
|
||||
UTCZONE = ["UTC", "GMT", "Z"]
|
||||
PERTAIN = ["of"]
|
||||
TZOFFSET = {}
|
||||
|
||||
def __init__(self, dayfirst=False, yearfirst=False):
|
||||
self._jump = self._convert(self.JUMP)
|
||||
self._weekdays = self._convert(self.WEEKDAYS)
|
||||
self._months = self._convert(self.MONTHS)
|
||||
self._hms = self._convert(self.HMS)
|
||||
self._ampm = self._convert(self.AMPM)
|
||||
self._utczone = self._convert(self.UTCZONE)
|
||||
self._pertain = self._convert(self.PERTAIN)
|
||||
|
||||
self.dayfirst = dayfirst
|
||||
self.yearfirst = yearfirst
|
||||
|
||||
self._year = time.localtime().tm_year
|
||||
self._century = self._year/100*100
|
||||
|
||||
def _convert(self, lst):
|
||||
dct = {}
|
||||
for i in range(len(lst)):
|
||||
v = lst[i]
|
||||
if isinstance(v, tuple):
|
||||
for v in v:
|
||||
dct[v.lower()] = i
|
||||
else:
|
||||
dct[v.lower()] = i
|
||||
return dct
|
||||
|
||||
def jump(self, name):
|
||||
return name.lower() in self._jump
|
||||
|
||||
def weekday(self, name):
|
||||
if len(name) >= 3:
|
||||
try:
|
||||
return self._weekdays[name.lower()]
|
||||
except KeyError:
|
||||
pass
|
||||
return None
|
||||
|
||||
def month(self, name):
|
||||
if len(name) >= 3:
|
||||
try:
|
||||
return self._months[name.lower()]+1
|
||||
except KeyError:
|
||||
pass
|
||||
return None
|
||||
|
||||
def hms(self, name):
|
||||
try:
|
||||
return self._hms[name.lower()]
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
def ampm(self, name):
|
||||
try:
|
||||
return self._ampm[name.lower()]
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
def pertain(self, name):
|
||||
return name.lower() in self._pertain
|
||||
|
||||
def utczone(self, name):
|
||||
return name.lower() in self._utczone
|
||||
|
||||
def tzoffset(self, name):
|
||||
if name in self._utczone:
|
||||
return 0
|
||||
return self.TZOFFSET.get(name)
|
||||
|
||||
def convertyear(self, year):
|
||||
if year < 100:
|
||||
year += self._century
|
||||
if abs(year-self._year) >= 50:
|
||||
if year < self._year:
|
||||
year += 100
|
||||
else:
|
||||
year -= 100
|
||||
return year
|
||||
|
||||
def validate(self, res):
|
||||
# move to info
|
||||
if res.year is not None:
|
||||
res.year = self.convertyear(res.year)
|
||||
if res.tzoffset == 0 and not res.tzname or res.tzname == 'Z':
|
||||
res.tzname = "UTC"
|
||||
res.tzoffset = 0
|
||||
elif res.tzoffset != 0 and res.tzname and self.utczone(res.tzname):
|
||||
res.tzoffset = 0
|
||||
return True
|
||||
|
||||
|
||||
class parser:
|
||||
|
||||
def __init__(self, parserinfo=parserinfo):
|
||||
self.info = parserinfo()
|
||||
|
||||
def parse(self, timestr, default=None,
|
||||
ignoretz=False, tzinfos=None,
|
||||
**kwargs):
|
||||
if not default:
|
||||
default = datetime.datetime.now().replace(hour=0, minute=0,
|
||||
second=0, microsecond=0)
|
||||
res = self._parse(timestr, **kwargs)
|
||||
if res is None:
|
||||
raise ValueError, "unknown string format"
|
||||
repl = {}
|
||||
for attr in ["year", "month", "day", "hour",
|
||||
"minute", "second", "microsecond"]:
|
||||
value = getattr(res, attr)
|
||||
if value is not None:
|
||||
repl[attr] = value
|
||||
ret = default.replace(**repl)
|
||||
if res.weekday is not None and not res.day:
|
||||
ret = ret+relativedelta.relativedelta(weekday=res.weekday)
|
||||
if not ignoretz:
|
||||
if callable(tzinfos) or tzinfos and res.tzname in tzinfos:
|
||||
if callable(tzinfos):
|
||||
tzdata = tzinfos(res.tzname, res.tzoffset)
|
||||
else:
|
||||
tzdata = tzinfos.get(res.tzname)
|
||||
if isinstance(tzdata, datetime.tzinfo):
|
||||
tzinfo = tzdata
|
||||
elif isinstance(tzdata, basestring):
|
||||
tzinfo = tz.tzstr(tzdata)
|
||||
elif isinstance(tzdata, int):
|
||||
tzinfo = tz.tzoffset(res.tzname, tzdata)
|
||||
else:
|
||||
raise ValueError, "offset must be tzinfo subclass, " \
|
||||
"tz string, or int offset"
|
||||
ret = ret.replace(tzinfo=tzinfo)
|
||||
elif res.tzname and res.tzname in time.tzname:
|
||||
ret = ret.replace(tzinfo=tz.tzlocal())
|
||||
elif res.tzoffset == 0:
|
||||
ret = ret.replace(tzinfo=tz.tzutc())
|
||||
elif res.tzoffset:
|
||||
ret = ret.replace(tzinfo=tz.tzoffset(res.tzname, res.tzoffset))
|
||||
return ret
|
||||
|
||||
class _result(_resultbase):
|
||||
__slots__ = ["year", "month", "day", "weekday",
|
||||
"hour", "minute", "second", "microsecond",
|
||||
"tzname", "tzoffset"]
|
||||
|
||||
def _parse(self, timestr, dayfirst=None, yearfirst=None, fuzzy=False):
|
||||
info = self.info
|
||||
if dayfirst is None:
|
||||
dayfirst = info.dayfirst
|
||||
if yearfirst is None:
|
||||
yearfirst = info.yearfirst
|
||||
res = self._result()
|
||||
l = _timelex.split(timestr)
|
||||
try:
|
||||
|
||||
# year/month/day list
|
||||
ymd = []
|
||||
|
||||
# Index of the month string in ymd
|
||||
mstridx = -1
|
||||
|
||||
len_l = len(l)
|
||||
i = 0
|
||||
while i < len_l:
|
||||
|
||||
# Check if it's a number
|
||||
try:
|
||||
value = float(l[i])
|
||||
except ValueError:
|
||||
value = None
|
||||
if value is not None:
|
||||
# Token is a number
|
||||
len_li = len(l[i])
|
||||
i += 1
|
||||
if (len(ymd) == 3 and len_li in (2, 4)
|
||||
and (i >= len_l or l[i] != ':')):
|
||||
# 19990101T23[59]
|
||||
s = l[i-1]
|
||||
res.hour = int(s[:2])
|
||||
if len_li == 4:
|
||||
res.minute = int(s[2:])
|
||||
elif len_li == 6 or (len_li > 6 and l[i-1].find('.') == 6):
|
||||
# YYMMDD or HHMMSS[.ss]
|
||||
s = l[i-1]
|
||||
if not ymd and l[i-1].find('.') == -1:
|
||||
ymd.append(info.convertyear(int(s[:2])))
|
||||
ymd.append(int(s[2:4]))
|
||||
ymd.append(int(s[4:]))
|
||||
else:
|
||||
# 19990101T235959[.59]
|
||||
res.hour = int(s[:2])
|
||||
res.minute = int(s[2:4])
|
||||
value = float(s[4:])
|
||||
res.second = int(value)
|
||||
if value%1:
|
||||
res.microsecond = int(1000000*(value%1))
|
||||
elif len_li == 8:
|
||||
# YYYYMMDD
|
||||
s = l[i-1]
|
||||
ymd.append(int(s[:4]))
|
||||
ymd.append(int(s[4:6]))
|
||||
ymd.append(int(s[6:]))
|
||||
elif len_li in (12, 14):
|
||||
# YYYYMMDDhhmm[ss]
|
||||
s = l[i-1]
|
||||
ymd.append(int(s[:4]))
|
||||
ymd.append(int(s[4:6]))
|
||||
ymd.append(int(s[6:8]))
|
||||
res.hour = int(s[8:10])
|
||||
res.minute = int(s[10:12])
|
||||
if len_li == 14:
|
||||
res.second = int(s[12:])
|
||||
elif ((i < len_l and info.hms(l[i]) is not None) or
|
||||
(i+1 < len_l and l[i] == ' ' and
|
||||
info.hms(l[i+1]) is not None)):
|
||||
# HH[ ]h or MM[ ]m or SS[.ss][ ]s
|
||||
if l[i] == ' ':
|
||||
i += 1
|
||||
idx = info.hms(l[i])
|
||||
while True:
|
||||
if idx == 0:
|
||||
res.hour = int(value)
|
||||
if value%1:
|
||||
res.minute = int(60*(value%1))
|
||||
elif idx == 1:
|
||||
res.minute = int(value)
|
||||
if value%1:
|
||||
res.second = int(60*(value%1))
|
||||
elif idx == 2:
|
||||
res.second = int(value)
|
||||
if value%1:
|
||||
res.microsecond = int(1000000*(value%1))
|
||||
i += 1
|
||||
if i >= len_l or idx == 2:
|
||||
break
|
||||
# 12h00
|
||||
try:
|
||||
value = float(l[i])
|
||||
except ValueError:
|
||||
break
|
||||
else:
|
||||
i += 1
|
||||
idx += 1
|
||||
if i < len_l:
|
||||
newidx = info.hms(l[i])
|
||||
if newidx is not None:
|
||||
idx = newidx
|
||||
elif i+1 < len_l and l[i] == ':':
|
||||
# HH:MM[:SS[.ss]]
|
||||
res.hour = int(value)
|
||||
i += 1
|
||||
value = float(l[i])
|
||||
res.minute = int(value)
|
||||
if value%1:
|
||||
res.second = int(60*(value%1))
|
||||
i += 1
|
||||
if i < len_l and l[i] == ':':
|
||||
value = float(l[i+1])
|
||||
res.second = int(value)
|
||||
if value%1:
|
||||
res.microsecond = int(1000000*(value%1))
|
||||
i += 2
|
||||
elif i < len_l and l[i] in ('-', '/', '.'):
|
||||
sep = l[i]
|
||||
ymd.append(int(value))
|
||||
i += 1
|
||||
if i < len_l and not info.jump(l[i]):
|
||||
try:
|
||||
# 01-01[-01]
|
||||
ymd.append(int(l[i]))
|
||||
except ValueError:
|
||||
# 01-Jan[-01]
|
||||
value = info.month(l[i])
|
||||
if value is not None:
|
||||
ymd.append(value)
|
||||
assert mstridx == -1
|
||||
mstridx = len(ymd)-1
|
||||
else:
|
||||
return None
|
||||
i += 1
|
||||
if i < len_l and l[i] == sep:
|
||||
# We have three members
|
||||
i += 1
|
||||
value = info.month(l[i])
|
||||
if value is not None:
|
||||
ymd.append(value)
|
||||
mstridx = len(ymd)-1
|
||||
assert mstridx == -1
|
||||
else:
|
||||
ymd.append(int(l[i]))
|
||||
i += 1
|
||||
elif i >= len_l or info.jump(l[i]):
|
||||
if i+1 < len_l and info.ampm(l[i+1]) is not None:
|
||||
# 12 am
|
||||
res.hour = int(value)
|
||||
if res.hour < 12 and info.ampm(l[i+1]) == 1:
|
||||
res.hour += 12
|
||||
elif res.hour == 12 and info.ampm(l[i+1]) == 0:
|
||||
res.hour = 0
|
||||
i += 1
|
||||
else:
|
||||
# Year, month or day
|
||||
ymd.append(int(value))
|
||||
i += 1
|
||||
elif info.ampm(l[i]) is not None:
|
||||
# 12am
|
||||
res.hour = int(value)
|
||||
if res.hour < 12 and info.ampm(l[i]) == 1:
|
||||
res.hour += 12
|
||||
elif res.hour == 12 and info.ampm(l[i]) == 0:
|
||||
res.hour = 0
|
||||
i += 1
|
||||
elif not fuzzy:
|
||||
return None
|
||||
else:
|
||||
i += 1
|
||||
continue
|
||||
|
||||
# Check weekday
|
||||
value = info.weekday(l[i])
|
||||
if value is not None:
|
||||
res.weekday = value
|
||||
i += 1
|
||||
continue
|
||||
|
||||
# Check month name
|
||||
value = info.month(l[i])
|
||||
if value is not None:
|
||||
ymd.append(value)
|
||||
assert mstridx == -1
|
||||
mstridx = len(ymd)-1
|
||||
i += 1
|
||||
if i < len_l:
|
||||
if l[i] in ('-', '/'):
|
||||
# Jan-01[-99]
|
||||
sep = l[i]
|
||||
i += 1
|
||||
ymd.append(int(l[i]))
|
||||
i += 1
|
||||
if i < len_l and l[i] == sep:
|
||||
# Jan-01-99
|
||||
i += 1
|
||||
ymd.append(int(l[i]))
|
||||
i += 1
|
||||
elif (i+3 < len_l and l[i] == l[i+2] == ' '
|
||||
and info.pertain(l[i+1])):
|
||||
# Jan of 01
|
||||
# In this case, 01 is clearly year
|
||||
try:
|
||||
value = int(l[i+3])
|
||||
except ValueError:
|
||||
# Wrong guess
|
||||
pass
|
||||
else:
|
||||
# Convert it here to become unambiguous
|
||||
ymd.append(info.convertyear(value))
|
||||
i += 4
|
||||
continue
|
||||
|
||||
# Check am/pm
|
||||
value = info.ampm(l[i])
|
||||
if value is not None:
|
||||
if value == 1 and res.hour < 12:
|
||||
res.hour += 12
|
||||
elif value == 0 and res.hour == 12:
|
||||
res.hour = 0
|
||||
i += 1
|
||||
continue
|
||||
|
||||
# Check for a timezone name
|
||||
if (res.hour is not None and len(l[i]) <= 5 and
|
||||
res.tzname is None and res.tzoffset is None and
|
||||
not [x for x in l[i] if x not in string.ascii_uppercase]):
|
||||
res.tzname = l[i]
|
||||
res.tzoffset = info.tzoffset(res.tzname)
|
||||
i += 1
|
||||
|
||||
# Check for something like GMT+3, or BRST+3. Notice
|
||||
# that it doesn't mean "I am 3 hours after GMT", but
|
||||
# "my time +3 is GMT". If found, we reverse the
|
||||
# logic so that timezone parsing code will get it
|
||||
# right.
|
||||
if i < len_l and l[i] in ('+', '-'):
|
||||
l[i] = ('+', '-')[l[i] == '+']
|
||||
res.tzoffset = None
|
||||
if info.utczone(res.tzname):
|
||||
# With something like GMT+3, the timezone
|
||||
# is *not* GMT.
|
||||
res.tzname = None
|
||||
|
||||
continue
|
||||
|
||||
# Check for a numbered timezone
|
||||
if res.hour is not None and l[i] in ('+', '-'):
|
||||
signal = (-1,1)[l[i] == '+']
|
||||
i += 1
|
||||
len_li = len(l[i])
|
||||
if len_li == 4:
|
||||
# -0300
|
||||
res.tzoffset = int(l[i][:2])*3600+int(l[i][2:])*60
|
||||
elif i+1 < len_l and l[i+1] == ':':
|
||||
# -03:00
|
||||
res.tzoffset = int(l[i])*3600+int(l[i+2])*60
|
||||
i += 2
|
||||
elif len_li <= 2:
|
||||
# -[0]3
|
||||
res.tzoffset = int(l[i][:2])*3600
|
||||
else:
|
||||
return None
|
||||
i += 1
|
||||
res.tzoffset *= signal
|
||||
|
||||
# Look for a timezone name between parenthesis
|
||||
if (i+3 < len_l and
|
||||
info.jump(l[i]) and l[i+1] == '(' and l[i+3] == ')' and
|
||||
3 <= len(l[i+2]) <= 5 and
|
||||
not [x for x in l[i+2]
|
||||
if x not in string.ascii_uppercase]):
|
||||
# -0300 (BRST)
|
||||
res.tzname = l[i+2]
|
||||
i += 4
|
||||
continue
|
||||
|
||||
# Check jumps
|
||||
if not (info.jump(l[i]) or fuzzy):
|
||||
return None
|
||||
|
||||
i += 1
|
||||
|
||||
# Process year/month/day
|
||||
len_ymd = len(ymd)
|
||||
if len_ymd > 3:
|
||||
# More than three members!?
|
||||
return None
|
||||
elif len_ymd == 1 or (mstridx != -1 and len_ymd == 2):
|
||||
# One member, or two members with a month string
|
||||
if mstridx != -1:
|
||||
res.month = ymd[mstridx]
|
||||
del ymd[mstridx]
|
||||
if len_ymd > 1 or mstridx == -1:
|
||||
if ymd[0] > 31:
|
||||
res.year = ymd[0]
|
||||
else:
|
||||
res.day = ymd[0]
|
||||
elif len_ymd == 2:
|
||||
# Two members with numbers
|
||||
if ymd[0] > 31:
|
||||
# 99-01
|
||||
res.year, res.month = ymd
|
||||
elif ymd[1] > 31:
|
||||
# 01-99
|
||||
res.month, res.year = ymd
|
||||
elif dayfirst and ymd[1] <= 12:
|
||||
# 13-01
|
||||
res.day, res.month = ymd
|
||||
else:
|
||||
# 01-13
|
||||
res.month, res.day = ymd
|
||||
if len_ymd == 3:
|
||||
# Three members
|
||||
if mstridx == 0:
|
||||
res.month, res.day, res.year = ymd
|
||||
elif mstridx == 1:
|
||||
if ymd[0] > 31 or (yearfirst and ymd[2] <= 31):
|
||||
# 99-Jan-01
|
||||
res.year, res.month, res.day = ymd
|
||||
else:
|
||||
# 01-Jan-01
|
||||
# Give precendence to day-first, since
|
||||
# two-digit years is usually hand-written.
|
||||
res.day, res.month, res.year = ymd
|
||||
elif mstridx == 2:
|
||||
# WTF!?
|
||||
if ymd[1] > 31:
|
||||
# 01-99-Jan
|
||||
res.day, res.year, res.month = ymd
|
||||
else:
|
||||
# 99-01-Jan
|
||||
res.year, res.day, res.month = ymd
|
||||
else:
|
||||
if ymd[0] > 31 or \
|
||||
(yearfirst and ymd[1] <= 12 and ymd[2] <= 31):
|
||||
# 99-01-01
|
||||
res.year, res.month, res.day = ymd
|
||||
elif ymd[0] > 12 or (dayfirst and ymd[1] <= 12):
|
||||
# 13-01-01
|
||||
res.day, res.month, res.year = ymd
|
||||
else:
|
||||
# 01-13-01
|
||||
res.month, res.day, res.year = ymd
|
||||
|
||||
except (IndexError, ValueError, AssertionError):
|
||||
return None
|
||||
|
||||
if not info.validate(res):
|
||||
return None
|
||||
return res
|
||||
|
||||
DEFAULTPARSER = parser()
|
||||
def parse(timestr, parserinfo=None, **kwargs):
|
||||
if parserinfo:
|
||||
return parser(parserinfo).parse(timestr, **kwargs)
|
||||
else:
|
||||
return DEFAULTPARSER.parse(timestr, **kwargs)
|
||||
|
||||
class _tzparser:
|
||||
|
||||
class _result(_resultbase):
|
||||
|
||||
__slots__ = ["stdabbr", "stdoffset", "dstabbr", "dstoffset",
|
||||
"start", "end"]
|
||||
|
||||
class _attr(_resultbase):
|
||||
__slots__ = ["month", "week", "weekday",
|
||||
"yday", "jyday", "day", "time"]
|
||||
|
||||
def __repr__(self):
|
||||
return self._repr("")
|
||||
|
||||
def __init__(self):
|
||||
_resultbase.__init__(self)
|
||||
self.start = self._attr()
|
||||
self.end = self._attr()
|
||||
|
||||
def parse(self, tzstr):
|
||||
res = self._result()
|
||||
l = _timelex.split(tzstr)
|
||||
try:
|
||||
|
||||
len_l = len(l)
|
||||
|
||||
i = 0
|
||||
while i < len_l:
|
||||
# BRST+3[BRDT[+2]]
|
||||
j = i
|
||||
while j < len_l and not [x for x in l[j]
|
||||
if x in "0123456789:,-+"]:
|
||||
j += 1
|
||||
if j != i:
|
||||
if not res.stdabbr:
|
||||
offattr = "stdoffset"
|
||||
res.stdabbr = "".join(l[i:j])
|
||||
else:
|
||||
offattr = "dstoffset"
|
||||
res.dstabbr = "".join(l[i:j])
|
||||
i = j
|
||||
if (i < len_l and
|
||||
(l[i] in ('+', '-') or l[i][0] in "0123456789")):
|
||||
if l[i] in ('+', '-'):
|
||||
signal = (1,-1)[l[i] == '+']
|
||||
i += 1
|
||||
else:
|
||||
signal = -1
|
||||
len_li = len(l[i])
|
||||
if len_li == 4:
|
||||
# -0300
|
||||
setattr(res, offattr,
|
||||
(int(l[i][:2])*3600+int(l[i][2:])*60)*signal)
|
||||
elif i+1 < len_l and l[i+1] == ':':
|
||||
# -03:00
|
||||
setattr(res, offattr,
|
||||
(int(l[i])*3600+int(l[i+2])*60)*signal)
|
||||
i += 2
|
||||
elif len_li <= 2:
|
||||
# -[0]3
|
||||
setattr(res, offattr,
|
||||
int(l[i][:2])*3600*signal)
|
||||
else:
|
||||
return None
|
||||
i += 1
|
||||
if res.dstabbr:
|
||||
break
|
||||
else:
|
||||
break
|
||||
|
||||
if i < len_l:
|
||||
for j in range(i, len_l):
|
||||
if l[j] == ';': l[j] = ','
|
||||
|
||||
assert l[i] == ','
|
||||
|
||||
i += 1
|
||||
|
||||
if i >= len_l:
|
||||
pass
|
||||
elif (8 <= l.count(',') <= 9 and
|
||||
not [y for x in l[i:] if x != ','
|
||||
for y in x if y not in "0123456789"]):
|
||||
# GMT0BST,3,0,30,3600,10,0,26,7200[,3600]
|
||||
for x in (res.start, res.end):
|
||||
x.month = int(l[i])
|
||||
i += 2
|
||||
if l[i] == '-':
|
||||
value = int(l[i+1])*-1
|
||||
i += 1
|
||||
else:
|
||||
value = int(l[i])
|
||||
i += 2
|
||||
if value:
|
||||
x.week = value
|
||||
x.weekday = (int(l[i])-1)%7
|
||||
else:
|
||||
x.day = int(l[i])
|
||||
i += 2
|
||||
x.time = int(l[i])
|
||||
i += 2
|
||||
if i < len_l:
|
||||
if l[i] in ('-','+'):
|
||||
signal = (-1,1)[l[i] == "+"]
|
||||
i += 1
|
||||
else:
|
||||
signal = 1
|
||||
res.dstoffset = (res.stdoffset+int(l[i]))*signal
|
||||
elif (l.count(',') == 2 and l[i:].count('/') <= 2 and
|
||||
not [y for x in l[i:] if x not in (',','/','J','M',
|
||||
'.','-',':')
|
||||
for y in x if y not in "0123456789"]):
|
||||
for x in (res.start, res.end):
|
||||
if l[i] == 'J':
|
||||
# non-leap year day (1 based)
|
||||
i += 1
|
||||
x.jyday = int(l[i])
|
||||
elif l[i] == 'M':
|
||||
# month[-.]week[-.]weekday
|
||||
i += 1
|
||||
x.month = int(l[i])
|
||||
i += 1
|
||||
assert l[i] in ('-', '.')
|
||||
i += 1
|
||||
x.week = int(l[i])
|
||||
if x.week == 5:
|
||||
x.week = -1
|
||||
i += 1
|
||||
assert l[i] in ('-', '.')
|
||||
i += 1
|
||||
x.weekday = (int(l[i])-1)%7
|
||||
else:
|
||||
# year day (zero based)
|
||||
x.yday = int(l[i])+1
|
||||
|
||||
i += 1
|
||||
|
||||
if i < len_l and l[i] == '/':
|
||||
i += 1
|
||||
# start time
|
||||
len_li = len(l[i])
|
||||
if len_li == 4:
|
||||
# -0300
|
||||
x.time = (int(l[i][:2])*3600+int(l[i][2:])*60)
|
||||
elif i+1 < len_l and l[i+1] == ':':
|
||||
# -03:00
|
||||
x.time = int(l[i])*3600+int(l[i+2])*60
|
||||
i += 2
|
||||
if i+1 < len_l and l[i+1] == ':':
|
||||
i += 2
|
||||
x.time += int(l[i])
|
||||
elif len_li <= 2:
|
||||
# -[0]3
|
||||
x.time = (int(l[i][:2])*3600)
|
||||
else:
|
||||
return None
|
||||
i += 1
|
||||
|
||||
assert i == len_l or l[i] == ','
|
||||
|
||||
i += 1
|
||||
|
||||
assert i >= len_l
|
||||
|
||||
except (IndexError, ValueError, AssertionError):
|
||||
return None
|
||||
|
||||
return res
|
||||
|
||||
DEFAULTTZPARSER = _tzparser()
|
||||
def _parsetz(tzstr):
|
||||
return DEFAULTTZPARSER.parse(tzstr)
|
||||
|
||||
# vim:ts=4:sw=4:et
|
@ -1,432 +0,0 @@
|
||||
"""
|
||||
Copyright (c) 2003 Gustavo Niemeyer <niemeyer@conectiva.com>
|
||||
|
||||
This module offers extensions to the standard python 2.3+
|
||||
datetime module.
|
||||
"""
|
||||
__author__ = "Gustavo Niemeyer <niemeyer@conectiva.com>"
|
||||
__license__ = "PSF License"
|
||||
|
||||
import datetime
|
||||
import calendar
|
||||
|
||||
__all__ = ["relativedelta", "MO", "TU", "WE", "TH", "FR", "SA", "SU"]
|
||||
|
||||
class weekday(object):
|
||||
__slots__ = ["weekday", "n"]
|
||||
|
||||
def __init__(self, weekday, n=0):
|
||||
self.weekday = weekday
|
||||
self.n = n
|
||||
|
||||
def __call__(self, n):
|
||||
if n == self.n:
|
||||
return self
|
||||
else:
|
||||
return self.__class__(self.weekday, n)
|
||||
|
||||
def __eq__(self, other):
|
||||
try:
|
||||
if self.weekday != other.weekday or self.n != other.n:
|
||||
return False
|
||||
except AttributeError:
|
||||
return False
|
||||
return True
|
||||
|
||||
def __repr__(self):
|
||||
s = ("MO", "TU", "WE", "TH", "FR", "SA", "SU")[self.weekday]
|
||||
if not self.n:
|
||||
return s
|
||||
else:
|
||||
return "%s(%+d)" % (s, self.n)
|
||||
|
||||
MO, TU, WE, TH, FR, SA, SU = weekdays = tuple([weekday(x) for x in range(7)])
|
||||
|
||||
class relativedelta:
|
||||
"""
|
||||
The relativedelta type is based on the specification of the excelent
|
||||
work done by M.-A. Lemburg in his mx.DateTime extension. However,
|
||||
notice that this type does *NOT* implement the same algorithm as
|
||||
his work. Do *NOT* expect it to behave like mx.DateTime's counterpart.
|
||||
|
||||
There's two different ways to build a relativedelta instance. The
|
||||
first one is passing it two date/datetime classes:
|
||||
|
||||
relativedelta(datetime1, datetime2)
|
||||
|
||||
And the other way is to use the following keyword arguments:
|
||||
|
||||
year, month, day, hour, minute, second, microsecond:
|
||||
Absolute information.
|
||||
|
||||
years, months, weeks, days, hours, minutes, seconds, microseconds:
|
||||
Relative information, may be negative.
|
||||
|
||||
weekday:
|
||||
One of the weekday instances (MO, TU, etc). These instances may
|
||||
receive a parameter N, specifying the Nth weekday, which could
|
||||
be positive or negative (like MO(+1) or MO(-2). Not specifying
|
||||
it is the same as specifying +1. You can also use an integer,
|
||||
where 0=MO.
|
||||
|
||||
leapdays:
|
||||
Will add given days to the date found, if year is a leap
|
||||
year, and the date found is post 28 of february.
|
||||
|
||||
yearday, nlyearday:
|
||||
Set the yearday or the non-leap year day (jump leap days).
|
||||
These are converted to day/month/leapdays information.
|
||||
|
||||
Here is the behavior of operations with relativedelta:
|
||||
|
||||
1) Calculate the absolute year, using the 'year' argument, or the
|
||||
original datetime year, if the argument is not present.
|
||||
|
||||
2) Add the relative 'years' argument to the absolute year.
|
||||
|
||||
3) Do steps 1 and 2 for month/months.
|
||||
|
||||
4) Calculate the absolute day, using the 'day' argument, or the
|
||||
original datetime day, if the argument is not present. Then,
|
||||
subtract from the day until it fits in the year and month
|
||||
found after their operations.
|
||||
|
||||
5) Add the relative 'days' argument to the absolute day. Notice
|
||||
that the 'weeks' argument is multiplied by 7 and added to
|
||||
'days'.
|
||||
|
||||
6) Do steps 1 and 2 for hour/hours, minute/minutes, second/seconds,
|
||||
microsecond/microseconds.
|
||||
|
||||
7) If the 'weekday' argument is present, calculate the weekday,
|
||||
with the given (wday, nth) tuple. wday is the index of the
|
||||
weekday (0-6, 0=Mon), and nth is the number of weeks to add
|
||||
forward or backward, depending on its signal. Notice that if
|
||||
the calculated date is already Monday, for example, using
|
||||
(0, 1) or (0, -1) won't change the day.
|
||||
"""
|
||||
|
||||
def __init__(self, dt1=None, dt2=None,
|
||||
years=0, months=0, days=0, leapdays=0, weeks=0,
|
||||
hours=0, minutes=0, seconds=0, microseconds=0,
|
||||
year=None, month=None, day=None, weekday=None,
|
||||
yearday=None, nlyearday=None,
|
||||
hour=None, minute=None, second=None, microsecond=None):
|
||||
if dt1 and dt2:
|
||||
if not isinstance(dt1, datetime.date) or \
|
||||
not isinstance(dt2, datetime.date):
|
||||
raise TypeError, "relativedelta only diffs datetime/date"
|
||||
if type(dt1) is not type(dt2):
|
||||
if not isinstance(dt1, datetime.datetime):
|
||||
dt1 = datetime.datetime.fromordinal(dt1.toordinal())
|
||||
elif not isinstance(dt2, datetime.datetime):
|
||||
dt2 = datetime.datetime.fromordinal(dt2.toordinal())
|
||||
self.years = 0
|
||||
self.months = 0
|
||||
self.days = 0
|
||||
self.leapdays = 0
|
||||
self.hours = 0
|
||||
self.minutes = 0
|
||||
self.seconds = 0
|
||||
self.microseconds = 0
|
||||
self.year = None
|
||||
self.month = None
|
||||
self.day = None
|
||||
self.weekday = None
|
||||
self.hour = None
|
||||
self.minute = None
|
||||
self.second = None
|
||||
self.microsecond = None
|
||||
self._has_time = 0
|
||||
|
||||
months = (dt1.year*12+dt1.month)-(dt2.year*12+dt2.month)
|
||||
self._set_months(months)
|
||||
dtm = self.__radd__(dt2)
|
||||
if dt1 < dt2:
|
||||
while dt1 > dtm:
|
||||
months += 1
|
||||
self._set_months(months)
|
||||
dtm = self.__radd__(dt2)
|
||||
else:
|
||||
while dt1 < dtm:
|
||||
months -= 1
|
||||
self._set_months(months)
|
||||
dtm = self.__radd__(dt2)
|
||||
delta = dt1 - dtm
|
||||
self.seconds = delta.seconds+delta.days*86400
|
||||
self.microseconds = delta.microseconds
|
||||
else:
|
||||
self.years = years
|
||||
self.months = months
|
||||
self.days = days+weeks*7
|
||||
self.leapdays = leapdays
|
||||
self.hours = hours
|
||||
self.minutes = minutes
|
||||
self.seconds = seconds
|
||||
self.microseconds = microseconds
|
||||
self.year = year
|
||||
self.month = month
|
||||
self.day = day
|
||||
self.hour = hour
|
||||
self.minute = minute
|
||||
self.second = second
|
||||
self.microsecond = microsecond
|
||||
|
||||
if type(weekday) is int:
|
||||
self.weekday = weekdays[weekday]
|
||||
else:
|
||||
self.weekday = weekday
|
||||
|
||||
yday = 0
|
||||
if nlyearday:
|
||||
yday = nlyearday
|
||||
elif yearday:
|
||||
yday = yearday
|
||||
if yearday > 59:
|
||||
self.leapdays = -1
|
||||
if yday:
|
||||
ydayidx = [31,59,90,120,151,181,212,243,273,304,334,366]
|
||||
for idx, ydays in enumerate(ydayidx):
|
||||
if yday <= ydays:
|
||||
self.month = idx+1
|
||||
if idx == 0:
|
||||
self.day = ydays
|
||||
else:
|
||||
self.day = yday-ydayidx[idx-1]
|
||||
break
|
||||
else:
|
||||
raise ValueError, "invalid year day (%d)" % yday
|
||||
|
||||
self._fix()
|
||||
|
||||
def _fix(self):
|
||||
if abs(self.microseconds) > 999999:
|
||||
s = self.microseconds/abs(self.microseconds)
|
||||
div, mod = divmod(self.microseconds*s, 1000000)
|
||||
self.microseconds = mod*s
|
||||
self.seconds += div*s
|
||||
if abs(self.seconds) > 59:
|
||||
s = self.seconds/abs(self.seconds)
|
||||
div, mod = divmod(self.seconds*s, 60)
|
||||
self.seconds = mod*s
|
||||
self.minutes += div*s
|
||||
if abs(self.minutes) > 59:
|
||||
s = self.minutes/abs(self.minutes)
|
||||
div, mod = divmod(self.minutes*s, 60)
|
||||
self.minutes = mod*s
|
||||
self.hours += div*s
|
||||
if abs(self.hours) > 23:
|
||||
s = self.hours/abs(self.hours)
|
||||
div, mod = divmod(self.hours*s, 24)
|
||||
self.hours = mod*s
|
||||
self.days += div*s
|
||||
if abs(self.months) > 11:
|
||||
s = self.months/abs(self.months)
|
||||
div, mod = divmod(self.months*s, 12)
|
||||
self.months = mod*s
|
||||
self.years += div*s
|
||||
if (self.hours or self.minutes or self.seconds or self.microseconds or
|
||||
self.hour is not None or self.minute is not None or
|
||||
self.second is not None or self.microsecond is not None):
|
||||
self._has_time = 1
|
||||
else:
|
||||
self._has_time = 0
|
||||
|
||||
def _set_months(self, months):
|
||||
self.months = months
|
||||
if abs(self.months) > 11:
|
||||
s = self.months/abs(self.months)
|
||||
div, mod = divmod(self.months*s, 12)
|
||||
self.months = mod*s
|
||||
self.years = div*s
|
||||
else:
|
||||
self.years = 0
|
||||
|
||||
def __radd__(self, other):
|
||||
if not isinstance(other, datetime.date):
|
||||
raise TypeError, "unsupported type for add operation"
|
||||
elif self._has_time and not isinstance(other, datetime.datetime):
|
||||
other = datetime.datetime.fromordinal(other.toordinal())
|
||||
year = (self.year or other.year)+self.years
|
||||
month = self.month or other.month
|
||||
if self.months:
|
||||
assert 1 <= abs(self.months) <= 12
|
||||
month += self.months
|
||||
if month > 12:
|
||||
year += 1
|
||||
month -= 12
|
||||
elif month < 1:
|
||||
year -= 1
|
||||
month += 12
|
||||
day = min(calendar.monthrange(year, month)[1],
|
||||
self.day or other.day)
|
||||
repl = {"year": year, "month": month, "day": day}
|
||||
for attr in ["hour", "minute", "second", "microsecond"]:
|
||||
value = getattr(self, attr)
|
||||
if value is not None:
|
||||
repl[attr] = value
|
||||
days = self.days
|
||||
if self.leapdays and month > 2 and calendar.isleap(year):
|
||||
days += self.leapdays
|
||||
ret = (other.replace(**repl)
|
||||
+ datetime.timedelta(days=days,
|
||||
hours=self.hours,
|
||||
minutes=self.minutes,
|
||||
seconds=self.seconds,
|
||||
microseconds=self.microseconds))
|
||||
if self.weekday:
|
||||
weekday, nth = self.weekday.weekday, self.weekday.n or 1
|
||||
jumpdays = (abs(nth)-1)*7
|
||||
if nth > 0:
|
||||
jumpdays += (7-ret.weekday()+weekday)%7
|
||||
else:
|
||||
jumpdays += (ret.weekday()-weekday)%7
|
||||
jumpdays *= -1
|
||||
ret += datetime.timedelta(days=jumpdays)
|
||||
return ret
|
||||
|
||||
def __rsub__(self, other):
|
||||
return self.__neg__().__radd__(other)
|
||||
|
||||
def __add__(self, other):
|
||||
if not isinstance(other, relativedelta):
|
||||
raise TypeError, "unsupported type for add operation"
|
||||
return relativedelta(years=other.years+self.years,
|
||||
months=other.months+self.months,
|
||||
days=other.days+self.days,
|
||||
hours=other.hours+self.hours,
|
||||
minutes=other.minutes+self.minutes,
|
||||
seconds=other.seconds+self.seconds,
|
||||
microseconds=other.microseconds+self.microseconds,
|
||||
leapdays=other.leapdays or self.leapdays,
|
||||
year=other.year or self.year,
|
||||
month=other.month or self.month,
|
||||
day=other.day or self.day,
|
||||
weekday=other.weekday or self.weekday,
|
||||
hour=other.hour or self.hour,
|
||||
minute=other.minute or self.minute,
|
||||
second=other.second or self.second,
|
||||
microsecond=other.second or self.microsecond)
|
||||
|
||||
def __sub__(self, other):
|
||||
if not isinstance(other, relativedelta):
|
||||
raise TypeError, "unsupported type for sub operation"
|
||||
return relativedelta(years=other.years-self.years,
|
||||
months=other.months-self.months,
|
||||
days=other.days-self.days,
|
||||
hours=other.hours-self.hours,
|
||||
minutes=other.minutes-self.minutes,
|
||||
seconds=other.seconds-self.seconds,
|
||||
microseconds=other.microseconds-self.microseconds,
|
||||
leapdays=other.leapdays or self.leapdays,
|
||||
year=other.year or self.year,
|
||||
month=other.month or self.month,
|
||||
day=other.day or self.day,
|
||||
weekday=other.weekday or self.weekday,
|
||||
hour=other.hour or self.hour,
|
||||
minute=other.minute or self.minute,
|
||||
second=other.second or self.second,
|
||||
microsecond=other.second or self.microsecond)
|
||||
|
||||
def __neg__(self):
|
||||
return relativedelta(years=-self.years,
|
||||
months=-self.months,
|
||||
days=-self.days,
|
||||
hours=-self.hours,
|
||||
minutes=-self.minutes,
|
||||
seconds=-self.seconds,
|
||||
microseconds=-self.microseconds,
|
||||
leapdays=self.leapdays,
|
||||
year=self.year,
|
||||
month=self.month,
|
||||
day=self.day,
|
||||
weekday=self.weekday,
|
||||
hour=self.hour,
|
||||
minute=self.minute,
|
||||
second=self.second,
|
||||
microsecond=self.microsecond)
|
||||
|
||||
def __nonzero__(self):
|
||||
return not (not self.years and
|
||||
not self.months and
|
||||
not self.days and
|
||||
not self.hours and
|
||||
not self.minutes and
|
||||
not self.seconds and
|
||||
not self.microseconds and
|
||||
not self.leapdays and
|
||||
self.year is None and
|
||||
self.month is None and
|
||||
self.day is None and
|
||||
self.weekday is None and
|
||||
self.hour is None and
|
||||
self.minute is None and
|
||||
self.second is None and
|
||||
self.microsecond is None)
|
||||
|
||||
def __mul__(self, other):
|
||||
f = float(other)
|
||||
return relativedelta(years=self.years*f,
|
||||
months=self.months*f,
|
||||
days=self.days*f,
|
||||
hours=self.hours*f,
|
||||
minutes=self.minutes*f,
|
||||
seconds=self.seconds*f,
|
||||
microseconds=self.microseconds*f,
|
||||
leapdays=self.leapdays,
|
||||
year=self.year,
|
||||
month=self.month,
|
||||
day=self.day,
|
||||
weekday=self.weekday,
|
||||
hour=self.hour,
|
||||
minute=self.minute,
|
||||
second=self.second,
|
||||
microsecond=self.microsecond)
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, relativedelta):
|
||||
return False
|
||||
if self.weekday or other.weekday:
|
||||
if not self.weekday or not other.weekday:
|
||||
return False
|
||||
if self.weekday.weekday != other.weekday.weekday:
|
||||
return False
|
||||
n1, n2 = self.weekday.n, other.weekday.n
|
||||
if n1 != n2 and not (n1 in (0, 1) and n2 in (0, 1)):
|
||||
return False
|
||||
return (self.years == other.years and
|
||||
self.months == other.months and
|
||||
self.days == other.days and
|
||||
self.hours == other.hours and
|
||||
self.minutes == other.minutes and
|
||||
self.seconds == other.seconds and
|
||||
self.leapdays == other.leapdays and
|
||||
self.year == other.year and
|
||||
self.month == other.month and
|
||||
self.day == other.day and
|
||||
self.hour == other.hour and
|
||||
self.minute == other.minute and
|
||||
self.second == other.second and
|
||||
self.microsecond == other.microsecond)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __div__(self, other):
|
||||
return self.__mul__(1/float(other))
|
||||
|
||||
def __repr__(self):
|
||||
l = []
|
||||
for attr in ["years", "months", "days", "leapdays",
|
||||
"hours", "minutes", "seconds", "microseconds"]:
|
||||
value = getattr(self, attr)
|
||||
if value:
|
||||
l.append("%s=%+d" % (attr, value))
|
||||
for attr in ["year", "month", "day", "weekday",
|
||||
"hour", "minute", "second", "microsecond"]:
|
||||
value = getattr(self, attr)
|
||||
if value is not None:
|
||||
l.append("%s=%s" % (attr, `value`))
|
||||
return "%s(%s)" % (self.__class__.__name__, ", ".join(l))
|
||||
|
||||
# vim:ts=4:sw=4:et
|
@ -1,883 +0,0 @@
|
||||
"""
|
||||
Copyright (c) 2003 Gustavo Niemeyer <niemeyer@conectiva.com>
|
||||
|
||||
This module offers extensions to the standard python 2.3+
|
||||
datetime module.
|
||||
"""
|
||||
__author__ = "Gustavo Niemeyer <niemeyer@conectiva.com>"
|
||||
__license__ = "PSF License"
|
||||
|
||||
import datetime
|
||||
import struct
|
||||
import time
|
||||
|
||||
relativedelta = None
|
||||
parser = None
|
||||
rrule = None
|
||||
|
||||
__all__ = ["tzutc", "tzoffset", "tzlocal", "tzfile",
|
||||
"tzrange", "tzstr", "tzical", "gettz"]
|
||||
|
||||
ZERO = datetime.timedelta(0)
|
||||
EPOCHORDINAL = datetime.datetime.utcfromtimestamp(0).toordinal()
|
||||
|
||||
class tzutc(datetime.tzinfo):
|
||||
|
||||
def utcoffset(self, dt):
|
||||
return ZERO
|
||||
|
||||
def dst(self, dt):
|
||||
return ZERO
|
||||
|
||||
def tzname(self, dt):
|
||||
return "UTC"
|
||||
|
||||
def __eq__(self, other):
|
||||
return (isinstance(other, tzutc) or
|
||||
(isinstance(other, tzoffset) and other._offset == ZERO))
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __repr__(self):
|
||||
return "%s()" % self.__class__.__name__
|
||||
|
||||
class tzoffset(datetime.tzinfo):
|
||||
|
||||
def __init__(self, name, offset):
|
||||
self._name = name
|
||||
self._offset = datetime.timedelta(seconds=offset)
|
||||
|
||||
def utcoffset(self, dt):
|
||||
return self._offset
|
||||
|
||||
def dst(self, dt):
|
||||
return ZERO
|
||||
|
||||
def tzname(self, dt):
|
||||
return self._name
|
||||
|
||||
def __eq__(self, other):
|
||||
return (isinstance(other, tzoffset) and
|
||||
self._offset == other._offset)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __repr__(self):
|
||||
return "%s(%s, %s)" % (self.__class__.__name__,
|
||||
`self._name`,
|
||||
self._offset.days*86400+self._offset.seconds)
|
||||
|
||||
class tzlocal(datetime.tzinfo):
|
||||
|
||||
_std_offset = datetime.timedelta(seconds=-time.timezone)
|
||||
if time.daylight:
|
||||
_dst_offset = datetime.timedelta(seconds=-time.altzone)
|
||||
else:
|
||||
_dst_offset = _std_offset
|
||||
|
||||
def utcoffset(self, dt):
|
||||
if self._isdst(dt):
|
||||
return self._dst_offset
|
||||
else:
|
||||
return self._std_offset
|
||||
|
||||
def dst(self, dt):
|
||||
if self._isdst(dt):
|
||||
return self._dst_offset-self._std_offset
|
||||
else:
|
||||
return ZERO
|
||||
|
||||
def tzname(self, dt):
|
||||
return time.tzname[self._isdst(dt)]
|
||||
|
||||
def _isdst(self, dt):
|
||||
# We can't use mktime here. It is unstable when deciding if
|
||||
# the hour near to a change is DST or not.
|
||||
#
|
||||
# timestamp = time.mktime((dt.year, dt.month, dt.day, dt.hour,
|
||||
# dt.minute, dt.second, dt.weekday(), 0, -1))
|
||||
# return time.localtime(timestamp).tm_isdst
|
||||
#
|
||||
# The code above yields the following result:
|
||||
#
|
||||
#>>> import tz, datetime
|
||||
#>>> t = tz.tzlocal()
|
||||
#>>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname()
|
||||
#'BRDT'
|
||||
#>>> datetime.datetime(2003,2,16,0,tzinfo=t).tzname()
|
||||
#'BRST'
|
||||
#>>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname()
|
||||
#'BRST'
|
||||
#>>> datetime.datetime(2003,2,15,22,tzinfo=t).tzname()
|
||||
#'BRDT'
|
||||
#>>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname()
|
||||
#'BRDT'
|
||||
#
|
||||
# Here is a more stable implementation:
|
||||
#
|
||||
timestamp = ((dt.toordinal() - EPOCHORDINAL) * 86400
|
||||
+ dt.hour * 3600
|
||||
+ dt.minute * 60
|
||||
+ dt.second)
|
||||
return time.localtime(timestamp+time.timezone).tm_isdst
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, tzlocal):
|
||||
return False
|
||||
return (self._std_offset == other._std_offset and
|
||||
self._dst_offset == other._dst_offset)
|
||||
return True
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __repr__(self):
|
||||
return "%s()" % self.__class__.__name__
|
||||
|
||||
class _ttinfo(object):
|
||||
__slots__ = ["offset", "delta", "isdst", "abbr", "isstd", "isgmt"]
|
||||
|
||||
def __init__(self):
|
||||
for attr in self.__slots__:
|
||||
setattr(self, attr, None)
|
||||
|
||||
def __repr__(self):
|
||||
l = []
|
||||
for attr in self.__slots__:
|
||||
value = getattr(self, attr)
|
||||
if value is not None:
|
||||
l.append("%s=%s" % (attr, `value`))
|
||||
return "%s(%s)" % (self.__class__.__name__, ", ".join(l))
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, _ttinfo):
|
||||
return False
|
||||
return (self.offset == other.offset and
|
||||
self.delta == other.delta and
|
||||
self.isdst == other.isdst and
|
||||
self.abbr == other.abbr and
|
||||
self.isstd == other.isstd and
|
||||
self.isgmt == other.isgmt)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
class tzfile(datetime.tzinfo):
|
||||
|
||||
# http://www.twinsun.com/tz/tz-link.htm
|
||||
# ftp://elsie.nci.nih.gov/pub/tz*.tar.gz
|
||||
|
||||
def __init__(self, fileobj):
|
||||
if isinstance(fileobj, basestring):
|
||||
self._s = fileobj
|
||||
fileobj = open(fileobj)
|
||||
elif hasattr(fileobj, "name"):
|
||||
self._s = fileobj.name
|
||||
else:
|
||||
self._s = `fileobj`
|
||||
|
||||
# From tzfile(5):
|
||||
#
|
||||
# The time zone information files used by tzset(3)
|
||||
# begin with the magic characters "TZif" to identify
|
||||
# them as time zone information files, followed by
|
||||
# sixteen bytes reserved for future use, followed by
|
||||
# six four-byte values of type long, written in a
|
||||
# ``standard'' byte order (the high-order byte
|
||||
# of the value is written first).
|
||||
|
||||
if fileobj.read(4) != "TZif":
|
||||
raise ValueError, "magic not found"
|
||||
|
||||
fileobj.read(16)
|
||||
|
||||
(
|
||||
# The number of UTC/local indicators stored in the file.
|
||||
ttisgmtcnt,
|
||||
|
||||
# The number of standard/wall indicators stored in the file.
|
||||
ttisstdcnt,
|
||||
|
||||
# The number of leap seconds for which data is
|
||||
# stored in the file.
|
||||
leapcnt,
|
||||
|
||||
# The number of "transition times" for which data
|
||||
# is stored in the file.
|
||||
timecnt,
|
||||
|
||||
# The number of "local time types" for which data
|
||||
# is stored in the file (must not be zero).
|
||||
typecnt,
|
||||
|
||||
# The number of characters of "time zone
|
||||
# abbreviation strings" stored in the file.
|
||||
charcnt,
|
||||
|
||||
) = struct.unpack(">6l", fileobj.read(24))
|
||||
|
||||
# The above header is followed by tzh_timecnt four-byte
|
||||
# values of type long, sorted in ascending order.
|
||||
# These values are written in ``standard'' byte order.
|
||||
# Each is used as a transition time (as returned by
|
||||
# time(2)) at which the rules for computing local time
|
||||
# change.
|
||||
|
||||
if timecnt:
|
||||
self._trans_list = struct.unpack(">%dl" % timecnt,
|
||||
fileobj.read(timecnt*4))
|
||||
else:
|
||||
self._trans_list = []
|
||||
|
||||
# Next come tzh_timecnt one-byte values of type unsigned
|
||||
# char; each one tells which of the different types of
|
||||
# ``local time'' types described in the file is associated
|
||||
# with the same-indexed transition time. These values
|
||||
# serve as indices into an array of ttinfo structures that
|
||||
# appears next in the file.
|
||||
|
||||
if timecnt:
|
||||
self._trans_idx = struct.unpack(">%dB" % timecnt,
|
||||
fileobj.read(timecnt))
|
||||
else:
|
||||
self._trans_idx = []
|
||||
|
||||
# Each ttinfo structure is written as a four-byte value
|
||||
# for tt_gmtoff of type long, in a standard byte
|
||||
# order, followed by a one-byte value for tt_isdst
|
||||
# and a one-byte value for tt_abbrind. In each
|
||||
# structure, tt_gmtoff gives the number of
|
||||
# seconds to be added to UTC, tt_isdst tells whether
|
||||
# tm_isdst should be set by localtime(3), and
|
||||
# tt_abbrind serves as an index into the array of
|
||||
# time zone abbreviation characters that follow the
|
||||
# ttinfo structure(s) in the file.
|
||||
|
||||
ttinfo = []
|
||||
|
||||
for i in range(typecnt):
|
||||
ttinfo.append(struct.unpack(">lbb", fileobj.read(6)))
|
||||
|
||||
abbr = fileobj.read(charcnt)
|
||||
|
||||
# Then there are tzh_leapcnt pairs of four-byte
|
||||
# values, written in standard byte order; the
|
||||
# first value of each pair gives the time (as
|
||||
# returned by time(2)) at which a leap second
|
||||
# occurs; the second gives the total number of
|
||||
# leap seconds to be applied after the given time.
|
||||
# The pairs of values are sorted in ascending order
|
||||
# by time.
|
||||
|
||||
# Not used, for now
|
||||
if leapcnt:
|
||||
leap = struct.unpack(">%dl" % leapcnt*2,
|
||||
fileobj.read(leapcnt*8))
|
||||
|
||||
# Then there are tzh_ttisstdcnt standard/wall
|
||||
# indicators, each stored as a one-byte value;
|
||||
# they tell whether the transition times associated
|
||||
# with local time types were specified as standard
|
||||
# time or wall clock time, and are used when
|
||||
# a time zone file is used in handling POSIX-style
|
||||
# time zone environment variables.
|
||||
|
||||
if ttisstdcnt:
|
||||
isstd = struct.unpack(">%db" % ttisstdcnt,
|
||||
fileobj.read(ttisstdcnt))
|
||||
|
||||
# Finally, there are tzh_ttisgmtcnt UTC/local
|
||||
# indicators, each stored as a one-byte value;
|
||||
# they tell whether the transition times associated
|
||||
# with local time types were specified as UTC or
|
||||
# local time, and are used when a time zone file
|
||||
# is used in handling POSIX-style time zone envi-
|
||||
# ronment variables.
|
||||
|
||||
if ttisgmtcnt:
|
||||
isgmt = struct.unpack(">%db" % ttisgmtcnt,
|
||||
fileobj.read(ttisgmtcnt))
|
||||
|
||||
# ** Everything has been read **
|
||||
|
||||
# Build ttinfo list
|
||||
self._ttinfo_list = []
|
||||
for i in range(typecnt):
|
||||
tti = _ttinfo()
|
||||
tti.offset = ttinfo[i][0]
|
||||
tti.delta = datetime.timedelta(seconds=ttinfo[i][0])
|
||||
tti.isdst = ttinfo[i][1]
|
||||
tti.abbr = abbr[ttinfo[i][2]:abbr.find('\x00', ttinfo[i][2])]
|
||||
tti.isstd = (ttisstdcnt > i and isstd[i] != 0)
|
||||
tti.isgmt = (ttisgmtcnt > i and isgmt[i] != 0)
|
||||
self._ttinfo_list.append(tti)
|
||||
|
||||
# Replace ttinfo indexes for ttinfo objects.
|
||||
trans_idx = []
|
||||
for idx in self._trans_idx:
|
||||
trans_idx.append(self._ttinfo_list[idx])
|
||||
self._trans_idx = tuple(trans_idx)
|
||||
|
||||
# Set standard, dst, and before ttinfos. before will be
|
||||
# used when a given time is before any transitions,
|
||||
# and will be set to the first non-dst ttinfo, or to
|
||||
# the first dst, if all of them are dst.
|
||||
self._ttinfo_std = None
|
||||
self._ttinfo_dst = None
|
||||
self._ttinfo_before = None
|
||||
if self._ttinfo_list:
|
||||
if not self._trans_list:
|
||||
self._ttinfo_std = self._ttinfo_first = self._ttinfo_list[0]
|
||||
else:
|
||||
for i in range(timecnt-1,-1,-1):
|
||||
tti = self._trans_idx[i]
|
||||
if not self._ttinfo_std and not tti.isdst:
|
||||
self._ttinfo_std = tti
|
||||
elif not self._ttinfo_dst and tti.isdst:
|
||||
self._ttinfo_dst = tti
|
||||
if self._ttinfo_std and self._ttinfo_dst:
|
||||
break
|
||||
else:
|
||||
if self._ttinfo_dst and not self._ttinfo_std:
|
||||
self._ttinfo_std = self._ttinfo_dst
|
||||
|
||||
for tti in self._ttinfo_list:
|
||||
if not tti.isdst:
|
||||
self._ttinfo_before = tti
|
||||
break
|
||||
else:
|
||||
self._ttinfo_before = self._ttinfo_list[0]
|
||||
|
||||
# Now fix transition times to become relative to wall time.
|
||||
#
|
||||
# I'm not sure about this. In my tests, the tz source file
|
||||
# is setup to wall time, and in the binary file isstd and
|
||||
# isgmt are off, so it should be in wall time. OTOH, it's
|
||||
# always in gmt time. Let me know if you have comments
|
||||
# about this.
|
||||
laststdoffset = 0
|
||||
self._trans_list = list(self._trans_list)
|
||||
for i in range(len(self._trans_list)):
|
||||
tti = self._trans_idx[i]
|
||||
if not tti.isdst:
|
||||
# This is std time.
|
||||
self._trans_list[i] += tti.offset
|
||||
laststdoffset = tti.offset
|
||||
else:
|
||||
# This is dst time. Convert to std.
|
||||
self._trans_list[i] += laststdoffset
|
||||
self._trans_list = tuple(self._trans_list)
|
||||
|
||||
def _find_ttinfo(self, dt, laststd=0):
|
||||
timestamp = ((dt.toordinal() - EPOCHORDINAL) * 86400
|
||||
+ dt.hour * 3600
|
||||
+ dt.minute * 60
|
||||
+ dt.second)
|
||||
idx = 0
|
||||
for trans in self._trans_list:
|
||||
if timestamp < trans:
|
||||
break
|
||||
idx += 1
|
||||
else:
|
||||
return self._ttinfo_std
|
||||
if idx == 0:
|
||||
return self._ttinfo_before
|
||||
if laststd:
|
||||
while idx > 0:
|
||||
tti = self._trans_idx[idx-1]
|
||||
if not tti.isdst:
|
||||
return tti
|
||||
idx -= 1
|
||||
else:
|
||||
return self._ttinfo_std
|
||||
else:
|
||||
return self._trans_idx[idx-1]
|
||||
|
||||
def utcoffset(self, dt):
|
||||
if not self._ttinfo_std:
|
||||
return ZERO
|
||||
return self._find_ttinfo(dt).delta
|
||||
|
||||
def dst(self, dt):
|
||||
if not self._ttinfo_dst:
|
||||
return ZERO
|
||||
tti = self._find_ttinfo(dt)
|
||||
if not tti.isdst:
|
||||
return ZERO
|
||||
|
||||
# The documentation says that utcoffset()-dst() must
|
||||
# be constant for every dt.
|
||||
return self._find_ttinfo(dt, laststd=1).delta-tti.delta
|
||||
|
||||
# An alternative for that would be:
|
||||
#
|
||||
# return self._ttinfo_dst.offset-self._ttinfo_std.offset
|
||||
#
|
||||
# However, this class stores historical changes in the
|
||||
# dst offset, so I belive that this wouldn't be the right
|
||||
# way to implement this.
|
||||
|
||||
def tzname(self, dt):
|
||||
if not self._ttinfo_std:
|
||||
return None
|
||||
return self._find_ttinfo(dt).abbr
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, tzfile):
|
||||
return False
|
||||
return (self._trans_list == other._trans_list and
|
||||
self._trans_idx == other._trans_idx and
|
||||
self._ttinfo_list == other._ttinfo_list)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
return "%s(%s)" % (self.__class__.__name__, `self._s`)
|
||||
|
||||
class tzrange(datetime.tzinfo):
|
||||
|
||||
def __init__(self, stdabbr, stdoffset=None,
|
||||
dstabbr=None, dstoffset=None,
|
||||
start=None, end=None):
|
||||
global relativedelta
|
||||
if not relativedelta:
|
||||
from dateutil import relativedelta
|
||||
self._std_abbr = stdabbr
|
||||
self._dst_abbr = dstabbr
|
||||
if stdoffset is not None:
|
||||
self._std_offset = datetime.timedelta(seconds=stdoffset)
|
||||
else:
|
||||
self._std_offset = ZERO
|
||||
if dstoffset is not None:
|
||||
self._dst_offset = datetime.timedelta(seconds=dstoffset)
|
||||
elif dstabbr and stdoffset is not None:
|
||||
self._dst_offset = self._std_offset+datetime.timedelta(hours=+1)
|
||||
else:
|
||||
self._dst_offset = ZERO
|
||||
if start is None:
|
||||
self._start_delta = relativedelta.relativedelta(
|
||||
hours=+2, month=4, day=1, weekday=relativedelta.SU(+1))
|
||||
else:
|
||||
self._start_delta = start
|
||||
if end is None:
|
||||
self._end_delta = relativedelta.relativedelta(
|
||||
hours=+1, month=10, day=31, weekday=relativedelta.SU(-1))
|
||||
else:
|
||||
self._end_delta = end
|
||||
|
||||
def utcoffset(self, dt):
|
||||
if self._isdst(dt):
|
||||
return self._dst_offset
|
||||
else:
|
||||
return self._std_offset
|
||||
|
||||
def dst(self, dt):
|
||||
if self._isdst(dt):
|
||||
return self._dst_offset-self._std_offset
|
||||
else:
|
||||
return ZERO
|
||||
|
||||
def tzname(self, dt):
|
||||
if self._isdst(dt):
|
||||
return self._dst_abbr
|
||||
else:
|
||||
return self._std_abbr
|
||||
|
||||
def _isdst(self, dt):
|
||||
if not self._start_delta:
|
||||
return False
|
||||
year = datetime.date(dt.year,1,1)
|
||||
start = year+self._start_delta
|
||||
end = year+self._end_delta
|
||||
dt = dt.replace(tzinfo=None)
|
||||
if start < end:
|
||||
return dt >= start and dt < end
|
||||
else:
|
||||
return dt >= start or dt < end
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, tzrange):
|
||||
return False
|
||||
return (self._std_abbr == other._std_abbr and
|
||||
self._dst_abbr == other._dst_abbr and
|
||||
self._std_offset == other._std_offset and
|
||||
self._dst_offset == other._dst_offset and
|
||||
self._start_delta == other._start_delta and
|
||||
self._end_delta == other._end_delta)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __repr__(self):
|
||||
return "%s(...)" % self.__class__.__name__
|
||||
|
||||
|
||||
class tzstr(tzrange):
|
||||
|
||||
def __init__(self, s):
|
||||
global parser
|
||||
if not parser:
|
||||
from dateutil import parser
|
||||
self._s = s
|
||||
|
||||
res = parser._parsetz(s)
|
||||
if res is None:
|
||||
raise ValueError, "unknown string format"
|
||||
|
||||
# We must initialize it first, since _delta() needs
|
||||
# _std_offset and _dst_offset set. Use False in start/end
|
||||
# to avoid building it two times.
|
||||
tzrange.__init__(self, res.stdabbr, res.stdoffset,
|
||||
res.dstabbr, res.dstoffset,
|
||||
start=False, end=False)
|
||||
|
||||
self._start_delta = self._delta(res.start)
|
||||
if self._start_delta:
|
||||
self._end_delta = self._delta(res.end, isend=1)
|
||||
|
||||
def _delta(self, x, isend=0):
|
||||
kwargs = {}
|
||||
if x.month is not None:
|
||||
kwargs["month"] = x.month
|
||||
if x.weekday is not None:
|
||||
kwargs["weekday"] = relativedelta.weekday(x.weekday, x.week)
|
||||
if x.week > 0:
|
||||
kwargs["day"] = 1
|
||||
else:
|
||||
kwargs["day"] = 31
|
||||
elif x.day:
|
||||
kwargs["day"] = x.day
|
||||
elif x.yday is not None:
|
||||
kwargs["yearday"] = x.yday
|
||||
elif x.jyday is not None:
|
||||
kwargs["nlyearday"] = x.jyday
|
||||
if not kwargs:
|
||||
# Default is to start on first sunday of april, and end
|
||||
# on last sunday of october.
|
||||
if not isend:
|
||||
kwargs["month"] = 4
|
||||
kwargs["day"] = 1
|
||||
kwargs["weekday"] = relativedelta.SU(+1)
|
||||
else:
|
||||
kwargs["month"] = 10
|
||||
kwargs["day"] = 31
|
||||
kwargs["weekday"] = relativedelta.SU(-1)
|
||||
if x.time is not None:
|
||||
kwargs["seconds"] = x.time
|
||||
else:
|
||||
# Default is 2AM.
|
||||
kwargs["seconds"] = 7200
|
||||
if isend:
|
||||
# Convert to standard time, to follow the documented way
|
||||
# of working with the extra hour. See the documentation
|
||||
# of the tzinfo class.
|
||||
delta = self._dst_offset-self._std_offset
|
||||
kwargs["seconds"] -= delta.seconds+delta.days*86400
|
||||
return relativedelta.relativedelta(**kwargs)
|
||||
|
||||
def __repr__(self):
|
||||
return "%s(%s)" % (self.__class__.__name__, `self._s`)
|
||||
|
||||
class _tzicalvtzcomp:
|
||||
def __init__(self, tzoffsetfrom, tzoffsetto, isdst,
|
||||
tzname=None, rrule=None):
|
||||
self.tzoffsetfrom = datetime.timedelta(seconds=tzoffsetfrom)
|
||||
self.tzoffsetto = datetime.timedelta(seconds=tzoffsetto)
|
||||
self.tzoffsetdiff = self.tzoffsetto-self.tzoffsetfrom
|
||||
self.isdst = isdst
|
||||
self.tzname = tzname
|
||||
self.rrule = rrule
|
||||
|
||||
class _tzicalvtz(datetime.tzinfo):
|
||||
def __init__(self, tzid, comps=[]):
|
||||
self._tzid = tzid
|
||||
self._comps = comps
|
||||
self._cachedate = []
|
||||
self._cachecomp = []
|
||||
|
||||
def _find_comp(self, dt):
|
||||
if len(self._comps) == 1:
|
||||
return self._comps[0]
|
||||
dt = dt.replace(tzinfo=None)
|
||||
try:
|
||||
return self._cachecomp[self._cachedate.index(dt)]
|
||||
except ValueError:
|
||||
pass
|
||||
lastcomp = None
|
||||
lastcompdt = None
|
||||
for comp in self._comps:
|
||||
if not comp.isdst:
|
||||
# Handle the extra hour in DST -> STD
|
||||
compdt = comp.rrule.before(dt-comp.tzoffsetdiff, inc=True)
|
||||
else:
|
||||
compdt = comp.rrule.before(dt, inc=True)
|
||||
if compdt and (not lastcompdt or lastcompdt < compdt):
|
||||
lastcompdt = compdt
|
||||
lastcomp = comp
|
||||
if not lastcomp:
|
||||
# RFC says nothing about what to do when a given
|
||||
# time is before the first onset date. We'll look for the
|
||||
# first standard component, or the first component, if
|
||||
# none is found.
|
||||
for comp in self._comps:
|
||||
if not comp.isdst:
|
||||
lastcomp = comp
|
||||
break
|
||||
else:
|
||||
lastcomp = comp[0]
|
||||
self._cachedate.insert(0, dt)
|
||||
self._cachecomp.insert(0, lastcomp)
|
||||
if len(self._cachedate) > 10:
|
||||
self._cachedate.pop()
|
||||
self._cachecomp.pop()
|
||||
return lastcomp
|
||||
|
||||
def utcoffset(self, dt):
|
||||
return self._find_comp(dt).tzoffsetto
|
||||
|
||||
def dst(self, dt):
|
||||
comp = self._find_comp(dt)
|
||||
if comp.isdst:
|
||||
return comp.tzoffsetdiff
|
||||
else:
|
||||
return ZERO
|
||||
|
||||
def tzname(self, dt):
|
||||
return self._find_comp(dt).tzname
|
||||
|
||||
def __repr__(self):
|
||||
return "<tzicalvtz %s>" % `self._tzid`
|
||||
|
||||
class tzical:
|
||||
def __init__(self, fileobj):
|
||||
global rrule
|
||||
if not rrule:
|
||||
from dateutil import rrule
|
||||
|
||||
if isinstance(fileobj, basestring):
|
||||
self._s = fileobj
|
||||
fileobj = open(fileobj)
|
||||
elif hasattr(fileobj, "name"):
|
||||
self._s = fileobj.name
|
||||
else:
|
||||
self._s = `fileobj`
|
||||
|
||||
self._vtz = {}
|
||||
|
||||
self._parse_rfc(fileobj.read())
|
||||
|
||||
def keys(self):
|
||||
return self._vtz.keys()
|
||||
|
||||
def get(self, tzid=None):
|
||||
if tzid is None:
|
||||
keys = self._vtz.keys()
|
||||
if len(keys) == 0:
|
||||
raise "no timezones defined"
|
||||
elif len(keys) > 1:
|
||||
raise "more than one timezone available"
|
||||
tzid = keys[0]
|
||||
return self._vtz.get(tzid)
|
||||
|
||||
def _parse_offset(self, s):
|
||||
s = s.strip()
|
||||
if not s:
|
||||
raise ValueError, "empty offset"
|
||||
if s[0] in ('+', '-'):
|
||||
signal = (-1,+1)[s[0]=='+']
|
||||
s = s[1:]
|
||||
else:
|
||||
signal = +1
|
||||
if len(s) == 4:
|
||||
return (int(s[:2])*3600+int(s[2:])*60)*signal
|
||||
elif len(s) == 6:
|
||||
return (int(s[:2])*3600+int(s[2:4])*60+int(s[4:]))*signal
|
||||
else:
|
||||
raise ValueError, "invalid offset: "+s
|
||||
|
||||
def _parse_rfc(self, s):
|
||||
lines = s.splitlines()
|
||||
if not lines:
|
||||
raise ValueError, "empty string"
|
||||
|
||||
# Unfold
|
||||
i = 0
|
||||
while i < len(lines):
|
||||
line = lines[i].rstrip()
|
||||
if not line:
|
||||
del lines[i]
|
||||
elif i > 0 and line[0] == " ":
|
||||
lines[i-1] += line[1:]
|
||||
del lines[i]
|
||||
else:
|
||||
i += 1
|
||||
|
||||
invtz = False
|
||||
comptype = None
|
||||
for line in lines:
|
||||
if not line:
|
||||
continue
|
||||
name, value = line.split(':', 1)
|
||||
parms = name.split(';')
|
||||
if not parms:
|
||||
raise ValueError, "empty property name"
|
||||
name = parms[0].upper()
|
||||
parms = parms[1:]
|
||||
if invtz:
|
||||
if name == "BEGIN":
|
||||
if value in ("STANDARD", "DAYLIGHT"):
|
||||
# Process component
|
||||
pass
|
||||
else:
|
||||
raise ValueError, "unknown component: "+value
|
||||
comptype = value
|
||||
founddtstart = False
|
||||
tzoffsetfrom = None
|
||||
tzoffsetto = None
|
||||
rrulelines = []
|
||||
tzname = None
|
||||
elif name == "END":
|
||||
if value == "VTIMEZONE":
|
||||
if comptype:
|
||||
raise ValueError, \
|
||||
"component not closed: "+comptype
|
||||
if not tzid:
|
||||
raise ValueError, \
|
||||
"mandatory TZID not found"
|
||||
if not comps:
|
||||
raise ValueError, \
|
||||
"at least one component is needed"
|
||||
# Process vtimezone
|
||||
self._vtz[tzid] = _tzicalvtz(tzid, comps)
|
||||
invtz = False
|
||||
elif value == comptype:
|
||||
if not founddtstart:
|
||||
raise ValueError, \
|
||||
"mandatory DTSTART not found"
|
||||
if not tzoffsetfrom:
|
||||
raise ValueError, \
|
||||
"mandatory TZOFFSETFROM not found"
|
||||
if not tzoffsetto:
|
||||
raise ValueError, \
|
||||
"mandatory TZOFFSETFROM not found"
|
||||
# Process component
|
||||
rr = None
|
||||
if rrulelines:
|
||||
rr = rrule.rrulestr("\n".join(rrulelines),
|
||||
compatible=True,
|
||||
ignoretz=True,
|
||||
cache=True)
|
||||
comp = _tzicalvtzcomp(tzoffsetfrom, tzoffsetto,
|
||||
(comptype == "DAYLIGHT"),
|
||||
tzname, rr)
|
||||
comps.append(comp)
|
||||
comptype = None
|
||||
else:
|
||||
raise ValueError, \
|
||||
"invalid component end: "+value
|
||||
elif comptype:
|
||||
if name == "DTSTART":
|
||||
rrulelines.append(line)
|
||||
founddtstart = True
|
||||
elif name in ("RRULE", "RDATE", "EXRULE", "EXDATE"):
|
||||
rrulelines.append(line)
|
||||
elif name == "TZOFFSETFROM":
|
||||
if parms:
|
||||
raise ValueError, \
|
||||
"unsupported %s parm: %s "%(name, parms[0])
|
||||
tzoffsetfrom = self._parse_offset(value)
|
||||
elif name == "TZOFFSETTO":
|
||||
if parms:
|
||||
raise ValueError, \
|
||||
"unsupported TZOFFSETTO parm: "+parms[0]
|
||||
tzoffsetto = self._parse_offset(value)
|
||||
elif name == "TZNAME":
|
||||
if parms:
|
||||
raise ValueError, \
|
||||
"unsupported TZNAME parm: "+parms[0]
|
||||
tzname = value
|
||||
elif name == "COMMENT":
|
||||
pass
|
||||
else:
|
||||
raise ValueError, "unsupported property: "+name
|
||||
else:
|
||||
if name == "TZID":
|
||||
if parms:
|
||||
raise ValueError, \
|
||||
"unsupported TZID parm: "+parms[0]
|
||||
tzid = value
|
||||
elif name in ("TZURL", "LAST-MODIFIED", "COMMENT"):
|
||||
pass
|
||||
else:
|
||||
raise ValueError, "unsupported property: "+name
|
||||
elif name == "BEGIN" and value == "VTIMEZONE":
|
||||
tzid = None
|
||||
comps = []
|
||||
invtz = True
|
||||
|
||||
def __repr__(self):
|
||||
return "%s(%s)" % (self.__class__.__name__, `self._s`)
|
||||
|
||||
TZFILES = ["/etc/localtime", "localtime"]
|
||||
TZPATHS = ["/usr/share/zoneinfo", "/usr/lib/zoneinfo", "/etc/zoneinfo"]
|
||||
|
||||
import os
|
||||
|
||||
def gettz(name=None):
|
||||
tz = None
|
||||
if not name:
|
||||
try:
|
||||
name = os.environ["TZ"]
|
||||
except KeyError:
|
||||
pass
|
||||
if name is None:
|
||||
for filepath in TZFILES:
|
||||
if not os.path.isabs(filepath):
|
||||
filename = filepath
|
||||
for path in TZPATHS:
|
||||
filepath = os.path.join(path, filename)
|
||||
if os.path.isfile(filepath):
|
||||
break
|
||||
else:
|
||||
continue
|
||||
if os.path.isfile(filepath):
|
||||
try:
|
||||
tz = tzfile(filepath)
|
||||
break
|
||||
except (IOError, OSError, ValueError):
|
||||
pass
|
||||
else:
|
||||
if name and name[0] == ":":
|
||||
name = name[:-1]
|
||||
for path in TZPATHS:
|
||||
filepath = os.path.join(path, name)
|
||||
if not os.path.isfile(filepath):
|
||||
filepath = filepath.replace(' ','_')
|
||||
if not os.path.isfile(filepath):
|
||||
continue
|
||||
try:
|
||||
tz = tzfile(filepath)
|
||||
break
|
||||
except (IOError, OSError, ValueError):
|
||||
pass
|
||||
else:
|
||||
for c in name:
|
||||
# name must have at least one offset to be a tzstr
|
||||
if c in "0123456789":
|
||||
try:
|
||||
tz = tzstr(name)
|
||||
except ValueError:
|
||||
pass
|
||||
break
|
||||
else:
|
||||
if name in ("GMT", "UTC"):
|
||||
tz = tzutc()
|
||||
elif name in time.tzname:
|
||||
tz = tzlocal()
|
||||
return tz
|
||||
|
||||
# vim:ts=4:sw=4:et
|
@ -1,323 +0,0 @@
|
||||
# Client for the DICT protocol (RFC2229)
|
||||
#
|
||||
# Copyright (C) 2002 John Goerzen
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
# Retrieved from: http://gopher.quux.org:80/devel
|
||||
|
||||
import socket, re
|
||||
|
||||
version = '1.0'
|
||||
|
||||
def dequote(s):
|
||||
"""Will remove single or double quotes from the start and end of a string
|
||||
and return the result."""
|
||||
quotechars = "'\""
|
||||
while len(s) and s[0] in quotechars:
|
||||
s = s[1:]
|
||||
while len(s) and s[-1] in quotechars:
|
||||
s = s[0:-1]
|
||||
return s
|
||||
|
||||
def enquote(s):
|
||||
"""This function will put a string in double quotes, properly
|
||||
escaping any existing double quotes with a backslash. It will
|
||||
return the result."""
|
||||
return '"%s"' % s.replace('"', "\\\"")
|
||||
|
||||
class Connection:
|
||||
"""This class is used to establish a connection to a database server.
|
||||
You will usually use this as the first call into the dictclient library.
|
||||
Instantiating it takes two optional arguments: a hostname (a string)
|
||||
and a port (an int). The hostname defaults to localhost
|
||||
and the port to 2628, the port specified in RFC."""
|
||||
def __init__(self, hostname='localhost', port=2628):
|
||||
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.sock.connect((hostname, port))
|
||||
self.rfile = self.sock.makefile("rt")
|
||||
self.wfile = self.sock.makefile("wt", 0)
|
||||
self.saveconnectioninfo()
|
||||
|
||||
def getresultcode(self):
|
||||
"""Generic function to get a result code. It will return a list
|
||||
consisting of two items: the integer result code and the text
|
||||
following. You will not usually use this function directly."""
|
||||
line = self.rfile.readline().strip()
|
||||
code, text = line.split(' ', 1)
|
||||
return [int(code), text]
|
||||
|
||||
def get200result(self):
|
||||
"""Used when expecting a single line of text -- a 200-class
|
||||
result. Returns [intcode, remaindertext]"""
|
||||
|
||||
code, text = self.getresultcode()
|
||||
if code < 200 or code >= 300:
|
||||
raise Exception, "Got '%s' when 200-class response expected" % \
|
||||
line
|
||||
return [code, text]
|
||||
|
||||
def get100block(self):
|
||||
"""Used when expecting multiple lines of text -- gets the block
|
||||
part only. Does not get any codes or anything! Returns a string."""
|
||||
data = []
|
||||
while 1:
|
||||
line = self.rfile.readline().strip()
|
||||
if line == '.':
|
||||
break
|
||||
data.append(line)
|
||||
return "\n".join(data)
|
||||
|
||||
def get100result(self):
|
||||
"""Used when expecting multiple lines of text, terminated by a period
|
||||
and a 200 code. Returns: [initialcode, [bodytext_1lineperentry],
|
||||
finalcode]"""
|
||||
code, text = self.getresultcode()
|
||||
if code < 100 or code >= 200:
|
||||
raise Exception, "Got '%s' when 100-class response expected" % \
|
||||
code
|
||||
|
||||
bodylines = self.get100block().split("\n")
|
||||
|
||||
code2 = self.get200result()[0]
|
||||
return [code, bodylines, code2]
|
||||
|
||||
def get100dict(self):
|
||||
"""Used when expecting a dictionary of results. Will read from
|
||||
the initial 100 code, to a period and the 200 code."""
|
||||
dict = {}
|
||||
for line in self.get100result()[1]:
|
||||
key, val = line.split(' ', 1)
|
||||
dict[key] = dequote(val)
|
||||
return dict
|
||||
|
||||
def saveconnectioninfo(self):
|
||||
"""Called by __init__ to handle the initial connection. Will
|
||||
save off the capabilities and messageid."""
|
||||
code, string = self.get200result()
|
||||
assert code == 220
|
||||
m = re.search('<(.*)> (<.*>)$', string)
|
||||
assert m is not None
|
||||
capstr, msgid = m.groups()
|
||||
self.capabilities = capstr.split('.')
|
||||
self.messageid = msgid
|
||||
|
||||
def getcapabilities(self):
|
||||
"""Returns a list of the capabilities advertised by the server."""
|
||||
return self.capabilities
|
||||
|
||||
def getmessageid(self):
|
||||
"""Returns the message id, including angle brackets."""
|
||||
return self.messageid
|
||||
|
||||
def getdbdescs(self):
|
||||
"""Gets a dict of available databases. The key is the db name
|
||||
and the value is the db description. This command may generate
|
||||
network traffic!"""
|
||||
if hasattr(self, 'dbdescs'):
|
||||
return self.dbdescs
|
||||
|
||||
self.sendcommand("SHOW DB")
|
||||
self.dbdescs = self.get100dict()
|
||||
return self.dbdescs
|
||||
|
||||
def getstratdescs(self):
|
||||
"""Gets a dict of available strategies. The key is the strat
|
||||
name and the value is the strat description. This call may
|
||||
generate network traffic!"""
|
||||
if hasattr(self, 'stratdescs'):
|
||||
return self.stratdescs
|
||||
|
||||
self.sendcommand("SHOW STRAT")
|
||||
self.stratdescs = self.get100dict()
|
||||
return self.stratdescs
|
||||
|
||||
def getdbobj(self, dbname):
|
||||
"""Gets a Database object corresponding to the database name passed
|
||||
in. This function explicitly will *not* generate network traffic.
|
||||
If you have not yet run getdbdescs(), it will fail."""
|
||||
if not hasattr(self, 'dbobjs'):
|
||||
self.dbobjs = {}
|
||||
|
||||
if self.dbobjs.has_key(dbname):
|
||||
return self.dbobjs[dbname]
|
||||
|
||||
# We use self.dbdescs explicitly since we don't want to
|
||||
# generate net traffic with this request!
|
||||
|
||||
if dbname != '*' and dbname != '!' and \
|
||||
not dbname in self.dbdescs.keys():
|
||||
raise Exception, "Invalid database name '%s'" % dbname
|
||||
|
||||
self.dbobjs[dbname] = Database(self, dbname)
|
||||
return self.dbobjs[dbname]
|
||||
|
||||
def sendcommand(self, command):
|
||||
"""Takes a command, without a newline character, and sends it to
|
||||
the server."""
|
||||
self.wfile.write(command + "\n")
|
||||
|
||||
def define(self, database, word):
|
||||
"""Returns a list of Definition objects for each matching
|
||||
definition. Parameters are the database name and the word
|
||||
to look up. This is one of the main functions you will use
|
||||
to interact with the server. Returns a list of Definition
|
||||
objects. If there are no matches, an empty list is returned.
|
||||
|
||||
Note: database may be '*' which means to search all databases,
|
||||
or '!' which means to return matches from the first database that
|
||||
has a match."""
|
||||
self.getdbdescs() # Prime the cache
|
||||
|
||||
if database != '*' and database != '!' and \
|
||||
not database in self.getdbdescs():
|
||||
raise Exception, "Invalid database '%s' specified" % database
|
||||
|
||||
self.sendcommand("DEFINE " + enquote(database) + " " + enquote(word))
|
||||
code = self.getresultcode()[0]
|
||||
|
||||
retval = []
|
||||
|
||||
if code == 552:
|
||||
# No definitions.
|
||||
return []
|
||||
if code != 150:
|
||||
raise Exception, "Unknown code %d" % code
|
||||
|
||||
while 1:
|
||||
code, text = self.getresultcode()
|
||||
if code != 151:
|
||||
break
|
||||
|
||||
m = re.search('^"(.+)" (\S+)', text)
|
||||
assert m is not None
|
||||
resultword, resultdb = m.groups()
|
||||
defstr = self.get100block()
|
||||
retval.append(Definition(self, self.getdbobj(resultdb),
|
||||
resultword, defstr))
|
||||
return retval
|
||||
|
||||
def match(self, database, strategy, word):
|
||||
"""Gets matches for a query. Arguments are database name,
|
||||
the strategy (see available ones in getstratdescs()), and the
|
||||
pattern/word to look for. Returns a list of Definition objects.
|
||||
If there is no match, an empty list is returned.
|
||||
|
||||
Note: database may be '*' which means to search all databases,
|
||||
or '!' which means to return matches from the first database that
|
||||
has a match."""
|
||||
self.getstratdescs() # Prime the cache
|
||||
self.getdbdescs() # Prime the cache
|
||||
if not strategy in self.getstratdescs().keys():
|
||||
raise Exception, "Invalid strategy '%s'" % strategy
|
||||
if database != '*' and database != '!' and \
|
||||
not database in self.getdbdescs().keys():
|
||||
raise Exception, "Invalid database name '%s'" % database
|
||||
|
||||
self.sendcommand("MATCH %s %s %s" % (enquote(database),
|
||||
enquote(strategy),
|
||||
enquote(word)))
|
||||
code = self.getresultcode()[0]
|
||||
if code == 552:
|
||||
# No Matches
|
||||
return []
|
||||
if code != 152:
|
||||
raise Exception, "Unexpected code %d" % code
|
||||
|
||||
retval = []
|
||||
|
||||
for matchline in self.get100block().split("\n"):
|
||||
matchdict, matchword = matchline.split(" ", 1)
|
||||
retval.append(Definition(self, self.getdbobj(matchdict),
|
||||
dequote(matchword)))
|
||||
if self.getresultcode()[0] != 250:
|
||||
raise Exception, "Unexpected end-of-list code %d" % code
|
||||
return retval
|
||||
|
||||
class Database:
|
||||
"""An object corresponding to a particular database in a server."""
|
||||
def __init__(self, dictconn, dbname):
|
||||
"""Initialize the object -- requires a Connection object and
|
||||
a database name."""
|
||||
self.conn = dictconn
|
||||
self.name = dbname
|
||||
|
||||
def getname(self):
|
||||
"""Returns the short name for this database."""
|
||||
return self.name
|
||||
|
||||
def getdescription(self):
|
||||
if hasattr(self, 'description'):
|
||||
return self.description
|
||||
if self.getname() == '*':
|
||||
self.description = 'All Databases'
|
||||
elif self.getname() == '!':
|
||||
self.description = 'First matching database'
|
||||
else:
|
||||
self.description = self.conn.getdbdescs()[self.getname()]
|
||||
return self.description
|
||||
|
||||
def getinfo(self):
|
||||
"""Returns a string of info describing this database."""
|
||||
if hasattr(self, 'info'):
|
||||
return self.info
|
||||
|
||||
if self.getname() == '*':
|
||||
self.info = "This special database will search all databases on the system."
|
||||
elif self.getname() == '!':
|
||||
self.info = "This special database will return matches from the first matching database."
|
||||
else:
|
||||
self.conn.sendcommand("SHOW INFO " + self.name)
|
||||
self.info = "\n".join(self.conn.get100result()[1])
|
||||
return self.info
|
||||
|
||||
def define(self, word):
|
||||
"""Get a definition from within this database.
|
||||
The argument, word, is the word to look up. The return value is the
|
||||
same as from Connection.define()."""
|
||||
return self.conn.define(self.getname(), word)
|
||||
|
||||
def match(self, strategy, word):
|
||||
"""Get a match from within this database.
|
||||
The argument, word, is the word to look up. The return value is
|
||||
the same as from Connection.define()."""
|
||||
return self.conn.match(self.getname(), strategy, word)
|
||||
|
||||
class Definition:
|
||||
"""An object corresponding to a single definition."""
|
||||
def __init__(self, dictconn, db, word, defstr = None):
|
||||
"""Instantiate the object. Requires: a Connection object,
|
||||
a Database object (NOT corresponding to '*' or '!' databases),
|
||||
a word. Optional: a definition string. If not supplied,
|
||||
it will be fetched if/when it is requested."""
|
||||
self.conn = dictconn
|
||||
self.db = db
|
||||
self.word = word
|
||||
self.defstr = defstr
|
||||
|
||||
def getdb(self):
|
||||
"""Get the Database object corresponding to this definition."""
|
||||
return self.db
|
||||
|
||||
def getdefstr(self):
|
||||
"""Get the definition string (the actual content) of this
|
||||
definition."""
|
||||
if not self.defstr:
|
||||
self.defstr = self.conn.define(self.getdb().getname(), self.word)[0].getdefstr()
|
||||
return self.defstr
|
||||
|
||||
def getword(self):
|
||||
"""Get the word this object describes."""
|
||||
return self.word
|
@ -1,163 +0,0 @@
|
||||
"""Utilities for handling IEEE 754 floating point special values
|
||||
|
||||
This python module implements constants and functions for working with
|
||||
IEEE754 double-precision special values. It provides constants for
|
||||
Not-a-Number (NaN), Positive Infinity (PosInf), and Negative Infinity
|
||||
(NegInf), as well as functions to test for these values.
|
||||
|
||||
The code is implemented in pure python by taking advantage of the
|
||||
'struct' standard module. Care has been taken to generate proper
|
||||
results on both big-endian and little-endian machines. Some efficiency
|
||||
could be gained by translating the core routines into C.
|
||||
|
||||
See <http://babbage.cs.qc.edu/courses/cs341/IEEE-754references.html>
|
||||
for reference material on the IEEE 754 floating point standard.
|
||||
|
||||
Further information on this package is available at
|
||||
<http://www.analytics.washington.edu/statcomp/projects/rzope/fpconst/>.
|
||||
|
||||
Author: Gregory R. Warnes <gregory_r_warnes@groton.pfizer.com>
|
||||
Date:: 2003-04-08
|
||||
Copyright: (c) 2003, Pfizer, Inc.
|
||||
"""
|
||||
|
||||
__version__ = "0.7.0"
|
||||
ident = "$Id$"
|
||||
|
||||
import struct, operator
|
||||
|
||||
# check endianess
|
||||
_big_endian = struct.pack('i',1)[0] != '\x01'
|
||||
|
||||
# and define appropriate constants
|
||||
if(_big_endian):
|
||||
NaN = struct.unpack('d', '\x7F\xF8\x00\x00\x00\x00\x00\x00')[0]
|
||||
PosInf = struct.unpack('d', '\x7F\xF0\x00\x00\x00\x00\x00\x00')[0]
|
||||
NegInf = -PosInf
|
||||
else:
|
||||
NaN = struct.unpack('d', '\x00\x00\x00\x00\x00\x00\xf8\xff')[0]
|
||||
PosInf = struct.unpack('d', '\x00\x00\x00\x00\x00\x00\xf0\x7f')[0]
|
||||
NegInf = -PosInf
|
||||
|
||||
def _double_as_bytes(dval):
|
||||
"Use struct.unpack to decode a double precision float into eight bytes"
|
||||
tmp = list(struct.unpack('8B',struct.pack('d', dval)))
|
||||
if not _big_endian:
|
||||
tmp.reverse()
|
||||
return tmp
|
||||
|
||||
##
|
||||
## Functions to extract components of the IEEE 754 floating point format
|
||||
##
|
||||
|
||||
def _sign(dval):
|
||||
"Extract the sign bit from a double-precision floating point value"
|
||||
bb = _double_as_bytes(dval)
|
||||
return bb[0] >> 7 & 0x01
|
||||
|
||||
def _exponent(dval):
|
||||
"""Extract the exponentent bits from a double-precision floating
|
||||
point value.
|
||||
|
||||
Note that for normalized values, the exponent bits have an offset
|
||||
of 1023. As a consequence, the actual exponentent is obtained
|
||||
by subtracting 1023 from the value returned by this function
|
||||
"""
|
||||
bb = _double_as_bytes(dval)
|
||||
return (bb[0] << 4 | bb[1] >> 4) & 0x7ff
|
||||
|
||||
def _mantissa(dval):
|
||||
"""Extract the _mantissa bits from a double-precision floating
|
||||
point value."""
|
||||
|
||||
bb = _double_as_bytes(dval)
|
||||
mantissa = bb[1] & 0x0f << 48
|
||||
mantissa += bb[2] << 40
|
||||
mantissa += bb[3] << 32
|
||||
mantissa += bb[4]
|
||||
return mantissa
|
||||
|
||||
def _zero_mantissa(dval):
|
||||
"""Determine whether the mantissa bits of the given double are all
|
||||
zero."""
|
||||
bb = _double_as_bytes(dval)
|
||||
return ((bb[1] & 0x0f) | reduce(operator.or_, bb[2:])) == 0
|
||||
|
||||
##
|
||||
## Functions to test for IEEE 754 special values
|
||||
##
|
||||
|
||||
def isNaN(value):
|
||||
"Determine if the argument is a IEEE 754 NaN (Not a Number) value."
|
||||
return (_exponent(value)==0x7ff and not _zero_mantissa(value))
|
||||
|
||||
def isInf(value):
|
||||
"""Determine if the argument is an infinite IEEE 754 value (positive
|
||||
or negative inifinity)"""
|
||||
return (_exponent(value)==0x7ff and _zero_mantissa(value))
|
||||
|
||||
def isFinite(value):
|
||||
"""Determine if the argument is an finite IEEE 754 value (i.e., is
|
||||
not NaN, positive or negative inifinity)"""
|
||||
return (_exponent(value)!=0x7ff)
|
||||
|
||||
def isPosInf(value):
|
||||
"Determine if the argument is a IEEE 754 positive infinity value"
|
||||
return (_sign(value)==0 and _exponent(value)==0x7ff and \
|
||||
_zero_mantissa(value))
|
||||
|
||||
def isNegInf(value):
|
||||
"Determine if the argument is a IEEE 754 negative infinity value"
|
||||
return (_sign(value)==1 and _exponent(value)==0x7ff and \
|
||||
_zero_mantissa(value))
|
||||
|
||||
##
|
||||
## Functions to test public functions.
|
||||
##
|
||||
|
||||
def test_isNaN():
|
||||
assert( not isNaN(PosInf) )
|
||||
assert( not isNaN(NegInf) )
|
||||
assert( isNaN(NaN ) )
|
||||
assert( not isNaN( 1.0) )
|
||||
assert( not isNaN( -1.0) )
|
||||
|
||||
def test_isInf():
|
||||
assert( isInf(PosInf) )
|
||||
assert( isInf(NegInf) )
|
||||
assert( not isInf(NaN ) )
|
||||
assert( not isInf( 1.0) )
|
||||
assert( not isInf( -1.0) )
|
||||
|
||||
def test_isFinite():
|
||||
assert( not isFinite(PosInf) )
|
||||
assert( not isFinite(NegInf) )
|
||||
assert( not isFinite(NaN ) )
|
||||
assert( isFinite( 1.0) )
|
||||
assert( isFinite( -1.0) )
|
||||
|
||||
def test_isPosInf():
|
||||
assert( isPosInf(PosInf) )
|
||||
assert( not isPosInf(NegInf) )
|
||||
assert( not isPosInf(NaN ) )
|
||||
assert( not isPosInf( 1.0) )
|
||||
assert( not isPosInf( -1.0) )
|
||||
|
||||
def test_isNegInf():
|
||||
assert( not isNegInf(PosInf) )
|
||||
assert( isNegInf(NegInf) )
|
||||
assert( not isNegInf(NaN ) )
|
||||
assert( not isNegInf( 1.0) )
|
||||
assert( not isNegInf( -1.0) )
|
||||
|
||||
# overall test
|
||||
def test():
|
||||
test_isNaN()
|
||||
test_isInf()
|
||||
test_isFinite()
|
||||
test_isPosInf()
|
||||
test_isNegInf()
|
||||
|
||||
if __name__ == "__main__":
|
||||
test()
|
||||
|
638
others/google.py
638
others/google.py
@ -1,638 +0,0 @@
|
||||
"""
|
||||
Python wrapper for Google web APIs
|
||||
|
||||
This module allows you to access Google's web APIs through SOAP,
|
||||
to do things like search Google and get the results programmatically.
|
||||
Described U{here <http://www.google.com/apis/>}
|
||||
|
||||
You need a Google-provided license key to use these services.
|
||||
Follow the link above to get one. These functions will look in
|
||||
several places (in this order) for the license key:
|
||||
|
||||
- the "license_key" argument of each function
|
||||
- the module-level LICENSE_KEY variable (call setLicense once to set it)
|
||||
- an environment variable called GOOGLE_LICENSE_KEY
|
||||
- a file called ".googlekey" in the current directory
|
||||
- a file called "googlekey.txt" in the current directory
|
||||
- a file called ".googlekey" in your home directory
|
||||
- a file called "googlekey.txt" in your home directory
|
||||
- a file called ".googlekey" in the same directory as google.py
|
||||
- a file called "googlekey.txt" in the same directory as google.py
|
||||
|
||||
Sample usage::
|
||||
|
||||
>>> import google
|
||||
>>> google.setLicense('...') # must get your own key!
|
||||
>>> data = google.doGoogleSearch('python')
|
||||
>>> data.meta.searchTime
|
||||
0.043221000000000002
|
||||
|
||||
>>> data.results[0].URL
|
||||
'http://www.python.org/'
|
||||
|
||||
>>> data.results[0].title
|
||||
'<b>Python</b> Language Website'
|
||||
|
||||
@newfield contrib: Contributors
|
||||
@author: Mark Pilgrim <f8dy@diveintomark.org>
|
||||
@author: Brian Landers <brian@bluecoat93.org>
|
||||
@license: Python
|
||||
@version: 0.6
|
||||
@contrib: David Ascher, for the install script
|
||||
@contrib: Erik Max Francis, for the command line interface
|
||||
@contrib: Michael Twomey, for HTTP proxy support
|
||||
@contrib: Mark Recht, for patches to support SOAPpy
|
||||
"""
|
||||
|
||||
__author__ = "Mark Pilgrim (f8dy@diveintomark.org)"
|
||||
__version__ = "0.6"
|
||||
__cvsversion__ = "$Revision$"[11:-2]
|
||||
__date__ = "$Date$"[7:-2]
|
||||
__copyright__ = "Copyright (c) 2002 Mark Pilgrim"
|
||||
__license__ = "Python"
|
||||
__credits__ = """David Ascher, for the install script
|
||||
Erik Max Francis, for the command line interface
|
||||
Michael Twomey, for HTTP proxy support"""
|
||||
|
||||
import os, sys, getopt
|
||||
import GoogleSOAPFacade
|
||||
|
||||
LICENSE_KEY = None
|
||||
HTTP_PROXY = None
|
||||
|
||||
#
|
||||
# Constants
|
||||
#
|
||||
_url = 'http://api.google.com/search/beta2'
|
||||
_namespace = 'urn:GoogleSearch'
|
||||
_googlefile1 = ".googlekey"
|
||||
_googlefile2 = "googlekey.txt"
|
||||
|
||||
_false = GoogleSOAPFacade.false
|
||||
_true = GoogleSOAPFacade.true
|
||||
|
||||
_licenseLocations = (
|
||||
( lambda key: key,
|
||||
'passed to the function in license_key variable' ),
|
||||
( lambda key: LICENSE_KEY,
|
||||
'module-level LICENSE_KEY variable (call setLicense to set it)' ),
|
||||
( lambda key: os.environ.get( 'GOOGLE_LICENSE_KEY', None ),
|
||||
'an environment variable called GOOGLE_LICENSE_KEY' ),
|
||||
( lambda key: _contentsOf( os.getcwd(), _googlefile1 ),
|
||||
'%s in the current directory' % _googlefile1),
|
||||
( lambda key: _contentsOf( os.getcwd(), _googlefile2 ),
|
||||
'%s in the current directory' % _googlefile2),
|
||||
( lambda key: _contentsOf( os.environ.get( 'HOME', '' ), _googlefile1 ),
|
||||
'%s in your home directory' % _googlefile1),
|
||||
( lambda key: _contentsOf( os.environ.get( 'HOME', '' ), _googlefile2 ),
|
||||
'%s in your home directory' % _googlefile2 ),
|
||||
( lambda key: _contentsOf( _getScriptDir(), _googlefile1 ),
|
||||
'%s in the google.py directory' % _googlefile1 ),
|
||||
( lambda key: _contentsOf( _getScriptDir(), _googlefile2 ),
|
||||
'%s in the google.py directory' % _googlefile2 )
|
||||
)
|
||||
|
||||
## ----------------------------------------------------------------------
|
||||
## Exceptions
|
||||
## ----------------------------------------------------------------------
|
||||
|
||||
class NoLicenseKey(Exception):
|
||||
"""
|
||||
Thrown when the API is unable to find a valid license key.
|
||||
"""
|
||||
pass
|
||||
|
||||
## ----------------------------------------------------------------------
|
||||
## administrative functions (non-API)
|
||||
## ----------------------------------------------------------------------
|
||||
|
||||
def _version():
|
||||
"""
|
||||
Display a formatted version string for the module
|
||||
"""
|
||||
print """PyGoogle %(__version__)s
|
||||
%(__copyright__)s
|
||||
released %(__date__)s
|
||||
|
||||
Thanks to:
|
||||
%(__credits__)s""" % globals()
|
||||
|
||||
|
||||
def _usage():
|
||||
"""
|
||||
Display usage information for the command-line interface
|
||||
"""
|
||||
program = os.path.basename(sys.argv[0])
|
||||
print """Usage: %(program)s [options] [querytype] query
|
||||
|
||||
options:
|
||||
-k, --key= <license key> Google license key (see important note below)
|
||||
-1, -l, --lucky show only first hit
|
||||
-m, --meta show meta information
|
||||
-r, --reverse show results in reverse order
|
||||
-x, --proxy= <url> use HTTP proxy
|
||||
-h, --help print this help
|
||||
-v, --version print version and copyright information
|
||||
-t, --test run test queries
|
||||
|
||||
querytype:
|
||||
-s, --search= <query> search (default)
|
||||
-c, --cache= <url> retrieve cached page
|
||||
-p, --spelling= <word> check spelling
|
||||
|
||||
IMPORTANT NOTE: all Google functions require a valid license key;
|
||||
visit http://www.google.com/apis/ to get one. %(program)s will look in
|
||||
these places (in order) and use the first license key it finds:
|
||||
* the key specified on the command line""" % vars()
|
||||
for get, location in _licenseLocations[2:]:
|
||||
print " *", location
|
||||
|
||||
## ----------------------------------------------------------------------
|
||||
## utility functions (API)
|
||||
## ----------------------------------------------------------------------
|
||||
|
||||
def setLicense(license_key):
|
||||
"""
|
||||
Set the U{Google APIs <http://www.google.com/api>} license key
|
||||
|
||||
@param license_key: The new key to use
|
||||
@type license_key: String
|
||||
@todo: validate the key?
|
||||
"""
|
||||
global LICENSE_KEY
|
||||
LICENSE_KEY = license_key
|
||||
|
||||
|
||||
def getLicense(license_key = None):
|
||||
"""
|
||||
Get the U{Google APIs <http://www.google.com/api>} license key
|
||||
|
||||
The key can be read from any number of locations. See the module-leve
|
||||
documentation for the search order.
|
||||
|
||||
@return: the license key
|
||||
@rtype: String
|
||||
@raise NoLicenseKey: if no valid key could be found
|
||||
"""
|
||||
for get, location in _licenseLocations:
|
||||
rc = get(license_key)
|
||||
if rc: return rc
|
||||
_usage()
|
||||
raise NoLicenseKey, 'get a license key at http://www.google.com/apis/'
|
||||
|
||||
|
||||
def setProxy(http_proxy):
|
||||
"""
|
||||
Set the HTTP proxy to be used when accessing Google
|
||||
|
||||
@param http_proxy: the proxy to use
|
||||
@type http_proxy: String
|
||||
@todo: validiate the input?
|
||||
"""
|
||||
global HTTP_PROXY
|
||||
HTTP_PROXY = http_proxy
|
||||
|
||||
|
||||
def getProxy(http_proxy = None):
|
||||
"""
|
||||
Get the HTTP proxy we use for accessing Google
|
||||
|
||||
@return: the proxy
|
||||
@rtype: String
|
||||
"""
|
||||
return http_proxy or HTTP_PROXY
|
||||
|
||||
|
||||
def _contentsOf(dirname, filename):
|
||||
filename = os.path.join(dirname, filename)
|
||||
if not os.path.exists(filename): return None
|
||||
fsock = open(filename)
|
||||
contents = fsock.read()
|
||||
fsock.close()
|
||||
return contents
|
||||
|
||||
|
||||
def _getScriptDir():
|
||||
if __name__ == '__main__':
|
||||
return os.path.abspath(os.path.dirname(sys.argv[0]))
|
||||
else:
|
||||
return os.path.abspath(os.path.dirname(sys.modules[__name__].__file__))
|
||||
|
||||
|
||||
def _marshalBoolean(value):
|
||||
if value:
|
||||
return _true
|
||||
else:
|
||||
return _false
|
||||
|
||||
|
||||
def _getRemoteServer( http_proxy ):
|
||||
return GoogleSOAPFacade.getProxy( _url, _namespace, http_proxy )
|
||||
|
||||
|
||||
## ----------------------------------------------------------------------
|
||||
## search results classes
|
||||
## ----------------------------------------------------------------------
|
||||
|
||||
class _SearchBase:
|
||||
def __init__(self, params):
|
||||
for k, v in params.items():
|
||||
if isinstance(v, GoogleSOAPFacade.structType):
|
||||
v = GoogleSOAPFacade.toDict( v )
|
||||
|
||||
try:
|
||||
if isinstance(v[0], GoogleSOAPFacade.structType):
|
||||
v = [ SOAPProxy.toDict( node ) for node in v ]
|
||||
|
||||
except:
|
||||
pass
|
||||
self.__dict__[str(k)] = v
|
||||
|
||||
## ----------------------------------------------------------------------
|
||||
|
||||
class SearchResultsMetaData(_SearchBase):
|
||||
"""
|
||||
Container class for metadata about a given search query's results.
|
||||
|
||||
@ivar documentFiltering: is duplicate page filtering active?
|
||||
|
||||
@ivar searchComments: human-readable informational message
|
||||
|
||||
example::
|
||||
|
||||
"'the' is a very common word and was not included in your search"
|
||||
|
||||
@ivar estimatedTotalResultsCount: estimated total number of results
|
||||
for this query.
|
||||
|
||||
@ivar estimateIsExact: is estimatedTotalResultsCount an exact value?
|
||||
|
||||
@ivar searchQuery: search string that initiated this search
|
||||
|
||||
@ivar startIndex: index of the first result returned (zero-based)
|
||||
|
||||
@ivar endIndex: index of the last result returned (zero-based)
|
||||
|
||||
@ivar searchTips: human-readable informational message on how to better
|
||||
use Google.
|
||||
|
||||
@ivar directoryCategories: list of categories for the search results
|
||||
|
||||
This field is a list of dictionaries, like so::
|
||||
|
||||
{ 'fullViewableName': 'the Open Directory category',
|
||||
'specialEncoding': 'encoding scheme of this directory category'
|
||||
}
|
||||
|
||||
@ivar searchTime: total search time, in seconds
|
||||
"""
|
||||
pass
|
||||
|
||||
## ----------------------------------------------------------------------
|
||||
|
||||
class SearchResult(_SearchBase):
|
||||
"""
|
||||
Encapsulates the results from a search.
|
||||
|
||||
@ivar URL: URL
|
||||
|
||||
@ivar title: title (HTML)
|
||||
|
||||
@ivar snippet: snippet showing query context (HTML
|
||||
|
||||
@ivar cachedSize: size of cached version of this result, (KB)
|
||||
|
||||
@ivar relatedInformationPresent: is the "related:" keyword supported?
|
||||
|
||||
Flag indicates that the "related:" keyword is supported for this URL
|
||||
|
||||
@ivar hostName: used when filtering occurs
|
||||
|
||||
When filtering occurs, a maximum of two results from any given
|
||||
host is returned. When this occurs, the second resultElement
|
||||
that comes from that host contains the host name in this parameter.
|
||||
|
||||
@ivar directoryCategory: Open Directory category information
|
||||
|
||||
This field is a dictionary with the following values::
|
||||
|
||||
{ 'fullViewableName': 'the Open Directory category',
|
||||
'specialEncoding' : 'encoding scheme of this directory category'
|
||||
}
|
||||
|
||||
@ivar directoryTitle: Open Directory title of this result (or blank)
|
||||
|
||||
@ivar summary: Open Directory summary for this result (or blank)
|
||||
"""
|
||||
pass
|
||||
|
||||
## ----------------------------------------------------------------------
|
||||
|
||||
class SearchReturnValue:
|
||||
"""
|
||||
complete search results for a single query
|
||||
|
||||
@ivar meta: L{SearchResultsMetaData} instance for this query
|
||||
|
||||
@ivar results: list of L{SearchResult} objects for this query
|
||||
"""
|
||||
def __init__( self, metadata, results ):
|
||||
self.meta = metadata
|
||||
self.results = results
|
||||
|
||||
## ----------------------------------------------------------------------
|
||||
## main functions
|
||||
## ----------------------------------------------------------------------
|
||||
|
||||
def doGoogleSearch( q, start = 0, maxResults = 10, filter = 1,
|
||||
restrict='', safeSearch = 0, language = '',
|
||||
inputencoding = '', outputencoding = '',\
|
||||
license_key = None, http_proxy = None ):
|
||||
"""
|
||||
Search Google using the SOAP API and return the results.
|
||||
|
||||
You need a license key to call this function; see the
|
||||
U{Google APIs <http://www.google.com/apis/>} site to get one.
|
||||
Then you can either pass it to this function every time, or
|
||||
set it globally; see the L{google} module-level docs for details.
|
||||
|
||||
See U{http://www.google.com/help/features.html}
|
||||
for examples of advanced features. Anything that works at the
|
||||
Google web site will work as a query string in this method.
|
||||
|
||||
You can use the C{start} and C{maxResults} parameters to page
|
||||
through multiple pages of results. Note that 'maxResults' is
|
||||
currently limited by Google to 10.
|
||||
|
||||
See the API reference for more advanced examples and a full list of
|
||||
country codes and topics for use in the C{restrict} parameter, along
|
||||
with legal values for the C{language}, C{inputencoding}, and
|
||||
C{outputencoding} parameters.
|
||||
|
||||
You can download the API documentation
|
||||
U{http://www.google.com/apis/download.html <here>}.
|
||||
|
||||
@param q: search string.
|
||||
@type q: String
|
||||
|
||||
@param start: (optional) zero-based index of first desired result.
|
||||
@type start: int
|
||||
|
||||
@param maxResults: (optional) maximum number of results to return.
|
||||
@type maxResults: int
|
||||
|
||||
@param filter: (optional) flag to request filtering of similar results
|
||||
@type filter: int
|
||||
|
||||
@param restrict: (optional) restrict results by country or topic.
|
||||
@type restrict: String
|
||||
|
||||
@param safeSearch: (optional)
|
||||
@type safeSearch: int
|
||||
|
||||
@param language: (optional)
|
||||
@type language: String
|
||||
|
||||
@param inputencoding: (optional)
|
||||
@type inputencoding: String
|
||||
|
||||
@param outputencoding: (optional)
|
||||
@type outputencoding: String
|
||||
|
||||
@param license_key: (optional) the Google API license key to use
|
||||
@type license_key: String
|
||||
|
||||
@param http_proxy: (optional) the HTTP proxy to use for talking to Google
|
||||
@type http_proxy: String
|
||||
|
||||
@return: the search results encapsulated in an object
|
||||
@rtype: L{SearchReturnValue}
|
||||
"""
|
||||
license_key = getLicense( license_key )
|
||||
http_proxy = getProxy( http_proxy )
|
||||
remoteserver = _getRemoteServer( http_proxy )
|
||||
|
||||
filter = _marshalBoolean( filter )
|
||||
safeSearch = _marshalBoolean( safeSearch )
|
||||
|
||||
data = remoteserver.doGoogleSearch( license_key, q, start, maxResults,
|
||||
filter, restrict, safeSearch,
|
||||
language, inputencoding,
|
||||
outputencoding )
|
||||
|
||||
metadata = GoogleSOAPFacade.toDict( data )
|
||||
del metadata["resultElements"]
|
||||
|
||||
metadata = SearchResultsMetaData( metadata )
|
||||
|
||||
results = [ SearchResult( GoogleSOAPFacade.toDict( node ) ) \
|
||||
for node in data.resultElements ]
|
||||
|
||||
return SearchReturnValue( metadata, results )
|
||||
|
||||
## ----------------------------------------------------------------------
|
||||
|
||||
def doGetCachedPage( url, license_key = None, http_proxy = None ):
|
||||
"""
|
||||
Retrieve a page from the Google cache.
|
||||
|
||||
You need a license key to call this function; see the
|
||||
U{Google APIs <http://www.google.com/apis/>} site to get one.
|
||||
Then you can either pass it to this function every time, or
|
||||
set it globally; see the L{google} module-level docs for details.
|
||||
|
||||
@param url: full URL to the page to retrieve
|
||||
@type url: String
|
||||
|
||||
@param license_key: (optional) the Google API key to use
|
||||
@type license_key: String
|
||||
|
||||
@param http_proxy: (optional) the HTTP proxy server to use
|
||||
@type http_proxy: String
|
||||
|
||||
@return: full text of the cached page
|
||||
@rtype: String
|
||||
"""
|
||||
license_key = getLicense( license_key )
|
||||
http_proxy = getProxy( http_proxy )
|
||||
remoteserver = _getRemoteServer( http_proxy )
|
||||
|
||||
return remoteserver.doGetCachedPage( license_key, url )
|
||||
|
||||
## ----------------------------------------------------------------------
|
||||
|
||||
def doSpellingSuggestion( phrase, license_key = None, http_proxy = None ):
|
||||
"""
|
||||
Get spelling suggestions from Google
|
||||
|
||||
You need a license key to call this function; see the
|
||||
U{Google APIs <http://www.google.com/apis/>} site to get one.
|
||||
Then you can either pass it to this function every time, or
|
||||
set it globally; see the L{google} module-level docs for details.
|
||||
|
||||
@param phrase: word or phrase to spell-check
|
||||
@type phrase: String
|
||||
|
||||
@param license_key: (optional) the Google API key to use
|
||||
@type license_key: String
|
||||
|
||||
@param http_proxy: (optional) the HTTP proxy to use
|
||||
@type http_proxy: String
|
||||
|
||||
@return: text of any suggested replacement, or None
|
||||
"""
|
||||
license_key = getLicense( license_key )
|
||||
http_proxy = getProxy( http_proxy)
|
||||
remoteserver = _getRemoteServer( http_proxy )
|
||||
|
||||
return remoteserver.doSpellingSuggestion( license_key, phrase )
|
||||
|
||||
## ----------------------------------------------------------------------
|
||||
## functional test suite (see googletest.py for unit test suite)
|
||||
## ----------------------------------------------------------------------
|
||||
|
||||
def _test():
|
||||
"""
|
||||
Run functional test suite.
|
||||
"""
|
||||
try:
|
||||
getLicense(None)
|
||||
except NoLicenseKey:
|
||||
return
|
||||
|
||||
print "Searching for Python at google.com..."
|
||||
data = doGoogleSearch( "Python" )
|
||||
_output( data, { "func": "doGoogleSearch"} )
|
||||
|
||||
print "\nSearching for 5 _French_ pages about Python, "
|
||||
print "encoded in ISO-8859-1..."
|
||||
|
||||
data = doGoogleSearch( "Python", language = 'lang_fr',
|
||||
outputencoding = 'ISO-8859-1',
|
||||
maxResults = 5 )
|
||||
|
||||
_output( data, { "func": "doGoogleSearch" } )
|
||||
|
||||
phrase = "Pyhton programming languager"
|
||||
print "\nTesting spelling suggestions for '%s'..." % phrase
|
||||
|
||||
data = doSpellingSuggestion( phrase )
|
||||
|
||||
_output( data, { "func": "doSpellingSuggestion" } )
|
||||
|
||||
## ----------------------------------------------------------------------
|
||||
## Command-line interface
|
||||
## ----------------------------------------------------------------------
|
||||
|
||||
class _OutputFormatter:
|
||||
def boil(self, data):
|
||||
if type(data) == type(u""):
|
||||
return data.encode("ISO-8859-1", "replace")
|
||||
else:
|
||||
return data
|
||||
|
||||
class _TextOutputFormatter(_OutputFormatter):
|
||||
def common(self, data, params):
|
||||
if params.get("showMeta", 0):
|
||||
meta = data.meta
|
||||
for category in meta.directoryCategories:
|
||||
print "directoryCategory: %s" % \
|
||||
self.boil(category["fullViewableName"])
|
||||
for attr in [node for node in dir(meta) if \
|
||||
node <> "directoryCategories" and node[:2] <> '__']:
|
||||
print "%s:" % attr, self.boil(getattr(meta, attr))
|
||||
|
||||
def doGoogleSearch(self, data, params):
|
||||
results = data.results
|
||||
if params.get("feelingLucky", 0):
|
||||
results = results[:1]
|
||||
if params.get("reverseOrder", 0):
|
||||
results.reverse()
|
||||
for result in results:
|
||||
for attr in dir(result):
|
||||
if attr == "directoryCategory":
|
||||
print "directoryCategory:", \
|
||||
self.boil(result.directoryCategory["fullViewableName"])
|
||||
elif attr[:2] <> '__':
|
||||
print "%s:" % attr, self.boil(getattr(result, attr))
|
||||
print
|
||||
self.common(data, params)
|
||||
|
||||
def doGetCachedPage(self, data, params):
|
||||
print data
|
||||
self.common(data, params)
|
||||
|
||||
doSpellingSuggestion = doGetCachedPage
|
||||
|
||||
def _makeFormatter(outputFormat):
|
||||
classname = "_%sOutputFormatter" % outputFormat.capitalize()
|
||||
return globals()[classname]()
|
||||
|
||||
def _output(results, params):
|
||||
formatter = _makeFormatter(params.get("outputFormat", "text"))
|
||||
outputmethod = getattr(formatter, params["func"])
|
||||
outputmethod(results, params)
|
||||
|
||||
def main(argv):
|
||||
"""
|
||||
Command-line interface.
|
||||
"""
|
||||
if not argv:
|
||||
_usage()
|
||||
return
|
||||
q = None
|
||||
func = None
|
||||
http_proxy = None
|
||||
license_key = None
|
||||
feelingLucky = 0
|
||||
showMeta = 0
|
||||
reverseOrder = 0
|
||||
runTest = 0
|
||||
outputFormat = "text"
|
||||
try:
|
||||
opts, args = getopt.getopt(argv, "s:c:p:k:lmrx:hvt1",
|
||||
["search=", "cache=", "spelling=", "key=", "lucky", "meta",
|
||||
"reverse", "proxy=", "help", "version", "test"])
|
||||
except getopt.GetoptError:
|
||||
_usage()
|
||||
sys.exit(2)
|
||||
for opt, arg in opts:
|
||||
if opt in ("-s", "--search"):
|
||||
q = arg
|
||||
func = "doGoogleSearch"
|
||||
elif opt in ("-c", "--cache"):
|
||||
q = arg
|
||||
func = "doGetCachedPage"
|
||||
elif opt in ("-p", "--spelling"):
|
||||
q = arg
|
||||
func = "doSpellingSuggestion"
|
||||
elif opt in ("-k", "--key"):
|
||||
license_key = arg
|
||||
elif opt in ("-l", "-1", "--lucky"):
|
||||
feelingLucky = 1
|
||||
elif opt in ("-m", "--meta"):
|
||||
showMeta = 1
|
||||
elif opt in ("-r", "--reverse"):
|
||||
reverseOrder = 1
|
||||
elif opt in ("-x", "--proxy"):
|
||||
http_proxy = arg
|
||||
elif opt in ("-h", "--help"):
|
||||
_usage()
|
||||
elif opt in ("-v", "--version"):
|
||||
_version()
|
||||
elif opt in ("-t", "--test"):
|
||||
runTest = 1
|
||||
if runTest:
|
||||
setLicense(license_key)
|
||||
setProxy(http_proxy)
|
||||
_test()
|
||||
if args and not q:
|
||||
q = args[0]
|
||||
func = "doGoogleSearch"
|
||||
if func:
|
||||
results = globals()[func]( q, http_proxy=http_proxy,
|
||||
license_key=license_key )
|
||||
_output(results, locals())
|
||||
|
||||
if __name__ == '__main__':
|
||||
main(sys.argv[1:])
|
193
others/poker.py
193
others/poker.py
@ -1,193 +0,0 @@
|
||||
###
|
||||
# Copyright (c) 2004, Jeremiah Fincher
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions, and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions, and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
# * Neither the name of the author of this software nor the name of
|
||||
# contributors to this software may be used to endorse or promote products
|
||||
# derived from this software without specific prior written consent.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
###
|
||||
|
||||
import supybot.fix
|
||||
|
||||
# Constants.
|
||||
S = 's'
|
||||
C = 'c'
|
||||
H = 'h'
|
||||
D = 'd'
|
||||
|
||||
class Rank(int):
|
||||
def __str__(self):
|
||||
if 2 <= self <= 10:
|
||||
return str(self)
|
||||
elif self == 11:
|
||||
return 'J'
|
||||
elif self == 12:
|
||||
return 'Q'
|
||||
elif self == 13:
|
||||
return 'K'
|
||||
elif self == 14:
|
||||
return 'A'
|
||||
elif self == 1:
|
||||
return 'A'
|
||||
|
||||
A = Rank(14)
|
||||
K = Rank(13)
|
||||
Q = Rank(12)
|
||||
J = Rank(11)
|
||||
|
||||
class Card(object):
|
||||
def __init__(self, rank, suit):
|
||||
self.rank = rank
|
||||
self.suit = suit
|
||||
|
||||
def __str__(self):
|
||||
return '%s%s' % (self.rank, self.suit)
|
||||
__repr__ = __str__
|
||||
|
||||
def __cmp__(self, other):
|
||||
return cmp(self.rank, other.rank)
|
||||
|
||||
def combinations(L, n):
|
||||
if len(L) >= n:
|
||||
if n == 0:
|
||||
yield []
|
||||
else:
|
||||
(first, rest) = (L[:1], L[1:])
|
||||
for miniL in combinations(rest, n-1):
|
||||
yield first + miniL
|
||||
for miniL in combinations(rest, n):
|
||||
yield miniL
|
||||
|
||||
def sort(hand):
|
||||
hand.sort()
|
||||
hand.reverse()
|
||||
return hand
|
||||
|
||||
def getFlush(hand):
|
||||
if all(lambda c: c.suit == hand[0].suit, hand):
|
||||
return sort(hand)
|
||||
return []
|
||||
|
||||
def getStraight(hand):
|
||||
sort(hand)
|
||||
diffs = [x.rank-y.rank for (x,y) in window(hand,2)]
|
||||
if diffs == [1,1,1,1]:
|
||||
return hand
|
||||
elif hand[0].rank == A and diffs == [9,1,1,1]:
|
||||
ace = hand.pop(0)
|
||||
hand.append(ace)
|
||||
return hand
|
||||
return []
|
||||
|
||||
def getPair(hand):
|
||||
sort(hand)
|
||||
for (x, y) in window(hand, 2):
|
||||
if x.rank == y.rank:
|
||||
return ([x,y], [c for c in hand if c.rank != x.rank])
|
||||
return ([], hand)
|
||||
|
||||
def getTrips(hand):
|
||||
sort(hand)
|
||||
for (x, y, z) in window(hand, 3):
|
||||
if x.rank == y.rank == z.rank:
|
||||
return ([x,y,z], [c for c in hand if c.rank != x.rank])
|
||||
return ([], hand)
|
||||
|
||||
def getFours(hand):
|
||||
sort(hand)
|
||||
for (x, y, z, w) in window(hand, 4):
|
||||
if x.rank == y.rank == z.rank == w.rank:
|
||||
return ([x,y,z,w], [c for c in hand if c.rank != x.rank])
|
||||
return ([], hand)
|
||||
|
||||
STRAIGHT_FLUSH = '8:straight flush'
|
||||
FOUR_OF_A_KIND = '7:four of a kind'
|
||||
FULL_HOUSE = '6:full house'
|
||||
FLUSH = '5:flush'
|
||||
STRAIGHT = '4:straight'
|
||||
THREE_OF_A_KIND = '3:three of a kind'
|
||||
TWO_PAIR = '2:two pair'
|
||||
PAIR = '1:pair'
|
||||
RUNT = '0:runt'
|
||||
|
||||
def score(cards):
|
||||
"""Returns a comparable value for a list of cards."""
|
||||
def getRank(hand):
|
||||
assert len(hand) == 5
|
||||
(pair, pairRest) = getPair(hand)
|
||||
if pair:
|
||||
# Can't be flushes or straights.
|
||||
(trips, tripRest) = getTrips(hand)
|
||||
if trips:
|
||||
(fours, fourRest) = getFours(hand)
|
||||
if fours:
|
||||
return (FOUR_OF_A_KIND, fours + fourRest)
|
||||
(pair, _) = getPair(tripRest)
|
||||
if pair:
|
||||
# Full house.
|
||||
return (FULL_HOUSE, trips + pair)
|
||||
sort(tripRest)
|
||||
return (THREE_OF_A_KIND, trips + tripRest)
|
||||
(otherPair, twoPairRest) = getPair(pairRest)
|
||||
if otherPair:
|
||||
if otherPair[0] > pair[0]:
|
||||
return (TWO_PAIR, otherPair + pair + twoPairRest)
|
||||
else:
|
||||
return (TWO_PAIR, pair + otherPair + twoPairRest)
|
||||
sort(pairRest)
|
||||
return (PAIR, pair + pairRest)
|
||||
else:
|
||||
flush = getFlush(hand)
|
||||
if flush:
|
||||
straight = getStraight(hand)
|
||||
if straight:
|
||||
return (STRAIGHT_FLUSH, straight)
|
||||
return (FLUSH, flush)
|
||||
straight = getStraight(hand)
|
||||
if straight:
|
||||
return (STRAIGHT, straight)
|
||||
hand.sort()
|
||||
return (RUNT, hand)
|
||||
first = 0
|
||||
second = None
|
||||
for hand in combinations(cards, 5):
|
||||
(maybeFirst, maybeSecond) = getRank(hand)
|
||||
if maybeFirst > first:
|
||||
first = maybeFirst
|
||||
second = maybeSecond
|
||||
elif maybeFirst == first:
|
||||
second = max(second, maybeSecond)
|
||||
assert len(second) == 5, 'invalid second len'
|
||||
return (first, second)
|
||||
|
||||
deck = []
|
||||
for suit in [S, H, C, D]:
|
||||
for rank in [A, K, Q, J] + range(10, 1, -1):
|
||||
deck.append(Card(rank, suit))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import random
|
||||
random.shuffle(deck)
|
||||
for hand in window(deck, 7):
|
||||
print '%s: %s' % (hand, score(hand))
|
@ -1,96 +0,0 @@
|
||||
# This module is part of the Pyndex project and is Copyright 2003 Amir
|
||||
# Bakhtiar (amir@divmod.org). This is free software; you can redistribute
|
||||
# it and/or modify it under the terms of version 2.1 of the GNU Lesser
|
||||
# General Public License as published by the Free Software Foundation.
|
||||
|
||||
import string
|
||||
|
||||
class Splitter(object):
|
||||
"""Split plain text into words" utility class
|
||||
Adapted from David Mertz's article in IBM developerWorks
|
||||
Needs work to handle international characters, etc"""
|
||||
|
||||
## __slots__ = ['stemmer', 'porter', 'stopwording', 'word_only', 'nonword',
|
||||
## 'nondigits', 'alpha', 'ident', 'tokens', 'position']
|
||||
|
||||
|
||||
stopWords = {'and': 1, 'be': 1, 'to': 1, 'that': 1, 'into': 1,
|
||||
'it': 1, 'but': 1, 'as': 1, 'are': 1, 'they': 1,
|
||||
'in': 1, 'not': 1, 'such': 1, 'with': 1, 'by': 1,
|
||||
'is': 1, 'if': 1, 'a': 1, 'on': 1, 'for': 1,
|
||||
'no': 1, 'these': 1, 'of': 1, 'there': 1,
|
||||
'this': 1, 'will': 1, 'their': 1, 's': 1, 't': 1,
|
||||
'then': 1, 'the': 1, 'was': 1, 'or': 1, 'at': 1}
|
||||
|
||||
yes = string.lowercase + string.digits + '' # throw in any extras
|
||||
nonword = ''
|
||||
for i in range(0,255):
|
||||
if chr(i) not in yes:
|
||||
nonword += chr(i)
|
||||
|
||||
word_only = string.maketrans(nonword, " " * len(nonword))
|
||||
|
||||
nondigits = string.join(map(chr, range(0,48)) + map(chr, range(58,255)), '')
|
||||
alpha = string.join(map(chr, range(65,91)) + map(chr, range(97,123)), '')
|
||||
ident = string.join(map(chr, range(256)), '')
|
||||
|
||||
def close(self):
|
||||
# Lupy support
|
||||
pass
|
||||
|
||||
def tokenStream(self, fieldName, file, casesensitive=False):
|
||||
"""Split text/plain string into a list of words
|
||||
"""
|
||||
self.tokens = self.split(file.read())
|
||||
self.position = 0
|
||||
return self
|
||||
|
||||
def next(self):
|
||||
if self.position >= len(self.tokens):
|
||||
return None
|
||||
res = Token(self.tokens[self.position])
|
||||
self.position += 1
|
||||
return res
|
||||
|
||||
def split(self, text, casesensitive=0):
|
||||
# Speedup trick: attributes into local scope
|
||||
word_only = self.word_only
|
||||
ident = self.ident
|
||||
alpha = self.alpha
|
||||
nondigits = self.nondigits
|
||||
|
||||
# Let's adjust case if not case-sensitive
|
||||
if not casesensitive: text = string.lower(text)
|
||||
|
||||
# Split the raw text
|
||||
allwords = text.translate(word_only).split() # Let's strip funny byte values
|
||||
|
||||
# Finally, let's skip some words not worth indexing
|
||||
words = []
|
||||
for word in allwords:
|
||||
if len(word) > 32: continue # too long (probably gibberish)
|
||||
|
||||
# Identify common patterns in non-word data (binary, UU/MIME, etc)
|
||||
num_nonalpha = len(word.translate(ident, alpha))
|
||||
numdigits = len(word.translate(ident, nondigits))
|
||||
if numdigits > len(word)-2: # almost all digits
|
||||
if numdigits > 5: # too many digits is gibberish
|
||||
continue # a moderate number is year/zipcode/etc
|
||||
elif num_nonalpha*2 > len(word): # too much scattered nonalpha = gibberish
|
||||
continue
|
||||
|
||||
word = word.translate(word_only) # Let's strip funny byte values
|
||||
subwords = word.split() # maybe embedded non-alphanumeric
|
||||
for subword in subwords: # ...so we might have subwords
|
||||
if len(subword) <= 1: continue # too short a subword
|
||||
words.append(subword)
|
||||
|
||||
return words
|
||||
|
||||
class Token:
|
||||
def __init__(self, trmText):
|
||||
self.trmText = trmText
|
||||
|
||||
def termText(self):
|
||||
return self.trmText
|
||||
|
@ -1,309 +0,0 @@
|
||||
# This module is part of the Divmod project and is Copyright 2003 Amir Bakhtiar:
|
||||
# amir@divmod.org. This is free software; you can redistribute it and/or
|
||||
# modify it under the terms of version 2.1 of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation.
|
||||
#
|
||||
|
||||
import operator
|
||||
import string
|
||||
import math
|
||||
from sets import Set
|
||||
from splitter import Splitter
|
||||
|
||||
class BayesData(dict):
|
||||
|
||||
def __init__(self, name='', pool=None):
|
||||
self.name = name
|
||||
self.training = []
|
||||
self.pool = pool
|
||||
self.tokenCount = 0
|
||||
self.trainCount = 0
|
||||
|
||||
def trainedOn(self, item):
|
||||
return item in self.training
|
||||
|
||||
def __repr__(self):
|
||||
return '<BayesDict: %s, %s tokens>' % (self.name, self.tokenCount)
|
||||
|
||||
|
||||
|
||||
class Bayes(object):
|
||||
|
||||
def __init__(self, tokenizer=None, combiner=None, dataClass=None):
|
||||
if dataClass is None:
|
||||
self.dataClass = BayesData
|
||||
else:
|
||||
self.dataClass = dataClass
|
||||
self.corpus = self.dataClass('__Corpus__')
|
||||
self.pools = {}
|
||||
self.pools['__Corpus__'] = self.corpus
|
||||
self.trainCount = 0
|
||||
self.splitter = Splitter()
|
||||
self.dirty = True
|
||||
# The tokenizer takes an object and returns
|
||||
# a list of strings
|
||||
if tokenizer is None:
|
||||
self.tokenizer = self.getTokens
|
||||
else:
|
||||
self.tokenizer = tokenizer
|
||||
# The combiner combines probabilities
|
||||
if combiner is None:
|
||||
self.combiner = self.robinson
|
||||
else:
|
||||
self.combiner = combiner
|
||||
|
||||
def split(self, text):
|
||||
return self.splitter.split(text)
|
||||
|
||||
def commit(self):
|
||||
self.save()
|
||||
|
||||
def newPool(self, poolName):
|
||||
"""Create a new pool, without actually doing any
|
||||
training.
|
||||
"""
|
||||
self.dirty = True # not always true, but it's simple
|
||||
return self.pools.setdefault(poolName, self.dataClass(poolName))
|
||||
|
||||
def removePool(self, poolName):
|
||||
del(self.pools[poolName])
|
||||
self.dirty = True
|
||||
|
||||
def renamePool(self, poolName, newName):
|
||||
self.pools[newName] = self.pools[poolName]
|
||||
self.pools[newName].name = newName
|
||||
self.removePool(poolName)
|
||||
self.dirty = True
|
||||
|
||||
def mergePools(self, destPool, sourcePool):
|
||||
"""Merge an existing pool into another.
|
||||
The data from sourcePool is merged into destPool.
|
||||
The arguments are the names of the pools to be merged.
|
||||
The pool named sourcePool is left in tact and you may
|
||||
want to call removePool() to get rid of it.
|
||||
"""
|
||||
sp = self.pools[sourcePool]
|
||||
dp = self.pools[destPool]
|
||||
for tok, count in sp.items():
|
||||
if dp.get(tok):
|
||||
dp[tok] += count
|
||||
else:
|
||||
dp[tok] = count
|
||||
dp.tokenCount += 1
|
||||
self.dirty = True
|
||||
|
||||
def poolData(self, poolName):
|
||||
"""Return a list of the (token, count) tuples.
|
||||
"""
|
||||
return self.pools[poolName].items()
|
||||
|
||||
def poolTokens(self, poolName):
|
||||
"""Return a list of the tokens in this pool.
|
||||
"""
|
||||
return [tok for tok, count in self.poolData(poolName)]
|
||||
|
||||
def save(self, fname='bayesdata.dat'):
|
||||
from cPickle import dump
|
||||
fp = open(fname, 'wb')
|
||||
dump(self.pools, fp)
|
||||
fp.close()
|
||||
|
||||
def load(self, fname='bayesdata.dat'):
|
||||
from cPickle import load
|
||||
fp = open(fname, 'rb')
|
||||
self.pools = load(fp)
|
||||
fp.close()
|
||||
self.corpus = self.pools['__Corpus__']
|
||||
self.dirty = True
|
||||
|
||||
def poolNames(self):
|
||||
"""Return a sorted list of Pool names.
|
||||
Does not include the system pool '__Corpus__'.
|
||||
"""
|
||||
pools = self.pools.keys()
|
||||
pools.remove('__Corpus__')
|
||||
pools = [pool for pool in pools]
|
||||
pools.sort()
|
||||
return pools
|
||||
|
||||
def buildCache(self):
|
||||
""" merges corpora and computes probabilities
|
||||
"""
|
||||
self.cache = {}
|
||||
for pname, pool in self.pools.items():
|
||||
# skip our special pool
|
||||
if pname == '__Corpus__':
|
||||
continue
|
||||
|
||||
poolCount = len(pool)
|
||||
themCount = max(len(self.corpus) - poolCount, 1)
|
||||
cacheDict = self.cache.setdefault(pname, self.dataClass(pname))
|
||||
|
||||
for word, totCount in self.corpus.items():
|
||||
# for every word in the copus
|
||||
# check to see if this pool contains this word
|
||||
thisCount = float(pool.get(word, 0.0))
|
||||
otherCount = float(totCount) - thisCount
|
||||
|
||||
if not poolCount:
|
||||
goodMetric = 1.0
|
||||
else:
|
||||
goodMetric = min(1.0, otherCount/poolCount)
|
||||
badMetric = min(1.0, thisCount/themCount)
|
||||
f = badMetric / (goodMetric + badMetric)
|
||||
|
||||
# PROBABILITY_THRESHOLD
|
||||
if abs(f-0.5) >= 0.1 :
|
||||
# GOOD_PROB, BAD_PROB
|
||||
cacheDict[word] = max(0.0001, min(0.9999, f))
|
||||
|
||||
def poolProbs(self):
|
||||
if self.dirty:
|
||||
self.buildCache()
|
||||
self.dirty = False
|
||||
return self.cache
|
||||
|
||||
def getTokens(self, obj):
|
||||
"""Hopefully it's a string and we'll just split it
|
||||
on non-alphanumeric stuff.
|
||||
|
||||
Override this in your subclass for objects other
|
||||
than text.
|
||||
|
||||
Alternatively, you can pass in a tokenizer as part of
|
||||
instance creation.
|
||||
"""
|
||||
return self.split(obj)
|
||||
|
||||
def getProbs(self, pool, words):
|
||||
""" extracts the probabilities of tokens in a message
|
||||
"""
|
||||
probs = [(word, pool[word]) for word in words if word in pool]
|
||||
probs.sort(lambda x,y: cmp(y[1],x[1]))
|
||||
return probs[:2048]
|
||||
|
||||
def train(self, pool, item, uid=None):
|
||||
"""Train Bayes by telling him that item belongs
|
||||
in pool. uid is optional and may be used to uniquely
|
||||
identify the item that is being trained on.
|
||||
"""
|
||||
tokens = self.tokenizer(item)
|
||||
pool = self.pools.setdefault(pool, self.dataClass(pool))
|
||||
self._train(pool, tokens)
|
||||
self.corpus.trainCount += 1
|
||||
pool.trainCount += 1
|
||||
if uid:
|
||||
pool.training.append(uid)
|
||||
self.dirty = True
|
||||
|
||||
def untrain(self, pool, item, uid=None):
|
||||
tokens = self.tokenizer(item)
|
||||
pool = self.pools.get(pool, None)
|
||||
if not pool:
|
||||
return
|
||||
self._untrain(pool, tokens)
|
||||
# I guess we want to count this as additional training?
|
||||
self.corpus.trainCount += 1
|
||||
pool.trainCount += 1
|
||||
if uid:
|
||||
pool.training.remove(uid)
|
||||
self.dirty = True
|
||||
|
||||
def _train(self, pool, tokens):
|
||||
wc = 0
|
||||
for token in tokens:
|
||||
count = pool.get(token, 0)
|
||||
pool[token] = count + 1
|
||||
count = self.corpus.get(token, 0)
|
||||
self.corpus[token] = count + 1
|
||||
wc += 1
|
||||
pool.tokenCount += wc
|
||||
self.corpus.tokenCount += wc
|
||||
|
||||
def _untrain(self, pool, tokens):
|
||||
for token in tokens:
|
||||
count = pool.get(token, 0)
|
||||
if count:
|
||||
if count == 1:
|
||||
del(pool[token])
|
||||
else:
|
||||
pool[token] = count - 1
|
||||
pool.tokenCount -= 1
|
||||
|
||||
count = self.corpus.get(token, 0)
|
||||
if count:
|
||||
if count == 1:
|
||||
del(self.corpus[token])
|
||||
else:
|
||||
self.corpus[token] = count - 1
|
||||
self.corpus.tokenCount -= 1
|
||||
|
||||
def trainedOn(self, msg):
|
||||
for p in self.cache.values():
|
||||
if msg in p.training:
|
||||
return True
|
||||
return False
|
||||
|
||||
def guess(self, msg):
|
||||
tokens = Set(self.tokenizer(msg))
|
||||
pools = self.poolProbs()
|
||||
|
||||
res = {}
|
||||
for pname, pprobs in pools.items():
|
||||
p = self.getProbs(pprobs, tokens)
|
||||
if len(p) != 0:
|
||||
res[pname]=self.combiner(p, pname)
|
||||
res = res.items()
|
||||
res.sort(lambda x,y: cmp(y[1], x[1]))
|
||||
return res
|
||||
|
||||
def robinson(self, probs, ignore):
|
||||
""" computes the probability of a message being spam (Robinson's method)
|
||||
P = 1 - prod(1-p)^(1/n)
|
||||
Q = 1 - prod(p)^(1/n)
|
||||
S = (1 + (P-Q)/(P+Q)) / 2
|
||||
Courtesy of http://christophe.delord.free.fr/en/index.html
|
||||
"""
|
||||
|
||||
nth = 1./len(probs)
|
||||
P = 1.0 - reduce(operator.mul, map(lambda p: 1.0-p[1], probs), 1.0) ** nth
|
||||
Q = 1.0 - reduce(operator.mul, map(lambda p: p[1], probs)) ** nth
|
||||
S = (P - Q) / (P + Q)
|
||||
return (1 + S) / 2
|
||||
|
||||
|
||||
def robinsonFisher(self, probs, ignore):
|
||||
""" computes the probability of a message being spam (Robinson-Fisher method)
|
||||
H = C-1( -2.ln(prod(p)), 2*n )
|
||||
S = C-1( -2.ln(prod(1-p)), 2*n )
|
||||
I = (1 + H - S) / 2
|
||||
Courtesy of http://christophe.delord.free.fr/en/index.html
|
||||
"""
|
||||
n = len(probs)
|
||||
try: H = chi2P(-2.0 * math.log(reduce(operator.mul, map(lambda p: p[1], probs), 1.0)), 2*n)
|
||||
except OverflowError: H = 0.0
|
||||
try: S = chi2P(-2.0 * math.log(reduce(operator.mul, map(lambda p: 1.0-p[1], probs), 1.0)), 2*n)
|
||||
except OverflowError: S = 0.0
|
||||
return (1 + H - S) / 2
|
||||
|
||||
def __repr__(self):
|
||||
return '<Bayes: %s>' % [self.pools[p] for p in self.poolNames()]
|
||||
|
||||
def __len__(self):
|
||||
return len(self.corpus)
|
||||
|
||||
|
||||
|
||||
def chi2P(chi, df):
|
||||
""" return P(chisq >= chi, with df degree of freedom)
|
||||
|
||||
df must be even
|
||||
"""
|
||||
assert df & 1 == 0
|
||||
m = chi / 2.0
|
||||
sum = term = math.exp(-m)
|
||||
for i in range(1, df/2):
|
||||
term *= m/i
|
||||
sum += term
|
||||
return min(sum, 1.0)
|
||||
|
1013
others/rfc822.py
1013
others/rfc822.py
File diff suppressed because it is too large
Load Diff
2571
others/rssparser.py
2571
others/rssparser.py
File diff suppressed because it is too large
Load Diff
218
others/shlex.py
218
others/shlex.py
@ -1,218 +0,0 @@
|
||||
"""A lexical analyzer class for simple shell-like syntaxes."""
|
||||
|
||||
# Module and documentation by Eric S. Raymond, 21 Dec 1998
|
||||
# Input stacking and error message cleanup added by ESR, March 2000
|
||||
# push_source() and pop_source() made explicit by ESR, January 2001.
|
||||
|
||||
import os.path
|
||||
import sys
|
||||
|
||||
__all__ = ["shlex"]
|
||||
|
||||
class shlex:
|
||||
"A lexical analyzer class for simple shell-like syntaxes."
|
||||
def __init__(self, instream=None, infile=None):
|
||||
if instream is not None:
|
||||
self.instream = instream
|
||||
self.infile = infile
|
||||
else:
|
||||
self.instream = sys.stdin
|
||||
self.infile = None
|
||||
self.commenters = '#'
|
||||
self.wordchars = ('abcdfeghijklmnopqrstuvwxyz'
|
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_')
|
||||
self.whitespace = ' \t\r\n'
|
||||
self.quotes = '\'"'
|
||||
self.state = ' '
|
||||
self.pushback = []
|
||||
self.lineno = 1
|
||||
self.debug = 0
|
||||
self.token = ''
|
||||
self.backslash = False
|
||||
self.filestack = []
|
||||
self.source = None
|
||||
if self.debug:
|
||||
print 'shlex: reading from %s, line %d' \
|
||||
% (self.instream, self.lineno)
|
||||
|
||||
def push_token(self, tok):
|
||||
"Push a token onto the stack popped by the get_token method"
|
||||
if self.debug >= 1:
|
||||
print "shlex: pushing token " + `tok`
|
||||
self.pushback = [tok] + self.pushback
|
||||
|
||||
def push_source(self, newstream, newfile=None):
|
||||
"Push an input source onto the lexer's input source stack."
|
||||
self.filestack.insert(0, (self.infile, self.instream, self.lineno))
|
||||
self.infile = newfile
|
||||
self.instream = newstream
|
||||
self.lineno = 1
|
||||
if self.debug:
|
||||
if newfile is not None:
|
||||
print 'shlex: pushing to file %s' % (self.infile,)
|
||||
else:
|
||||
print 'shlex: pushing to stream %s' % (self.instream,)
|
||||
|
||||
def pop_source(self):
|
||||
"Pop the input source stack."
|
||||
self.instream.close()
|
||||
(self.infile, self.instream, self.lineno) = self.filestack[0]
|
||||
self.filestack = self.filestack[1:]
|
||||
if self.debug:
|
||||
print 'shlex: popping to %s, line %d' \
|
||||
% (self.instream, self.lineno)
|
||||
self.state = ' '
|
||||
|
||||
def get_token(self):
|
||||
"Get a token from the input stream (or from stack if it's nonempty)"
|
||||
if self.pushback:
|
||||
tok = self.pushback[0]
|
||||
self.pushback = self.pushback[1:]
|
||||
if self.debug >= 1:
|
||||
print "shlex: popping token " + `tok`
|
||||
return tok
|
||||
# No pushback. Get a token.
|
||||
raw = self.read_token()
|
||||
# Handle inclusions
|
||||
while raw == self.source:
|
||||
spec = self.sourcehook(self.read_token())
|
||||
if spec:
|
||||
(newfile, newstream) = spec
|
||||
self.push_source(newstream, newfile)
|
||||
raw = self.get_token()
|
||||
# Maybe we got EOF instead?
|
||||
while raw == "":
|
||||
if len(self.filestack) == 0:
|
||||
return ""
|
||||
else:
|
||||
self.pop_source()
|
||||
raw = self.get_token()
|
||||
# Neither inclusion nor EOF
|
||||
if self.debug >= 1:
|
||||
if raw:
|
||||
print "shlex: token=" + `raw`
|
||||
else:
|
||||
print "shlex: token=EOF"
|
||||
return raw
|
||||
|
||||
def read_token(self):
|
||||
"Read a token from the input stream (no pushback or inclusions)"
|
||||
while 1:
|
||||
nextchar = self.instream.read(1)
|
||||
if nextchar == '\n':
|
||||
self.lineno = self.lineno + 1
|
||||
if self.debug >= 3:
|
||||
print "shlex: in state", repr(self.state), \
|
||||
"I see character:", repr(nextchar)
|
||||
if self.state is None:
|
||||
self.token = '' # past end of file
|
||||
break
|
||||
elif self.state == ' ':
|
||||
if not nextchar:
|
||||
self.state = None # end of file
|
||||
break
|
||||
elif nextchar in self.whitespace:
|
||||
if self.debug >= 2:
|
||||
print "shlex: I see whitespace in whitespace state"
|
||||
if self.token:
|
||||
break # emit current token
|
||||
else:
|
||||
continue
|
||||
elif nextchar in self.commenters:
|
||||
self.instream.readline()
|
||||
self.lineno = self.lineno + 1
|
||||
elif nextchar in self.wordchars:
|
||||
self.token = nextchar
|
||||
self.state = 'a'
|
||||
elif nextchar in self.quotes:
|
||||
self.token = nextchar
|
||||
self.state = nextchar
|
||||
else:
|
||||
self.token = nextchar
|
||||
if self.token:
|
||||
break # emit current token
|
||||
else:
|
||||
continue
|
||||
elif self.state in self.quotes:
|
||||
self.token = self.token + nextchar
|
||||
if nextchar == '\\':
|
||||
if self.backslash:
|
||||
self.backslash = False
|
||||
else:
|
||||
self.backslash = True
|
||||
else:
|
||||
if not self.backslash and nextchar == self.state:
|
||||
self.state = ' '
|
||||
break
|
||||
elif self.backslash:
|
||||
self.backslash = False
|
||||
elif not nextchar: # end of file
|
||||
if self.debug >= 2:
|
||||
print "shlex: I see EOF in quotes state"
|
||||
# XXX what error should be raised here?
|
||||
raise ValueError, "No closing quotation"
|
||||
elif self.state == 'a':
|
||||
if not nextchar:
|
||||
self.state = None # end of file
|
||||
break
|
||||
elif nextchar in self.whitespace:
|
||||
if self.debug >= 2:
|
||||
print "shlex: I see whitespace in word state"
|
||||
self.state = ' '
|
||||
if self.token:
|
||||
break # emit current token
|
||||
else:
|
||||
continue
|
||||
elif nextchar in self.commenters:
|
||||
self.instream.readline()
|
||||
self.lineno = self.lineno + 1
|
||||
elif nextchar in self.wordchars or nextchar in self.quotes:
|
||||
self.token = self.token + nextchar
|
||||
else:
|
||||
self.pushback = [nextchar] + self.pushback
|
||||
if self.debug >= 2:
|
||||
print "shlex: I see punctuation in word state"
|
||||
self.state = ' '
|
||||
if self.token:
|
||||
break # emit current token
|
||||
else:
|
||||
continue
|
||||
result = self.token
|
||||
self.token = ''
|
||||
if self.debug > 1:
|
||||
if result:
|
||||
print "shlex: raw token=" + `result`
|
||||
else:
|
||||
print "shlex: raw token=EOF"
|
||||
return result
|
||||
|
||||
def sourcehook(self, newfile):
|
||||
"Hook called on a filename to be sourced."
|
||||
if newfile[0] == '"':
|
||||
newfile = newfile[1:-1]
|
||||
# This implements cpp-like semantics for relative-path inclusion.
|
||||
if type(self.infile) == type("") and not os.path.isabs(newfile):
|
||||
newfile = os.path.join(os.path.dirname(self.infile), newfile)
|
||||
return (newfile, open(newfile, "r"))
|
||||
|
||||
def error_leader(self, infile=None, lineno=None):
|
||||
"Emit a C-compiler-like, Emacs-friendly error-message leader."
|
||||
if infile is None:
|
||||
infile = self.infile
|
||||
if lineno is None:
|
||||
lineno = self.lineno
|
||||
return "\"%s\", line %d: " % (infile, lineno)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) == 1:
|
||||
lexer = shlex()
|
||||
else:
|
||||
file = sys.argv[1]
|
||||
lexer = shlex(open(file), file)
|
||||
while 1:
|
||||
tt = lexer.get_token()
|
||||
if tt:
|
||||
print "Token: " + repr(tt)
|
||||
else:
|
||||
break
|
@ -1,784 +0,0 @@
|
||||
'''
|
||||
Python unit testing framework, based on Erich Gamma's JUnit and Kent Beck's
|
||||
Smalltalk testing framework.
|
||||
|
||||
This module contains the core framework classes that form the basis of
|
||||
specific test cases and suites (TestCase, TestSuite etc.), and also a
|
||||
text-based utility class for running the tests and reporting the results
|
||||
(TextTestRunner).
|
||||
|
||||
Simple usage:
|
||||
|
||||
import unittest
|
||||
|
||||
class IntegerArithmenticTestCase(unittest.TestCase):
|
||||
def testAdd(self): ## test method names begin 'test*'
|
||||
self.assertEquals((1 + 2), 3)
|
||||
self.assertEquals(0 + 1, 1)
|
||||
def testMultiply(self):
|
||||
self.assertEquals((0 * 10), 0)
|
||||
self.assertEquals((5 * 8), 40)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
Further information is available in the bundled documentation, and from
|
||||
|
||||
http://pyunit.sourceforge.net/
|
||||
|
||||
Copyright (c) 1999, 2000, 2001 Steve Purcell
|
||||
This module is free software, and you may redistribute it and/or modify
|
||||
it under the same terms as Python itself, so long as this copyright message
|
||||
and disclaimer are retained in their original form.
|
||||
|
||||
IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
|
||||
SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF
|
||||
THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||
DAMAGE.
|
||||
|
||||
THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||
PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS,
|
||||
AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
|
||||
SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
|
||||
'''
|
||||
|
||||
__author__ = "Steve Purcell"
|
||||
__email__ = "stephen_purcell at yahoo dot com"
|
||||
__version__ = "#Revision: 1.46 $"[11:-2]
|
||||
|
||||
import time
|
||||
import sys
|
||||
import traceback
|
||||
import string
|
||||
import os
|
||||
import types
|
||||
|
||||
###
|
||||
# Globals
|
||||
###
|
||||
asserts = 0
|
||||
|
||||
##############################################################################
|
||||
# Test framework core
|
||||
##############################################################################
|
||||
|
||||
# All classes defined herein are 'new-style' classes, allowing use of 'super()'
|
||||
__metaclass__ = type
|
||||
|
||||
def _strclass(cls):
|
||||
return "%s.%s" % (cls.__module__, cls.__name__)
|
||||
|
||||
class TestResult:
|
||||
"""Holder for test result information.
|
||||
|
||||
Test results are automatically managed by the TestCase and TestSuite
|
||||
classes, and do not need to be explicitly manipulated by writers of tests.
|
||||
|
||||
Each instance holds the total number of tests run, and collections of
|
||||
failures and errors that occurred among those test runs. The collections
|
||||
contain tuples of (testcase, exceptioninfo), where exceptioninfo is the
|
||||
formatted traceback of the error that occurred.
|
||||
"""
|
||||
def __init__(self):
|
||||
self.failures = []
|
||||
self.errors = []
|
||||
self.testsRun = 0
|
||||
self.shouldStop = 0
|
||||
|
||||
def startTest(self, test):
|
||||
"Called when the given test is about to be run"
|
||||
self.testsRun = self.testsRun + 1
|
||||
|
||||
def stopTest(self, test):
|
||||
"Called when the given test has been run"
|
||||
pass
|
||||
|
||||
def addError(self, test, err):
|
||||
"""Called when an error has occurred. 'err' is a tuple of values as
|
||||
returned by sys.exc_info().
|
||||
"""
|
||||
self.errors.append((test, self._exc_info_to_string(err)))
|
||||
|
||||
def addFailure(self, test, err):
|
||||
"""Called when an error has occurred. 'err' is a tuple of values as
|
||||
returned by sys.exc_info()."""
|
||||
self.failures.append((test, self._exc_info_to_string(err)))
|
||||
|
||||
def addSuccess(self, test):
|
||||
"Called when a test has completed successfully"
|
||||
pass
|
||||
|
||||
def wasSuccessful(self):
|
||||
"Tells whether or not this result was a success"
|
||||
return len(self.failures) == len(self.errors) == 0
|
||||
|
||||
def stop(self):
|
||||
"Indicates that the tests should be aborted"
|
||||
self.shouldStop = 1
|
||||
|
||||
def _exc_info_to_string(self, err):
|
||||
"""Converts a sys.exc_info()-style tuple of values into a string."""
|
||||
return string.join(traceback.format_exception(*err), '')
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s run=%i errors=%i failures=%i>" % \
|
||||
(_strclass(self.__class__), self.testsRun, len(self.errors),
|
||||
len(self.failures))
|
||||
|
||||
|
||||
class TestCase:
|
||||
"""A class whose instances are single test cases.
|
||||
|
||||
By default, the test code itself should be placed in a method named
|
||||
'runTest'.
|
||||
|
||||
If the fixture may be used for many test cases, create as
|
||||
many test methods as are needed. When instantiating such a TestCase
|
||||
subclass, specify in the constructor arguments the name of the test method
|
||||
that the instance is to execute.
|
||||
|
||||
Test authors should subclass TestCase for their own tests. Construction
|
||||
and deconstruction of the test's environment ('fixture') can be
|
||||
implemented by overriding the 'setUp' and 'tearDown' methods respectively.
|
||||
|
||||
If it is necessary to override the __init__ method, the base class
|
||||
__init__ method must always be called. It is important that subclasses
|
||||
should not change the signature of their __init__ method, since instances
|
||||
of the classes are instantiated automatically by parts of the framework
|
||||
in order to be run.
|
||||
"""
|
||||
|
||||
# This attribute determines which exception will be raised when
|
||||
# the instance's assertion methods fail; test methods raising this
|
||||
# exception will be deemed to have 'failed' rather than 'errored'
|
||||
|
||||
failureException = AssertionError
|
||||
|
||||
def __init__(self, methodName='runTest'):
|
||||
"""Create an instance of the class that will use the named test
|
||||
method when executed. Raises a ValueError if the instance does
|
||||
not have a method with the specified name.
|
||||
"""
|
||||
try:
|
||||
self.__testMethodName = methodName
|
||||
testMethod = getattr(self, methodName)
|
||||
self.__testMethodDoc = testMethod.__doc__
|
||||
except AttributeError:
|
||||
raise ValueError, "no such test method in %s: %s" % \
|
||||
(self.__class__, methodName)
|
||||
|
||||
def setUp(self):
|
||||
"Hook method for setting up the test fixture before exercising it."
|
||||
pass
|
||||
|
||||
def tearDown(self):
|
||||
"Hook method for deconstructing the test fixture after testing it."
|
||||
pass
|
||||
|
||||
def countTestCases(self):
|
||||
return 1
|
||||
|
||||
def defaultTestResult(self):
|
||||
return TestResult()
|
||||
|
||||
def shortDescription(self):
|
||||
"""Returns a one-line description of the test, or None if no
|
||||
description has been provided.
|
||||
|
||||
The default implementation of this method returns the first line of
|
||||
the specified test method's docstring.
|
||||
"""
|
||||
doc = self.__testMethodDoc
|
||||
return doc and string.strip(string.split(doc, "\n")[0]) or None
|
||||
|
||||
def id(self):
|
||||
return "%s.%s" % (_strclass(self.__class__), self.__testMethodName)
|
||||
|
||||
def __str__(self):
|
||||
return "%s (%s)" % (self.__testMethodName, _strclass(self.__class__))
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s testMethod=%s>" % \
|
||||
(_strclass(self.__class__), self.__testMethodName)
|
||||
|
||||
def run(self, result=None):
|
||||
return self(result)
|
||||
|
||||
def __call__(self, result=None):
|
||||
if result is None: result = self.defaultTestResult()
|
||||
result.startTest(self)
|
||||
testMethod = getattr(self, self.__testMethodName)
|
||||
try:
|
||||
try:
|
||||
x = self.setUp()
|
||||
if x:
|
||||
print 'skipped (%s)' % x
|
||||
return
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except:
|
||||
result.addError(self, self.__exc_info())
|
||||
return
|
||||
|
||||
ok = 0
|
||||
try:
|
||||
testMethod()
|
||||
ok = 1
|
||||
except self.failureException, e:
|
||||
result.addFailure(self, self.__exc_info())
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except:
|
||||
result.addError(self, self.__exc_info())
|
||||
|
||||
try:
|
||||
self.tearDown()
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except:
|
||||
result.addError(self, self.__exc_info())
|
||||
ok = 0
|
||||
if ok: result.addSuccess(self)
|
||||
finally:
|
||||
result.stopTest(self)
|
||||
|
||||
def debug(self):
|
||||
"""Run the test without collecting errors in a TestResult"""
|
||||
self.setUp()
|
||||
getattr(self, self.__testMethodName)()
|
||||
self.tearDown()
|
||||
|
||||
def __exc_info(self):
|
||||
"""Return a version of sys.exc_info() with the traceback frame
|
||||
minimised; usually the top level of the traceback frame is not
|
||||
needed.
|
||||
"""
|
||||
exctype, excvalue, tb = sys.exc_info()
|
||||
if sys.platform[:4] == 'java': ## tracebacks look different in Jython
|
||||
return (exctype, excvalue, tb)
|
||||
newtb = tb.tb_next
|
||||
if newtb is None:
|
||||
return (exctype, excvalue, tb)
|
||||
return (exctype, excvalue, newtb)
|
||||
|
||||
def _fail(self, msg):
|
||||
"""Underlying implementation of failure."""
|
||||
raise self.failureException, msg
|
||||
|
||||
def fail(self, msg=None):
|
||||
"""Fail immediately, with the given message."""
|
||||
global asserts
|
||||
asserts += 1
|
||||
self._fail(msg)
|
||||
|
||||
def failIf(self, expr, msg=None):
|
||||
"Fail the test if the expression is true."
|
||||
global asserts
|
||||
asserts += 1
|
||||
if expr: self._fail(msg)
|
||||
|
||||
def failUnless(self, expr, msg=None):
|
||||
"""Fail the test unless the expression is true."""
|
||||
global asserts
|
||||
asserts += 1
|
||||
if not expr: self._fail(msg)
|
||||
|
||||
def failUnlessRaises(self, excClass, callableObj, *args, **kwargs):
|
||||
"""Fail unless an exception of class excClass is thrown
|
||||
by callableObj when invoked with arguments args and keyword
|
||||
arguments kwargs. If a different type of exception is
|
||||
thrown, it will not be caught, and the test case will be
|
||||
deemed to have suffered an error, exactly as for an
|
||||
unexpected exception.
|
||||
"""
|
||||
global asserts
|
||||
asserts += 1
|
||||
try:
|
||||
callableObj(*args, **kwargs)
|
||||
except excClass:
|
||||
return
|
||||
else:
|
||||
if hasattr(excClass,'__name__'): excName = excClass.__name__
|
||||
else: excName = str(excClass)
|
||||
raise self._fail(excName)
|
||||
|
||||
def failUnlessEqual(self, first, second, msg=None):
|
||||
"""Fail if the two objects are unequal as determined by the '=='
|
||||
operator.
|
||||
"""
|
||||
global asserts
|
||||
asserts += 1
|
||||
if not first == second:
|
||||
self._fail(msg or '%s != %s' % (`first`, `second`))
|
||||
|
||||
def failIfEqual(self, first, second, msg=None):
|
||||
"""Fail if the two objects are equal as determined by the '=='
|
||||
operator.
|
||||
"""
|
||||
global asserts
|
||||
asserts += 1
|
||||
if first == second:
|
||||
self._fail(msg or '%s == %s' % (`first`, `second`))
|
||||
|
||||
def failUnlessAlmostEqual(self, first, second, places=7, msg=None):
|
||||
"""Fail if the two objects are unequal as determined by their
|
||||
difference rounded to the given number of decimal places
|
||||
(default 7) and comparing to zero.
|
||||
|
||||
Note that decimal places (from zero) is usually not the same
|
||||
as significant digits (measured from the most signficant digit).
|
||||
"""
|
||||
global asserts
|
||||
asserts += 1
|
||||
if round(second-first, places) != 0:
|
||||
self._fail(msg or '%s != %s within %s places' % \
|
||||
(`first`, `second`, `places`))
|
||||
|
||||
def failIfAlmostEqual(self, first, second, places=7, msg=None):
|
||||
"""Fail if the two objects are equal as determined by their
|
||||
difference rounded to the given number of decimal places
|
||||
(default 7) and comparing to zero.
|
||||
|
||||
Note that decimal places (from zero) is usually not the same
|
||||
as significant digits (measured from the most signficant digit).
|
||||
"""
|
||||
global asserts
|
||||
asserts += 1
|
||||
if round(second-first, places) == 0:
|
||||
self._fail(msg or '%s == %s within %s places' % \
|
||||
(`first`, `second`, `places`))
|
||||
|
||||
assertEqual = assertEquals = failUnlessEqual
|
||||
|
||||
assertNotEqual = assertNotEquals = failIfEqual
|
||||
|
||||
assertAlmostEqual = assertAlmostEquals = failUnlessAlmostEqual
|
||||
|
||||
assertNotAlmostEqual = assertNotAlmostEquals = failIfAlmostEqual
|
||||
|
||||
assertRaises = failUnlessRaises
|
||||
|
||||
assert_ = failUnless
|
||||
|
||||
|
||||
|
||||
class TestSuite:
|
||||
"""A test suite is a composite test consisting of a number of TestCases.
|
||||
|
||||
For use, create an instance of TestSuite, then add test case instances.
|
||||
When all tests have been added, the suite can be passed to a test
|
||||
runner, such as TextTestRunner. It will run the individual test cases
|
||||
in the order in which they were added, aggregating the results. When
|
||||
subclassing, do not forget to call the base class constructor.
|
||||
"""
|
||||
def __init__(self, tests=()):
|
||||
self._tests = []
|
||||
self.addTests(tests)
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s tests=%s>" % (_strclass(self.__class__), self._tests)
|
||||
|
||||
__str__ = __repr__
|
||||
|
||||
def countTestCases(self):
|
||||
cases = 0
|
||||
for test in self._tests:
|
||||
cases = cases + test.countTestCases()
|
||||
return cases
|
||||
|
||||
def addTest(self, test):
|
||||
self._tests.append(test)
|
||||
|
||||
def addTests(self, tests):
|
||||
for test in tests:
|
||||
self.addTest(test)
|
||||
|
||||
def run(self, result):
|
||||
return self(result)
|
||||
|
||||
def __call__(self, result):
|
||||
for test in self._tests:
|
||||
if result.shouldStop:
|
||||
break
|
||||
test(result)
|
||||
return result
|
||||
|
||||
def debug(self):
|
||||
"""Run the tests without collecting errors in a TestResult"""
|
||||
for test in self._tests: test.debug()
|
||||
|
||||
|
||||
class FunctionTestCase(TestCase):
|
||||
"""A test case that wraps a test function.
|
||||
|
||||
This is useful for slipping pre-existing test functions into the
|
||||
PyUnit framework. Optionally, set-up and tidy-up functions can be
|
||||
supplied. As with TestCase, the tidy-up ('tearDown') function will
|
||||
always be called if the set-up ('setUp') function ran successfully.
|
||||
"""
|
||||
|
||||
def __init__(self, testFunc, setUp=None, tearDown=None,
|
||||
description=None):
|
||||
TestCase.__init__(self)
|
||||
self.__setUpFunc = setUp
|
||||
self.__tearDownFunc = tearDown
|
||||
self.__testFunc = testFunc
|
||||
self.__description = description
|
||||
|
||||
def setUp(self):
|
||||
if self.__setUpFunc is not None:
|
||||
self.__setUpFunc()
|
||||
|
||||
def tearDown(self):
|
||||
if self.__tearDownFunc is not None:
|
||||
self.__tearDownFunc()
|
||||
|
||||
def runTest(self):
|
||||
self.__testFunc()
|
||||
|
||||
def id(self):
|
||||
return self.__testFunc.__name__
|
||||
|
||||
def __str__(self):
|
||||
return "%s (%s)" % (_strclass(self.__class__), self.__testFunc.__name__)
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s testFunc=%s>" % (_strclass(self.__class__), self.__testFunc)
|
||||
|
||||
def shortDescription(self):
|
||||
if self.__description is not None: return self.__description
|
||||
doc = self.__testFunc.__doc__
|
||||
return doc and string.strip(string.split(doc, "\n")[0]) or None
|
||||
|
||||
|
||||
|
||||
##############################################################################
|
||||
# Locating and loading tests
|
||||
##############################################################################
|
||||
|
||||
class TestLoader:
|
||||
"""This class is responsible for loading tests according to various
|
||||
criteria and returning them wrapped in a Test
|
||||
"""
|
||||
testMethodPrefix = 'test'
|
||||
sortTestMethodsUsing = cmp
|
||||
suiteClass = TestSuite
|
||||
|
||||
def loadTestsFromTestCase(self, testCaseClass):
|
||||
"""Return a suite of all tests cases contained in testCaseClass"""
|
||||
return self.suiteClass(map(testCaseClass,
|
||||
self.getTestCaseNames(testCaseClass)))
|
||||
|
||||
def loadTestsFromModule(self, module):
|
||||
"""Return a suite of all tests cases contained in the given module"""
|
||||
tests = []
|
||||
for name in dir(module):
|
||||
obj = getattr(module, name)
|
||||
if (isinstance(obj, (type, types.ClassType)) and
|
||||
issubclass(obj, TestCase)):
|
||||
tests.append(self.loadTestsFromTestCase(obj))
|
||||
return self.suiteClass(tests)
|
||||
|
||||
def loadTestsFromName(self, name, module=None):
|
||||
"""Return a suite of all tests cases given a string specifier.
|
||||
|
||||
The name may resolve either to a module, a test case class, a
|
||||
test method within a test case class, or a callable object which
|
||||
returns a TestCase or TestSuite instance.
|
||||
|
||||
The method optionally resolves the names relative to a given module.
|
||||
"""
|
||||
parts = string.split(name, '.')
|
||||
if module is None:
|
||||
if not parts:
|
||||
raise ValueError, "incomplete test name: %s" % name
|
||||
else:
|
||||
parts_copy = parts[:]
|
||||
while parts_copy:
|
||||
try:
|
||||
module = __import__(string.join(parts_copy,'.'))
|
||||
break
|
||||
except ImportError:
|
||||
del parts_copy[-1]
|
||||
if not parts_copy: raise
|
||||
parts = parts[1:]
|
||||
obj = module
|
||||
for part in parts:
|
||||
obj = getattr(obj, part)
|
||||
|
||||
import unittest
|
||||
if type(obj) == types.ModuleType:
|
||||
return self.loadTestsFromModule(obj)
|
||||
elif (isinstance(obj, (type, types.ClassType)) and
|
||||
issubclass(obj, unittest.TestCase)):
|
||||
return self.loadTestsFromTestCase(obj)
|
||||
elif type(obj) == types.UnboundMethodType:
|
||||
return obj.im_class(obj.__name__)
|
||||
elif callable(obj):
|
||||
test = obj()
|
||||
if not isinstance(test, unittest.TestCase) and \
|
||||
not isinstance(test, unittest.TestSuite):
|
||||
raise ValueError, \
|
||||
"calling %s returned %s, not a test" % (obj,test)
|
||||
return test
|
||||
else:
|
||||
raise ValueError, "don't know how to make test from: %s" % obj
|
||||
|
||||
def loadTestsFromNames(self, names, module=None):
|
||||
"""Return a suite of all tests cases found using the given sequence
|
||||
of string specifiers. See 'loadTestsFromName()'.
|
||||
"""
|
||||
suites = []
|
||||
for name in names:
|
||||
suites.append(self.loadTestsFromName(name, module))
|
||||
return self.suiteClass(suites)
|
||||
|
||||
def getTestCaseNames(self, testCaseClass):
|
||||
"""Return a sorted sequence of method names found within testCaseClass
|
||||
"""
|
||||
testFnNames = filter(lambda n,p=self.testMethodPrefix: n[:len(p)] == p,
|
||||
dir(testCaseClass))
|
||||
for baseclass in testCaseClass.__bases__:
|
||||
for testFnName in self.getTestCaseNames(baseclass):
|
||||
if testFnName not in testFnNames: # handle overridden methods
|
||||
testFnNames.append(testFnName)
|
||||
if self.sortTestMethodsUsing:
|
||||
testFnNames.sort(self.sortTestMethodsUsing)
|
||||
return testFnNames
|
||||
|
||||
|
||||
|
||||
defaultTestLoader = TestLoader()
|
||||
|
||||
|
||||
##############################################################################
|
||||
# Patches for old functions: these functions should be considered obsolete
|
||||
##############################################################################
|
||||
|
||||
def _makeLoader(prefix, sortUsing, suiteClass=None):
|
||||
loader = TestLoader()
|
||||
loader.sortTestMethodsUsing = sortUsing
|
||||
loader.testMethodPrefix = prefix
|
||||
if suiteClass: loader.suiteClass = suiteClass
|
||||
return loader
|
||||
|
||||
def getTestCaseNames(testCaseClass, prefix, sortUsing=cmp):
|
||||
return _makeLoader(prefix, sortUsing).getTestCaseNames(testCaseClass)
|
||||
|
||||
def makeSuite(testCaseClass, prefix='test', sortUsing=cmp, suiteClass=TestSuite):
|
||||
return _makeLoader(prefix, sortUsing, suiteClass).loadTestsFromTestCase(testCaseClass)
|
||||
|
||||
def findTestCases(module, prefix='test', sortUsing=cmp, suiteClass=TestSuite):
|
||||
return _makeLoader(prefix, sortUsing, suiteClass).loadTestsFromModule(module)
|
||||
|
||||
|
||||
##############################################################################
|
||||
# Text UI
|
||||
##############################################################################
|
||||
|
||||
class _WritelnDecorator:
|
||||
"""Used to decorate file-like objects with a handy 'writeln' method"""
|
||||
def __init__(self,stream):
|
||||
self.stream = stream
|
||||
|
||||
def __getattr__(self, attr):
|
||||
return getattr(self.stream,attr)
|
||||
|
||||
def writeln(self, *args):
|
||||
if args: self.write(*args)
|
||||
self.write('\n') # text-mode streams translate to \r\n if needed
|
||||
|
||||
|
||||
class _TextTestResult(TestResult):
|
||||
"""A test result class that can print formatted text results to a stream.
|
||||
|
||||
Used by TextTestRunner.
|
||||
"""
|
||||
separator1 = '=' * 70
|
||||
separator2 = '-' * 70
|
||||
|
||||
def __init__(self, stream, descriptions, verbosity):
|
||||
TestResult.__init__(self)
|
||||
self.stream = stream
|
||||
self.showAll = verbosity > 1
|
||||
self.dots = verbosity == 1
|
||||
self.descriptions = descriptions
|
||||
|
||||
def getDescription(self, test):
|
||||
if self.descriptions:
|
||||
return test.shortDescription() or str(test)
|
||||
else:
|
||||
return str(test)
|
||||
|
||||
def startTest(self, test):
|
||||
TestResult.startTest(self, test)
|
||||
if self.showAll:
|
||||
self.stream.write(self.getDescription(test))
|
||||
self.stream.write(" ... ")
|
||||
|
||||
def addSuccess(self, test):
|
||||
TestResult.addSuccess(self, test)
|
||||
if self.showAll:
|
||||
self.stream.writeln("ok")
|
||||
elif self.dots:
|
||||
self.stream.write('.')
|
||||
|
||||
def addError(self, test, err):
|
||||
TestResult.addError(self, test, err)
|
||||
if self.showAll:
|
||||
self.stream.writeln("ERROR")
|
||||
elif self.dots:
|
||||
self.stream.write('E')
|
||||
|
||||
def addFailure(self, test, err):
|
||||
TestResult.addFailure(self, test, err)
|
||||
if self.showAll:
|
||||
self.stream.writeln("FAIL")
|
||||
elif self.dots:
|
||||
self.stream.write('F')
|
||||
|
||||
def printErrors(self):
|
||||
if self.dots or self.showAll:
|
||||
self.stream.writeln()
|
||||
self.printErrorList('ERROR', self.errors)
|
||||
self.printErrorList('FAIL', self.failures)
|
||||
|
||||
def printErrorList(self, flavour, errors):
|
||||
for test, err in errors:
|
||||
self.stream.writeln(self.separator1)
|
||||
self.stream.writeln("%s: %s" % (flavour,self.getDescription(test)))
|
||||
self.stream.writeln(self.separator2)
|
||||
self.stream.writeln("%s" % err)
|
||||
|
||||
|
||||
class TextTestRunner:
|
||||
"""A test runner class that displays results in textual form.
|
||||
|
||||
It prints out the names of tests as they are run, errors as they
|
||||
occur, and a summary of the results at the end of the test run.
|
||||
"""
|
||||
def __init__(self, stream=sys.stderr, descriptions=1, verbosity=1):
|
||||
self.stream = _WritelnDecorator(stream)
|
||||
self.descriptions = descriptions
|
||||
self.verbosity = verbosity
|
||||
|
||||
def _makeResult(self):
|
||||
return _TextTestResult(self.stream, self.descriptions, self.verbosity)
|
||||
|
||||
def run(self, test):
|
||||
"Run the given test case or test suite."
|
||||
result = self._makeResult()
|
||||
startTime = time.time()
|
||||
test(result)
|
||||
stopTime = time.time()
|
||||
timeTaken = float(stopTime - startTime)
|
||||
result.printErrors()
|
||||
self.stream.writeln(result.separator2)
|
||||
run = result.testsRun
|
||||
self.stream.writeln("Ran %d test%s in %.3fs" %
|
||||
(run, run != 1 and "s" or "", timeTaken))
|
||||
self.stream.writeln()
|
||||
if not result.wasSuccessful():
|
||||
self.stream.write("FAILED (")
|
||||
failed, errored = map(len, (result.failures, result.errors))
|
||||
if failed:
|
||||
self.stream.write("failures=%d" % failed)
|
||||
if errored:
|
||||
if failed: self.stream.write(", ")
|
||||
self.stream.write("errors=%d" % errored)
|
||||
self.stream.writeln(")")
|
||||
else:
|
||||
self.stream.writeln("OK")
|
||||
return result
|
||||
|
||||
|
||||
|
||||
##############################################################################
|
||||
# Facilities for running tests from the command line
|
||||
##############################################################################
|
||||
|
||||
class TestProgram:
|
||||
"""A command-line program that runs a set of tests; this is primarily
|
||||
for making test modules conveniently executable.
|
||||
"""
|
||||
USAGE = """\
|
||||
Usage: %(progName)s [options] [test] [...]
|
||||
|
||||
Options:
|
||||
-h, --help Show this message
|
||||
-v, --verbose Verbose output
|
||||
-q, --quiet Minimal output
|
||||
|
||||
Examples:
|
||||
%(progName)s - run default set of tests
|
||||
%(progName)s MyTestSuite - run suite 'MyTestSuite'
|
||||
%(progName)s MyTestCase.testSomething - run MyTestCase.testSomething
|
||||
%(progName)s MyTestCase - run all 'test*' test methods
|
||||
in MyTestCase
|
||||
"""
|
||||
def __init__(self, module='__main__', defaultTest=None,
|
||||
argv=None, testRunner=None, testLoader=defaultTestLoader):
|
||||
if type(module) == type(''):
|
||||
self.module = __import__(module)
|
||||
for part in string.split(module,'.')[1:]:
|
||||
self.module = getattr(self.module, part)
|
||||
else:
|
||||
self.module = module
|
||||
if argv is None:
|
||||
argv = sys.argv
|
||||
self.verbosity = 1
|
||||
self.defaultTest = defaultTest
|
||||
self.testRunner = testRunner
|
||||
self.testLoader = testLoader
|
||||
self.progName = os.path.basename(argv[0])
|
||||
self.parseArgs(argv)
|
||||
self.runTests()
|
||||
|
||||
def usageExit(self, msg=None):
|
||||
if msg: print msg
|
||||
print self.USAGE % self.__dict__
|
||||
sys.exit(2)
|
||||
|
||||
def parseArgs(self, argv):
|
||||
import getopt
|
||||
try:
|
||||
options, args = getopt.getopt(argv[1:], 'hHvq',
|
||||
['help','verbose','quiet'])
|
||||
for opt, value in options:
|
||||
if opt in ('-h','-H','--help'):
|
||||
self.usageExit()
|
||||
if opt in ('-q','--quiet'):
|
||||
self.verbosity = 0
|
||||
if opt in ('-v','--verbose'):
|
||||
self.verbosity = 2
|
||||
if len(args) == 0 and self.defaultTest is None:
|
||||
self.test = self.testLoader.loadTestsFromModule(self.module)
|
||||
return
|
||||
if len(args) > 0:
|
||||
self.testNames = args
|
||||
else:
|
||||
self.testNames = (self.defaultTest,)
|
||||
self.createTests()
|
||||
except getopt.error, msg:
|
||||
self.usageExit(msg)
|
||||
|
||||
def createTests(self):
|
||||
self.test = self.testLoader.loadTestsFromNames(self.testNames,
|
||||
self.module)
|
||||
|
||||
def runTests(self):
|
||||
if self.testRunner is None:
|
||||
self.testRunner = TextTestRunner(verbosity=self.verbosity)
|
||||
result = self.testRunner.run(self.test)
|
||||
sys.exit(not result.wasSuccessful())
|
||||
|
||||
main = TestProgram
|
||||
|
||||
|
||||
##############################################################################
|
||||
# Executing this module from the command line
|
||||
##############################################################################
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(module=None)
|
1208
others/urllib2.py
1208
others/urllib2.py
File diff suppressed because it is too large
Load Diff
302
plugins/Alias.py
302
plugins/Alias.py
@ -1,302 +0,0 @@
|
||||
###
|
||||
# Copyright (c) 2002-2004, Jeremiah Fincher
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions, and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions, and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
# * Neither the name of the author of this software nor the name of
|
||||
# contributors to this software may be used to endorse or promote products
|
||||
# derived from this software without specific prior written consent.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
###
|
||||
|
||||
"""
|
||||
Allows aliases for other commands.
|
||||
"""
|
||||
|
||||
import supybot
|
||||
|
||||
__revision__ = "$Id$"
|
||||
__author__ = supybot.authors.jemfinch
|
||||
|
||||
import supybot.plugins as plugins
|
||||
|
||||
import os
|
||||
import re
|
||||
import sets
|
||||
|
||||
import supybot.conf as conf
|
||||
import supybot.utils as utils
|
||||
from supybot.commands import *
|
||||
import supybot.privmsgs as privmsgs
|
||||
import supybot.registry as registry
|
||||
import supybot.callbacks as callbacks
|
||||
import supybot.structures as structures
|
||||
import supybot.unpreserve as unpreserve
|
||||
|
||||
class AliasError(Exception):
|
||||
pass
|
||||
|
||||
class RecursiveAlias(AliasError):
|
||||
pass
|
||||
|
||||
def findAliasCommand(s, alias):
|
||||
s = re.escape(s)
|
||||
r = re.compile(r'(?:(^|\[)\s*\b%s\b|\|\s*\b%s\b)' % (s, s))
|
||||
return bool(r.search(alias))
|
||||
|
||||
dollarRe = re.compile(r'\$(\d+)')
|
||||
def findBiggestDollar(alias):
|
||||
dollars = dollarRe.findall(alias)
|
||||
dollars = map(int, dollars)
|
||||
dollars.sort()
|
||||
if dollars:
|
||||
return dollars[-1]
|
||||
else:
|
||||
return 0
|
||||
|
||||
atRe = re.compile(r'@(\d+)')
|
||||
def findBiggestAt(alias):
|
||||
ats = atRe.findall(alias)
|
||||
ats = map(int, ats)
|
||||
ats.sort()
|
||||
if ats:
|
||||
return ats[-1]
|
||||
else:
|
||||
return 0
|
||||
|
||||
def makeNewAlias(name, alias):
|
||||
original = alias
|
||||
if findAliasCommand(name, alias):
|
||||
raise RecursiveAlias
|
||||
biggestDollar = findBiggestDollar(original)
|
||||
biggestAt = findBiggestAt(original)
|
||||
wildcard = '$*' in original
|
||||
if biggestAt and wildcard:
|
||||
raise AliasError, 'Can\'t mix $* and optional args (@1, etc.)'
|
||||
if original.count('$*') > 1:
|
||||
raise AliasError, 'There can be only one $* in an alias.'
|
||||
testTokens = callbacks.tokenize(original)
|
||||
if testTokens and isinstance(testTokens[0], list):
|
||||
raise AliasError, 'Commands may not be the result of nesting.'
|
||||
def f(self, irc, msg, args):
|
||||
alias = original.replace('$nick', msg.nick)
|
||||
if '$channel' in original:
|
||||
channel = privmsgs.getChannel(msg, args)
|
||||
alias = alias.replace('$channel', channel)
|
||||
tokens = callbacks.tokenize(alias)
|
||||
if not wildcard and biggestDollar or biggestAt:
|
||||
args = privmsgs.getArgs(args,
|
||||
required=biggestDollar,
|
||||
optional=biggestAt)
|
||||
# Gotta have a mutable sequence (for replace).
|
||||
if biggestDollar + biggestAt == 1: # We got a string, no tuple.
|
||||
args = [args]
|
||||
def regexpReplace(m):
|
||||
idx = int(m.group(1))
|
||||
return args[idx-1]
|
||||
def replace(tokens, replacer):
|
||||
for (i, token) in enumerate(tokens):
|
||||
if isinstance(token, list):
|
||||
replace(token, replacer)
|
||||
else:
|
||||
tokens[i] = replacer(token)
|
||||
replace(tokens, lambda s: dollarRe.sub(regexpReplace, s))
|
||||
if biggestAt:
|
||||
assert not wildcard
|
||||
args = args[biggestDollar:]
|
||||
replace(tokens, lambda s: atRe.sub(regexpReplace, s))
|
||||
if wildcard:
|
||||
assert not biggestAt
|
||||
# Gotta remove the things that have already been subbed in.
|
||||
i = biggestDollar
|
||||
while i:
|
||||
args.pop(0)
|
||||
i -= 1
|
||||
def everythingReplace(tokens):
|
||||
for (i, token) in enumerate(tokens):
|
||||
if isinstance(token, list):
|
||||
if everythingReplace(token):
|
||||
return
|
||||
if token == '$*':
|
||||
tokens[i:i+1] = args
|
||||
return True
|
||||
elif '$*' in token:
|
||||
tokens[i] = token.replace('$*', ' '.join(args))
|
||||
return True
|
||||
return False
|
||||
everythingReplace(tokens)
|
||||
self.Proxy(irc, msg, tokens)
|
||||
doc ='<an alias, %s>\n\nAlias for <<%s>>' % \
|
||||
(utils.nItems('argument', biggestDollar), alias)
|
||||
f = utils.changeFunctionName(f, name, doc)
|
||||
return f
|
||||
|
||||
conf.registerPlugin('Alias')
|
||||
conf.registerGroup(conf.supybot.plugins.Alias, 'aliases')
|
||||
class Alias(callbacks.Privmsg):
|
||||
def __init__(self):
|
||||
callbacks.Privmsg.__init__(self)
|
||||
# Schema: {alias: [command, locked]}
|
||||
self.aliases = {}
|
||||
group = conf.supybot.plugins.Alias.aliases
|
||||
for (name, alias) in registry._cache.iteritems():
|
||||
name = name.lower()
|
||||
if name.startswith('supybot.plugins.alias.aliases.'):
|
||||
name = name[len('supybot.plugins.alias.aliases.'):]
|
||||
if '.' in name:
|
||||
continue
|
||||
conf.registerGlobalValue(group, name, registry.String('', ''))
|
||||
conf.registerGlobalValue(group.get(name), 'locked',
|
||||
registry.Boolean(False, ''))
|
||||
for (name, value) in group.getValues(fullNames=False):
|
||||
name = name.lower() # Just in case.
|
||||
command = value()
|
||||
locked = value.locked()
|
||||
self.aliases[name] = [command, locked]
|
||||
|
||||
def __call__(self, irc, msg):
|
||||
# Adding the aliases requires an Irc. So the first time we get called
|
||||
# with an Irc, we add our aliases and then delete ourselves :)
|
||||
for (alias, (command, locked)) in self.aliases.items():
|
||||
try:
|
||||
self.addAlias(irc, alias, command, locked)
|
||||
except Exception, e:
|
||||
self.log.exception('Exception when trying to add alias %s. '
|
||||
'Removing from the Alias database.' % alias)
|
||||
del self.aliases[alias]
|
||||
del self.__class__.__call__
|
||||
callbacks.Privmsg.__call__(self, irc, msg)
|
||||
|
||||
def lock(self, irc, msg, args, name):
|
||||
"""<alias>
|
||||
|
||||
Locks an alias so that no one else can change it.
|
||||
"""
|
||||
if hasattr(self, name) and self.isCommand(name):
|
||||
self.aliases[name][1] = True
|
||||
conf.supybot.plugins.Alias.aliases.get(name).locked.setValue(True)
|
||||
irc.replySuccess()
|
||||
else:
|
||||
irc.error('There is no such alias.')
|
||||
lock = wrap(lock, [('checkCapability', 'admin'), 'commandName'])
|
||||
|
||||
def unlock(self, irc, msg, args, name):
|
||||
"""<alias>
|
||||
|
||||
Unlocks an alias so that people can define new aliases over it.
|
||||
"""
|
||||
if hasattr(self, name) and self.isCommand(name):
|
||||
self.aliases[name][1] = False
|
||||
conf.supybot.plugins.Alias.aliases.get(name).locked.setValue(False)
|
||||
irc.replySuccess()
|
||||
else:
|
||||
irc.error('There is no such alias.')
|
||||
unlock = wrap(unlock, [('checkCapability', 'admin'), 'commandName'])
|
||||
|
||||
_invalidCharsRe = re.compile(r'[\[\]\s]')
|
||||
def addAlias(self, irc, name, alias, lock=False):
|
||||
if self._invalidCharsRe.search(name):
|
||||
raise AliasError, 'Names cannot contain spaces or square brackets.'
|
||||
if '|' in name:
|
||||
raise AliasError, 'Names cannot contain pipes.'
|
||||
if irc.getCallback(name):
|
||||
raise AliasError, 'Names cannot coincide with names of plugins.'
|
||||
realName = callbacks.canonicalName(name)
|
||||
if name != realName:
|
||||
s = 'That name isn\'t valid. Try %s instead.' % \
|
||||
utils.quoted(realName)
|
||||
raise AliasError, s
|
||||
name = realName
|
||||
cbs = callbacks.findCallbackForCommand(irc, name)
|
||||
if self in cbs:
|
||||
if hasattr(self, realName) and realName not in self.aliases:
|
||||
s = 'You can\'t overwrite commands in this plugin.'
|
||||
raise AliasError, s
|
||||
if name in self.aliases:
|
||||
(currentAlias, locked) = self.aliases[name]
|
||||
if locked and currentAlias != alias:
|
||||
raise AliasError, 'Alias %s is locked.' % utils.quoted(name)
|
||||
try:
|
||||
f = makeNewAlias(name, alias)
|
||||
except RecursiveAlias:
|
||||
raise AliasError, 'You can\'t define a recursive alias.'
|
||||
if name in self.aliases:
|
||||
# We gotta remove it so its value gets updated.
|
||||
conf.supybot.plugins.Alias.aliases.unregister(name)
|
||||
conf.supybot.plugins.Alias.aliases.register(name,
|
||||
registry.String(alias, ''))
|
||||
conf.supybot.plugins.Alias.aliases.get(name).register('locked',
|
||||
registry.Boolean(lock, ''))
|
||||
setattr(self.__class__, name, f)
|
||||
self.aliases[name] = [alias, lock]
|
||||
|
||||
def removeAlias(self, name, evenIfLocked=False):
|
||||
name = callbacks.canonicalName(name)
|
||||
if hasattr(self, name) and self.isCommand(name):
|
||||
if evenIfLocked or not self.aliases[name][1]:
|
||||
delattr(self.__class__, name)
|
||||
del self.aliases[name]
|
||||
conf.supybot.plugins.Alias.aliases.unregister(name)
|
||||
else:
|
||||
raise AliasError, 'That alias is locked.'
|
||||
else:
|
||||
raise AliasError, 'There is no such alias.'
|
||||
|
||||
def add(self, irc, msg, args, name, alias):
|
||||
"""<name> <alias>
|
||||
|
||||
Defines an alias <name> that executes <alias>. The <alias>
|
||||
should be in the standard "command argument [nestedcommand argument]"
|
||||
arguments to the alias; they'll be filled with the first, second, etc.
|
||||
arguments. $1, $2, etc. can be used for required arguments. @1, @2,
|
||||
etc. can be used for optional arguments. $* simply means "all
|
||||
remaining arguments," and cannot be combined with optional arguments.
|
||||
"""
|
||||
if ' ' not in alias:
|
||||
# If it's a single word, they probably want $*.
|
||||
alias += ' $*'
|
||||
try:
|
||||
self.addAlias(irc, name, alias)
|
||||
self.log.info('Adding alias %s for %s (from %s)' %
|
||||
(utils.quoted(name), utils.quoted(alias),msg.prefix))
|
||||
irc.replySuccess()
|
||||
except AliasError, e:
|
||||
irc.error(str(e))
|
||||
add = wrap(add, ['commandName', 'text'])
|
||||
|
||||
def remove(self, irc, msg, args, name):
|
||||
"""<name>
|
||||
|
||||
Removes the given alias, if unlocked.
|
||||
"""
|
||||
try:
|
||||
self.removeAlias(name)
|
||||
self.log.info('Removing alias %s (from %s)' % (utils.quoted(name),
|
||||
msg.prefix))
|
||||
irc.replySuccess()
|
||||
except AliasError, e:
|
||||
irc.error(str(e))
|
||||
remove = wrap(remove, ['commandName'])
|
||||
|
||||
|
||||
Class = Alias
|
||||
|
||||
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78:
|
@ -1,605 +0,0 @@
|
||||
###
|
||||
# Copyright (c) 2002-2004, Jeremiah Fincher
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions, and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions, and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
# * Neither the name of the author of this software nor the name of
|
||||
# contributors to this software may be used to endorse or promote products
|
||||
# derived from this software without specific prior written consent.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
###
|
||||
|
||||
"""
|
||||
Amazon module, to use Amazon's Web Services.
|
||||
"""
|
||||
|
||||
import supybot
|
||||
|
||||
__revision__ = "$Id$"
|
||||
__author__ = supybot.authors.jamessan
|
||||
|
||||
import getopt
|
||||
|
||||
import amazon
|
||||
|
||||
import supybot.conf as conf
|
||||
import supybot.utils as utils
|
||||
from supybot.commands import *
|
||||
import supybot.plugins as plugins
|
||||
import supybot.ircutils as ircutils
|
||||
import supybot.privmsgs as privmsgs
|
||||
import supybot.registry as registry
|
||||
import supybot.callbacks as callbacks
|
||||
|
||||
|
||||
def configure(advanced):
|
||||
from supybot.questions import output, expect, anything, something, yn
|
||||
output('To use Amazon\'s Web Services, you must have a license key.')
|
||||
if yn('Do you have a license key?'):
|
||||
key = anything('What is it?')
|
||||
|
||||
conf.registerPlugin('Amazon', True)
|
||||
conf.supybot.plugins.Amazon.licenseKey.set(key)
|
||||
else:
|
||||
output("""You'll need to get a key before you can use this plugin.
|
||||
You can apply for a key at
|
||||
http://www.amazon.com/webservices/""")
|
||||
|
||||
class Region(registry.OnlySomeStrings):
|
||||
validStrings = ('us', 'uk', 'de', 'jp')
|
||||
|
||||
class LicenseKey(registry.String):
|
||||
def set(self, s):
|
||||
# In case we decide we need to recover
|
||||
original = getattr(self, 'value', self._default)
|
||||
registry.String.set(self, s)
|
||||
if self.value:
|
||||
amazon.setLicense(self.value)
|
||||
|
||||
conf.registerPlugin('Amazon')
|
||||
conf.registerChannelValue(conf.supybot.plugins.Amazon, 'bold',
|
||||
registry.Boolean(True, """Determines whether the results are bolded."""))
|
||||
conf.registerGlobalValue(conf.supybot.plugins.Amazon, 'licenseKey',
|
||||
LicenseKey('', """Sets the license key for using Amazon Web Services.
|
||||
Must be set before any other commands in the plugin are used.""",
|
||||
private=True))
|
||||
conf.registerChannelValue(conf.supybot.plugins.Amazon, 'linkSnarfer',
|
||||
registry.Boolean(False, """Determines whether the bot will reply to
|
||||
Amazon.com URLs in the channel with a description of the item at the
|
||||
URL."""))
|
||||
conf.registerChannelValue(conf.supybot.plugins.Amazon, 'region', Region('us',
|
||||
"""Determines the region that will be used when performing searches."""))
|
||||
|
||||
class Amazon(callbacks.PrivmsgCommandAndRegexp):
|
||||
threaded = True
|
||||
callBefore = ['URL']
|
||||
regexps = ['amzSnarfer']
|
||||
def __init__(self):
|
||||
self.__parent = super(Amazon, self)
|
||||
self.__parent.__init__()
|
||||
|
||||
def callCommand(self, name, irc, msg, *L, **kwargs):
|
||||
try:
|
||||
self.__parent.callCommand(name, irc, msg, *L, **kwargs)
|
||||
except amazon.NoLicenseKey, e:
|
||||
irc.error('You must have a free Amazon web services license key '
|
||||
'in order to use this command. You can get one at '
|
||||
'<http://www.amazon.com/webservices>. Once you have '
|
||||
'one, you can set it with the command '
|
||||
'"config supybot.plugins.Amazon.licensekey <key>".')
|
||||
|
||||
def _genResults(self, reply, attribs, items, url, bold):
|
||||
results = {}
|
||||
res = []
|
||||
bold_item = 'title'
|
||||
if isinstance(items, amazon.Bag):
|
||||
items = [items]
|
||||
for item in items:
|
||||
try:
|
||||
for k,v in attribs.iteritems():
|
||||
results[v] = getattr(item, k, 'unknown')
|
||||
if isinstance(results[v], amazon.Bag):
|
||||
results[v] = getattr(results[v], k[:-1], 'unknown')
|
||||
if not isinstance(results[v], basestring):
|
||||
results[v] = utils.commaAndify(results[v])
|
||||
if bold_item in results:
|
||||
if bold:
|
||||
results[bold_item] = ircutils.bold(results[bold_item])
|
||||
else:
|
||||
results[bold_item] = '"%s"' % results[bold_item]
|
||||
if not url:
|
||||
results['url'] = ''
|
||||
else:
|
||||
results['url'] = ' <%s>' % results['url']
|
||||
s = reply % results
|
||||
if isinstance(s, unicode):
|
||||
s = s.encode('utf-8')
|
||||
res.append(str(s))
|
||||
except amazon.AmazonError, e:
|
||||
self.log.warning(str(e))
|
||||
except UnicodeEncodeError, e:
|
||||
self.log.warning(str(e))
|
||||
return res
|
||||
|
||||
def isbn(self, irc, msg, args, optlist, isbn):
|
||||
"""[--url] <isbn>
|
||||
|
||||
Returns the book matching the given ISBN number. If --url is
|
||||
specified, a link to amazon.com's page for the book will also be
|
||||
returned.
|
||||
"""
|
||||
url = False
|
||||
for (option, argument) in optlist:
|
||||
if option == 'url':
|
||||
url = True
|
||||
isbn = isbn.replace('-', '').replace(' ', '')
|
||||
attribs = {'ProductName' : 'title',
|
||||
'Manufacturer' : 'publisher',
|
||||
'Authors' : 'author',
|
||||
'OurPrice' : 'price',
|
||||
'URL' : 'url'
|
||||
}
|
||||
s = '%(title)s, written by %(author)s; published by ' \
|
||||
'%(publisher)s; price: %(price)s%(url)s'
|
||||
channel = msg.args[0]
|
||||
bold = self.registryValue('bold', channel)
|
||||
region = self.registryValue('region', channel)
|
||||
try:
|
||||
book = amazon.searchByKeyword(isbn, locale=region)
|
||||
res = self._genResults(s, attribs, book, url, bold)
|
||||
if res:
|
||||
irc.reply(utils.commaAndify(res))
|
||||
return
|
||||
except amazon.AmazonError, e:
|
||||
pass
|
||||
irc.reply('No book was found with that ISBN.')
|
||||
isbn = wrap(isbn, [getopts({'url':''}), 'text'])
|
||||
|
||||
def books(self, irc, msg, args, optlist, keyword):
|
||||
"""[--url] <keywords>
|
||||
|
||||
Returns the books matching the given <keywords> search. If --url is
|
||||
specified, a link to amazon.com's page for the book will also be
|
||||
returned.
|
||||
"""
|
||||
url = False
|
||||
for (option, _) in optlist:
|
||||
if option == 'url':
|
||||
url = True
|
||||
attribs = {'ProductName' : 'title',
|
||||
'Manufacturer' : 'publisher',
|
||||
'Authors' : 'author',
|
||||
'OurPrice' : 'price',
|
||||
'URL' : 'url'
|
||||
}
|
||||
s = '%(title)s, written by %(author)s; published by ' \
|
||||
'%(publisher)s; price: %(price)s%(url)s'
|
||||
channel = msg.args[0]
|
||||
region = self.registryValue('region', channel)
|
||||
bold = self.registryValue('bold', channel)
|
||||
try:
|
||||
books = amazon.searchByKeyword(keyword, locale=region)
|
||||
res = self._genResults(s, attribs, books, url, bold)
|
||||
if res:
|
||||
irc.reply(utils.commaAndify(res))
|
||||
return
|
||||
except amazon.AmazonError, e:
|
||||
pass
|
||||
irc.reply('No books were found with that keyword search.')
|
||||
books = wrap(books, [getopts({'url':''}), 'text'])
|
||||
|
||||
def videos(self, irc, msg, args, optlist, keyword):
|
||||
"""[--url] [--{dvd,vhs}] <keywords>
|
||||
|
||||
Returns the videos matching the given <keyword> search. If --url is
|
||||
specified, a link to amazon.com's page for the video will also be
|
||||
returned. Search defaults to using --dvd.
|
||||
"""
|
||||
url = False
|
||||
product = 'dvd'
|
||||
for (option, _) in optlist:
|
||||
if option == 'url':
|
||||
url = True
|
||||
else:
|
||||
product = option
|
||||
attribs = {'ProductName' : 'title',
|
||||
'Manufacturer' : 'publisher',
|
||||
'MpaaRating' : 'mpaa',
|
||||
'Media' : 'media',
|
||||
'ReleaseDate' : 'date',
|
||||
'OurPrice' : 'price',
|
||||
'URL' : 'url'
|
||||
}
|
||||
s = '%(title)s (%(media)s), rated %(mpaa)s; released ' \
|
||||
'%(date)s; published by %(publisher)s; price: %(price)s%(url)s'
|
||||
channel = msg.args[0]
|
||||
region = self.registryValue('region', channel)
|
||||
bold = self.registryValue('bold', channel)
|
||||
try:
|
||||
videos = amazon.searchByKeyword(keyword, product_line=product,
|
||||
locale=region)
|
||||
res = self._genResults(s, attribs, videos, url, bold)
|
||||
if res:
|
||||
irc.reply(utils.commaAndify(res))
|
||||
return
|
||||
except amazon.AmazonError, e:
|
||||
pass
|
||||
irc.reply('No videos were found with that keyword search.')
|
||||
videos = wrap(videos, [getopts({'url':'', 'dvd':'', 'vhs':''}), 'text'])
|
||||
|
||||
def asin(self, irc, msg, args, optlist, asin):
|
||||
"""[--url] <asin>
|
||||
|
||||
Returns the item matching the given ASIN number. If --url is
|
||||
specified, a link to amazon.com's page for the item will also be
|
||||
returned.
|
||||
"""
|
||||
url = False
|
||||
for (option, _) in optlist:
|
||||
if option == 'url':
|
||||
url = True
|
||||
asin = asin.replace('-', '').replace(' ', '')
|
||||
attribs = {'ProductName' : 'title',
|
||||
'OurPrice' : 'price',
|
||||
'URL' : 'url'
|
||||
}
|
||||
s = '%(title)s; price: %(price)s%(url)s'
|
||||
channel = msg.args[0]
|
||||
region = self.registryValue('region', channel)
|
||||
bold = self.registryValue('bold', channel)
|
||||
try:
|
||||
item = amazon.searchByASIN(asin, locale=region)
|
||||
res = self._genResults(s, attribs, item, url, bold)
|
||||
if res:
|
||||
irc.reply(utils.commaAndify(res))
|
||||
return
|
||||
except amazon.AmazonError, e:
|
||||
pass
|
||||
irc.reply('No item was found with that ASIN.')
|
||||
asin = wrap(asin, [getopts({'url':''}), 'text'])
|
||||
|
||||
def upc(self, irc, msg, args, optlist, upc):
|
||||
"""[--url] <upc>
|
||||
|
||||
Returns the item matching the given UPC number. If --url is
|
||||
specified, a link to amazon.com's page for the item will also be
|
||||
returned. Only items in the following categories may be found via upc
|
||||
search: music, classical, software, dvd, video, vhs, electronics,
|
||||
pc-hardware, and photo.
|
||||
"""
|
||||
url = False
|
||||
for (option, _) in optlist:
|
||||
if option == 'url':
|
||||
url = True
|
||||
upc = upc.replace('-', '').replace(' ', '')
|
||||
attribs = {'ProductName' : 'title',
|
||||
'Manufacturer' : 'manufacturer',
|
||||
'OurPrice' : 'price',
|
||||
'URL' : 'url'
|
||||
}
|
||||
s = '%(title)s %(manufacturer)s; price: %(price)s%(url)s'
|
||||
channel = msg.args[0]
|
||||
region = self.registryValue('region', channel)
|
||||
bold = self.registryValue('bold', channel)
|
||||
try:
|
||||
item = amazon.searchByUPC(upc, locale=region)
|
||||
res = self._genResults(s, attribs, item, url, bold)
|
||||
if res:
|
||||
irc.reply(utils.commaAndify(res))
|
||||
return
|
||||
except amazon.AmazonError, e:
|
||||
pass
|
||||
irc.reply('No item was found with that UPC.')
|
||||
upc = wrap(upc, [getopts({'url':''}), 'text'])
|
||||
|
||||
def author(self, irc, msg, args, optlist, author):
|
||||
"""[--url] <author>
|
||||
|
||||
Returns a list of books written by the given author. If --url is
|
||||
specified, a link to amazon.com's page for the book will also be
|
||||
returned.
|
||||
"""
|
||||
url = False
|
||||
for (option, argument) in optlist:
|
||||
if option == 'url':
|
||||
url = True
|
||||
attribs = {'ProductName' : 'title',
|
||||
'Manufacturer' : 'publisher',
|
||||
'Authors' : 'author',
|
||||
'OurPrice' : 'price',
|
||||
'URL' : 'url'
|
||||
}
|
||||
s = '%(title)s, written by %(author)s; published by ' \
|
||||
'%(publisher)s; price: %(price)s%(url)s'
|
||||
channel = msg.args[0]
|
||||
region = self.registryValue('region', channel)
|
||||
bold = self.registryValue('bold', channel)
|
||||
try:
|
||||
books = amazon.searchByAuthor(author, locale=region)
|
||||
res = self._genResults(s, attribs, books, url, bold)
|
||||
if res:
|
||||
irc.reply(utils.commaAndify(res))
|
||||
return
|
||||
except amazon.AmazonError, e:
|
||||
pass
|
||||
irc.reply('No books were found by that author.')
|
||||
author = wrap(author, [getopts({'url':''}), 'text'])
|
||||
|
||||
# FIXME: Until I get a *good* list of categories (ones that actually work),
|
||||
# these commands will remain unavailable
|
||||
'''
|
||||
_textToNode = {'dvds':'130', 'magazines':'599872', 'music':'301668',
|
||||
'software':'491286', 'vhs':'404272', 'kitchen':'491864',
|
||||
'video games':'471280', 'toys':'491290', 'camera':'502394',
|
||||
'outdoor':'468250', 'computers':'565118', 'tools':'468240',
|
||||
'electronics':'172282'
|
||||
}
|
||||
def categories(self, irc, msg, args):
|
||||
"""takes no arguments
|
||||
|
||||
Returns a list of valid categories to use with the bestsellers
|
||||
commands.
|
||||
"""
|
||||
cats = self._textToNode.keys()
|
||||
cats.sort()
|
||||
irc.reply(utils.commaAndify(cats))
|
||||
categories = wrap(categories)
|
||||
|
||||
def bestsellers(self, irc, msg, args, optlist, category):
|
||||
"""[--url] <category>
|
||||
|
||||
Returns a list of best selling items in <category>. The 'categories'
|
||||
command will return a list of the available categories. If --url
|
||||
is specified, a link to amazon.com's page for the item will also be
|
||||
returned.
|
||||
"""
|
||||
url = False
|
||||
for (option, _) in optlist:
|
||||
if option == 'url':
|
||||
url = True
|
||||
if category not in self._textToNode:
|
||||
irc.error('An invalid category was specified. The categories'
|
||||
' command will return a list of valid categories')
|
||||
return
|
||||
category = self._textToNode[category]
|
||||
attribs = {'ProductName' : 'title',
|
||||
'Manufacturer' : 'publisher',
|
||||
'URL' : 'url'
|
||||
}
|
||||
s = '"%(title)s", from %(publisher)s.%(url)s'
|
||||
channel = msg.args[0]
|
||||
region = self.registryValue('region', channel)
|
||||
bold = self.registryValue('bold', channel)
|
||||
try:
|
||||
items = amazon.browseBestSellers(category, locale=region)
|
||||
res = self._genResults(s, attribs, items, url, bold)
|
||||
if res:
|
||||
irc.reply(utils.commaAndify(res))
|
||||
return
|
||||
except amazon.AmazonError, e:
|
||||
pass
|
||||
irc.reply('No items were found on that best seller list.')
|
||||
bestsellers = wrap(bestsellers, [getopts({'url':''}), rest('lowered')])
|
||||
'''
|
||||
|
||||
|
||||
def artist(self, irc, msg, args, optlist, artist):
|
||||
"""[--url] [--{music,classical}] <artist>
|
||||
|
||||
Returns a list of items by the given artist. If --url is specified, a
|
||||
link to amazon.com's page for the match will also be returned. The
|
||||
search defaults to using --music.
|
||||
"""
|
||||
url = False
|
||||
product = None
|
||||
for (option, _) in optlist:
|
||||
if option == 'url':
|
||||
url = True
|
||||
else:
|
||||
product = option
|
||||
product = product or 'music'
|
||||
attribs = {'ProductName' : 'title',
|
||||
'Manufacturer' : 'publisher',
|
||||
'Artists' : 'artist',
|
||||
'Media' : 'media',
|
||||
'OurPrice' : 'price',
|
||||
'URL' : 'url'
|
||||
}
|
||||
s = '%(title)s (%(media)s), by %(artist)s; published by ' \
|
||||
'%(publisher)s; price: %(price)s%(url)s'
|
||||
channel = msg.args[0]
|
||||
region = self.registryValue('region', channel)
|
||||
bold = self.registryValue('bold', channel)
|
||||
try:
|
||||
items = amazon.searchByArtist(artist, product_line=product,
|
||||
locale=region)
|
||||
res = self._genResults(s, attribs, items, url, bold)
|
||||
if res:
|
||||
irc.reply(utils.commaAndify(res))
|
||||
return
|
||||
except amazon.AmazonError, e:
|
||||
pass
|
||||
irc.reply('No items were found by that artist.')
|
||||
artist = wrap(artist, [getopts({'music':'', 'classical':'', 'url':''}),
|
||||
'text'])
|
||||
|
||||
def actor(self, irc, msg, args, optlist, actor):
|
||||
"""[--url] [--{dvd,vhs,video}] <actor>
|
||||
|
||||
Returns a list of items starring the given actor. If --url is
|
||||
specified, a link to amazon.com's page for the match will also be
|
||||
returned. The search defaults to using --dvd.
|
||||
"""
|
||||
url = False
|
||||
product = ''
|
||||
for (option, _) in optlist:
|
||||
if option == 'url':
|
||||
url = True
|
||||
else:
|
||||
product = option
|
||||
product = product or 'dvd'
|
||||
attribs = {'ProductName' : 'title',
|
||||
'Manufacturer' : 'publisher',
|
||||
'MpaaRating' : 'mpaa',
|
||||
'Media' : 'media',
|
||||
'ReleaseDate' : 'date',
|
||||
'OurPrice' : 'price',
|
||||
'URL' : 'url'
|
||||
}
|
||||
s = '%(title)s (%(media)s), rated %(mpaa)s; released ' \
|
||||
'%(date)s; published by %(publisher)s; price: %(price)s%(url)s'
|
||||
channel = msg.args[0]
|
||||
region = self.registryValue('region', channel)
|
||||
bold = self.registryValue('bold', channel)
|
||||
try:
|
||||
items = amazon.searchByActor(actor, product_line=product,
|
||||
locale=region)
|
||||
res = self._genResults(s, attribs, items, url, bold)
|
||||
if res:
|
||||
irc.reply(utils.commaAndify(res))
|
||||
return
|
||||
except amazon.AmazonError, e:
|
||||
pass
|
||||
irc.reply('No items were found starring that actor.')
|
||||
actor = wrap(actor, [getopts({'dvd': '', 'video': '', 'vhs':'', 'url':''}),
|
||||
'text'])
|
||||
|
||||
def director(self, irc, msg, args, optlist, director):
|
||||
"""[--url] [--{dvd,vhs,video}] <director>
|
||||
|
||||
Returns a list of items by the given director. If --url is
|
||||
specified, a link to amazon.com's page for the match will also be
|
||||
returned. The search defaults to using --dvd.
|
||||
"""
|
||||
url = False
|
||||
product = None
|
||||
for (option, _) in optlist:
|
||||
if option == 'url':
|
||||
url = True
|
||||
else:
|
||||
product = option
|
||||
product = product or 'dvd'
|
||||
attribs = {'ProductName' : 'title',
|
||||
'Manufacturer' : 'publisher',
|
||||
'MpaaRating' : 'mpaa',
|
||||
'Media' : 'media',
|
||||
'ReleaseDate' : 'date',
|
||||
'OurPrice' : 'price',
|
||||
'URL' : 'url'
|
||||
}
|
||||
s = '%(title)s (%(media)s), rated %(mpaa)s; released ' \
|
||||
'%(date)s; published by %(publisher)s; price: %(price)s%(url)s'
|
||||
channel = msg.args[0]
|
||||
region = self.registryValue('region', channel)
|
||||
bold = self.registryValue('bold', channel)
|
||||
try:
|
||||
items = amazon.searchByDirector(director, product_line=product,
|
||||
locale=region)
|
||||
res = self._genResults(s, attribs, items, url, bold)
|
||||
if res:
|
||||
irc.reply(utils.commaAndify(res))
|
||||
return
|
||||
except amazon.AmazonError, e:
|
||||
pass
|
||||
irc.reply('No items were found by that director.')
|
||||
director = wrap(director, [getopts({'dvd': '', 'video': '', 'vhs': '',
|
||||
'url': ''}),
|
||||
'text'])
|
||||
|
||||
def manufacturer(self, irc, msg, args, optlist, manufacturer):
|
||||
""" [--url] \
|
||||
[--{pc-hardware,kitchen,electronics,videogames,software,photo}] \
|
||||
<manufacturer>
|
||||
|
||||
Returns a list of items by the given manufacturer. If --url is
|
||||
specified, a link to amazon.com's page for the match will also be
|
||||
returned. The search defaults to using --pc-hardware.
|
||||
"""
|
||||
url = False
|
||||
product = None
|
||||
for (option, _) in optlist:
|
||||
if option == 'url':
|
||||
url = True
|
||||
else:
|
||||
product = option
|
||||
product = product or 'pc-hardware'
|
||||
attribs = {'ProductName' : 'title',
|
||||
'OurPrice' : 'price',
|
||||
'URL' : 'url'
|
||||
}
|
||||
s = '%(title)s; price: %(price)s%(url)s'
|
||||
channel = msg.args[0]
|
||||
region = self.registryValue('region', channel)
|
||||
bold = self.registryValue('bold', channel)
|
||||
try:
|
||||
items = amazon.searchByManufacturer(manufacturer,
|
||||
product_line=product,
|
||||
locale=region)
|
||||
res = self._genResults(s, attribs, items, url, bold)
|
||||
if res:
|
||||
irc.reply(utils.commaAndify(res))
|
||||
return
|
||||
except amazon.AmazonError, e:
|
||||
pass
|
||||
irc.reply('No items were found by that manufacturer.')
|
||||
manufacturer = wrap(manufacturer,
|
||||
[getopts({'url':'', 'electronics':'', 'kitchen':'',
|
||||
'videogames':'', 'software':'', 'photo':'',
|
||||
'pc-hardware':'',
|
||||
}),
|
||||
'text'])
|
||||
|
||||
def amzSnarfer(self, irc, msg, match):
|
||||
r"http://www.amazon.com/exec/obidos/(?:tg/detail/-/|ASIN/)([^/]+)"
|
||||
if not self.registryValue('linkSnarfer', msg.args[0]):
|
||||
return
|
||||
match = match.group(1)
|
||||
attribs = {'ProductName' : 'title',
|
||||
'Manufacturer' : 'publisher',
|
||||
'Authors' : 'author',
|
||||
'MpaaRating' : 'mpaa',
|
||||
'Media' : 'media',
|
||||
'ReleaseDate' : 'date',
|
||||
'OurPrice' : 'price',
|
||||
'Artists' : 'artist',
|
||||
}
|
||||
s = '%(title)s; %(artist)s; %(author)s; %(mpaa)s; %(media)s; '\
|
||||
'%(date)s; %(publisher)s; price: %(price)s'
|
||||
channel = msg.args[0]
|
||||
region = self.registryValue('region', channel)
|
||||
bold = self.registryValue('bold', channel)
|
||||
try:
|
||||
item = amazon.searchByASIN(match, locale=region)
|
||||
res = self._genResults(s, attribs, item, False, bold)
|
||||
if res:
|
||||
res = utils.commaAndify(res)
|
||||
res = res.replace('; unknown', '')
|
||||
res = res.replace('; price: unknown', '')
|
||||
irc.reply(res, prefixName=False)
|
||||
return
|
||||
except amazon.AmazonError, e:
|
||||
pass
|
||||
self.log.debug('No item was found with that ASIN.')
|
||||
amzSnarfer = urlSnarfer(amzSnarfer)
|
||||
|
||||
Class = Amazon
|
||||
|
||||
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78:
|
@ -1,123 +0,0 @@
|
||||
###
|
||||
# Copyright (c) 2004, Jeremiah Fincher
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions, and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions, and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
# * Neither the name of the author of this software nor the name of
|
||||
# contributors to this software may be used to endorse or promote products
|
||||
# derived from this software without specific prior written consent.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
###
|
||||
|
||||
"""
|
||||
Allows folks to talk through the bot anonymously.
|
||||
"""
|
||||
|
||||
__revision__ = "$Id$"
|
||||
|
||||
import supybot.plugins as plugins
|
||||
|
||||
import supybot.conf as conf
|
||||
import supybot.utils as utils
|
||||
import supybot.ircdb as ircdb
|
||||
from supybot.commands import *
|
||||
import supybot.ircmsgs as ircmsgs
|
||||
import supybot.registry as registry
|
||||
import supybot.callbacks as callbacks
|
||||
|
||||
|
||||
def configure(advanced):
|
||||
# This will be called by setup.py to configure this module. Advanced is
|
||||
# a bool that specifies whether the user identified himself as an advanced
|
||||
# user or not. You should effect your configuration by manipulating the
|
||||
# registry as appropriate.
|
||||
from supybot.questions import expect, anything, something, yn
|
||||
conf.registerPlugin('Anonymous', True)
|
||||
|
||||
conf.registerPlugin('Anonymous')
|
||||
conf.registerChannelValue(conf.supybot.plugins.Anonymous,
|
||||
'requirePresenceInChannel', registry.Boolean(True, """Determines whether
|
||||
the bot should require people trying to use this plugin to be in the
|
||||
channel they wish to anonymously send to."""))
|
||||
conf.registerGlobalValue(conf.supybot.plugins.Anonymous, 'requireRegistration',
|
||||
registry.Boolean(True, """Determines whether the bot should require people
|
||||
trying to use this plugin to be registered."""))
|
||||
conf.registerGlobalValue(conf.supybot.plugins.Anonymous, 'requireCapability',
|
||||
registry.String('', """Determines what capability (if any) the bot should
|
||||
require people trying to use this plugin to have."""))
|
||||
conf.registerGlobalValue(conf.supybot.plugins.Anonymous, 'allowPrivateTarget',
|
||||
registry.Boolean(False, """Determines whether the bot will require targets
|
||||
of the "say" command to be public (i.e., channels). If this is True, the
|
||||
bot will allow people to use the "say" command to send private messages to
|
||||
other users."""))
|
||||
|
||||
|
||||
class Anonymous(callbacks.Privmsg):
|
||||
private = True
|
||||
def _preCheck(self, irc, msg, channel):
|
||||
if self.registryValue('requireRegistration'):
|
||||
try:
|
||||
_ = ircdb.users.getUser(msg.prefix)
|
||||
except KeyError:
|
||||
irc.errorNotRegistered(Raise=True)
|
||||
capability = self.registryValue('requireCapability')
|
||||
if capability:
|
||||
if not ircdb.checkCapability(msg.prefix, capability):
|
||||
irc.errorNoCapability(capability, Raise=True)
|
||||
if self.registryValue('requirePresenceInChannel', channel) and \
|
||||
msg.nick not in irc.state.channels[channel].users:
|
||||
irc.error('You must be in %s to "say" in there.' % channel,
|
||||
Raise=True)
|
||||
c = ircdb.channels.getChannel(channel)
|
||||
if c.lobotomized:
|
||||
irc.error('I\'m lobotomized in %s.' % channel, Raise=True)
|
||||
if not c._checkCapability(self.name()):
|
||||
irc.error('That channel has set its capabilities so as to '
|
||||
'disallow the use of this plugin.', Raise=True)
|
||||
|
||||
def say(self, irc, msg, args, channel, text):
|
||||
"""<channel> <text>
|
||||
|
||||
Sends <text> to <channel>.
|
||||
"""
|
||||
self._preCheck(irc, msg, channel)
|
||||
self.log.info('Saying %s in %s due to %s.',
|
||||
utils.quoted(text), channel, msg.prefix)
|
||||
irc.queueMsg(ircmsgs.privmsg(channel, text))
|
||||
irc.noReply()
|
||||
say = wrap(say, ['inChannel', 'text'])
|
||||
|
||||
def do(self, irc, msg, args, channel, text):
|
||||
"""<channel> <action>
|
||||
|
||||
Performs <action> in <channel>.
|
||||
"""
|
||||
self._preCheck(irc, msg, channel)
|
||||
self.log.info('Performing %s in %s due to %s.',
|
||||
utils.quoted(text), channel, msg.prefix)
|
||||
irc.queueMsg(ircmsgs.action(channel, text))
|
||||
irc.noReply()
|
||||
do = wrap(do, ['inChannel', 'text'])
|
||||
|
||||
|
||||
Class = Anonymous
|
||||
|
||||
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78:
|
@ -1,139 +0,0 @@
|
||||
###
|
||||
# Copyright (c) 2004, Jeremiah Fincher
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions, and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions, and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
# * Neither the name of the author of this software nor the name of
|
||||
# contributors to this software may be used to endorse or promote products
|
||||
# derived from this software without specific prior written consent.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
###
|
||||
|
||||
"""
|
||||
Automatically ops, voices, or halfops, or bans people when they join a channel,
|
||||
according to their capabilities. If you want your bot automatically op users
|
||||
when they join your channel, this is the plugin to load.
|
||||
"""
|
||||
|
||||
import supybot
|
||||
|
||||
__revision__ = "$Id$"
|
||||
__author__ = supybot.authors.jemfinch
|
||||
__contributors__ = {}
|
||||
|
||||
import time
|
||||
|
||||
import supybot.conf as conf
|
||||
import supybot.utils as utils
|
||||
import supybot.ircdb as ircdb
|
||||
import supybot.plugins as plugins
|
||||
import supybot.ircmsgs as ircmsgs
|
||||
import supybot.ircutils as ircutils
|
||||
import supybot.privmsgs as privmsgs
|
||||
import supybot.registry as registry
|
||||
import supybot.schedule as schedule
|
||||
import supybot.callbacks as callbacks
|
||||
|
||||
|
||||
def configure(advanced):
|
||||
# This will be called by setup.py to configure this module. Advanced is
|
||||
# a bool that specifies whether the user identified himself as an advanced
|
||||
# user or not. You should effect your configuration by manipulating the
|
||||
# registry as appropriate.
|
||||
from supybot.questions import expect, anything, something, yn
|
||||
conf.registerPlugin('AutoMode', True)
|
||||
|
||||
AutoMode = conf.registerPlugin('AutoMode')
|
||||
conf.registerChannelValue(AutoMode, 'enable',
|
||||
registry.Boolean(True, """Determines whether this plugin is enabled."""))
|
||||
conf.registerChannelValue(AutoMode, 'fallthrough',
|
||||
registry.Boolean(False, """Determines whether the bot will "fall through" to
|
||||
halfop/voicing when auto-opping is turned off but auto-halfopping/voicing
|
||||
are turned on."""))
|
||||
conf.registerChannelValue(AutoMode, 'op',
|
||||
registry.Boolean(True, """Determines whether the bot will automatically op
|
||||
people with the <channel>,op capability when they join the channel."""))
|
||||
conf.registerChannelValue(AutoMode, 'halfop',
|
||||
registry.Boolean(True, """Determines whether the bot will automatically
|
||||
halfop people with the <channel>,halfop capability when they join the
|
||||
channel."""))
|
||||
conf.registerChannelValue(AutoMode, 'voice',
|
||||
registry.Boolean(True, """Determines whether the bot will automatically
|
||||
voice people with the <channel>,voice capability when they join the
|
||||
channel."""))
|
||||
conf.registerChannelValue(AutoMode, 'ban',
|
||||
registry.Boolean(True, """Determines whether the bot will automatically ban
|
||||
people who join the channel and are on the banlist."""))
|
||||
conf.registerChannelValue(AutoMode.ban, 'period',
|
||||
registry.PositiveInteger(86400, """Determines how many seconds the bot will
|
||||
automatically ban a person when banning."""))
|
||||
|
||||
class Continue(Exception):
|
||||
pass # Used below, look in the "do" function nested in doJoin.
|
||||
|
||||
class AutoMode(callbacks.Privmsg):
|
||||
def doJoin(self, irc, msg):
|
||||
channel = msg.args[0]
|
||||
if ircutils.strEqual(irc.nick, msg.nick):
|
||||
return
|
||||
if not self.registryValue('enable', channel):
|
||||
return
|
||||
fallthrough = self.registryValue('fallthrough', channel)
|
||||
def do(type):
|
||||
cap = ircdb.makeChannelCapability(channel, type)
|
||||
if ircdb.checkCapability(msg.prefix, cap):
|
||||
if self.registryValue(type, channel):
|
||||
self.log.info('Sending auto-%s of %s in %s.',
|
||||
type, msg.prefix, channel)
|
||||
msgmaker = getattr(ircmsgs, type)
|
||||
irc.queueMsg(msgmaker(channel, msg.nick))
|
||||
raise Continue # Even if fallthrough, let's only do one.
|
||||
elif not fallthrough:
|
||||
self.log.debug('%s has %s, but supybot.plugins.AutoMode.%s'
|
||||
' is not enabled in %s, refusing to fall '
|
||||
'through.', msg.prefix, cap, type, channel)
|
||||
raise Continue
|
||||
try:
|
||||
do('op')
|
||||
if 'h' in irc.state.supported['prefix']:
|
||||
do('halfop')
|
||||
do('voice')
|
||||
except Continue:
|
||||
return
|
||||
c = ircdb.channels.getChannel(channel)
|
||||
if c.checkBan(msg.prefix) and self.registryValue('ban', channel):
|
||||
period = self.registryValue('ban.period', channel)
|
||||
if period:
|
||||
def unban():
|
||||
try:
|
||||
if msg.prefix in irc.state.channels[channel].bans:
|
||||
irc.queueMsg(ircmsgs.unban(channel, msg.prefix))
|
||||
except KeyError:
|
||||
# We're not in the channel anymore.
|
||||
pass
|
||||
schedule.addEvent(unban, time.time()+period)
|
||||
irc.queueMsg(ircmsgs.ban(channel, msg.prefix))
|
||||
irc.queueMsg(ircmsgs.kick(channel, msg.nick))
|
||||
|
||||
|
||||
Class = AutoMode
|
||||
|
||||
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78:
|
@ -1,196 +0,0 @@
|
||||
###
|
||||
# Copyright (c) 2002-2004, Jeremiah Fincher
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions, and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions, and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
# * Neither the name of the author of this software nor the name of
|
||||
# contributors to this software may be used to endorse or promote products
|
||||
# derived from this software without specific prior written consent.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
###
|
||||
|
||||
"""
|
||||
Babelfish-related commands.
|
||||
"""
|
||||
|
||||
import supybot
|
||||
|
||||
__revision__ = "$Id$"
|
||||
__author__ = supybot.authors.jamessan
|
||||
|
||||
import sets
|
||||
import random
|
||||
from itertools import imap
|
||||
|
||||
import babelfish
|
||||
|
||||
import supybot.conf as conf
|
||||
import supybot.utils as utils
|
||||
from supybot.commands import *
|
||||
import supybot.registry as registry
|
||||
import supybot.callbacks as callbacks
|
||||
|
||||
class Languages(registry.OnlySomeStrings):
|
||||
validStrings = tuple(map(str.capitalize, babelfish.available_languages))
|
||||
normalize = staticmethod(str.capitalize)
|
||||
|
||||
class SpaceSeparatedListOfLanguages(registry.SeparatedListOf):
|
||||
List = sets.Set
|
||||
Value = Languages
|
||||
def splitter(self, s):
|
||||
return s.split()
|
||||
joiner = ' '.join
|
||||
|
||||
conf.registerPlugin('Babelfish')
|
||||
conf.registerChannelValue(conf.supybot.plugins.Babelfish, 'languages',
|
||||
SpaceSeparatedListOfLanguages(babelfish.available_languages, """Determines
|
||||
which languages are available for translation; valid input is a list of
|
||||
languages separated by spaces."""))
|
||||
|
||||
class Babelfish(callbacks.Privmsg):
|
||||
threaded = True
|
||||
_abbrevs = utils.abbrev(map(str.lower, babelfish.available_languages))
|
||||
_abbrevs['de'] = 'german'
|
||||
_abbrevs['jp'] = 'japanese'
|
||||
_abbrevs['kr'] = 'korean'
|
||||
_abbrevs['es'] = 'spanish'
|
||||
_abbrevs['pt'] = 'portuguese'
|
||||
_abbrevs['it'] = 'italian'
|
||||
_abbrevs['zh'] = 'chinese_simple'
|
||||
_abbrevs['zt'] = 'chinese_traditional'
|
||||
_abbrevs['nl'] = 'dutch'
|
||||
_abbrevs['el'] = 'greek'
|
||||
for language in babelfish.available_languages:
|
||||
_abbrevs[language] = language
|
||||
|
||||
def _getLang(self, fromLang, toLang, chan):
|
||||
fromLang = self._abbrevs[fromLang.lower()]
|
||||
toLang = self._abbrevs[toLang.lower()]
|
||||
languages = map(str.lower, self.registryValue('languages',chan))
|
||||
if fromLang not in languages:
|
||||
fromLang = None
|
||||
if toLang not in languages:
|
||||
toLang = None
|
||||
return (fromLang, toLang)
|
||||
|
||||
def languages(self, irc, msg, args):
|
||||
"""takes no arguments
|
||||
|
||||
Returns the languages that Babelfish can translate to/from.
|
||||
"""
|
||||
irc.reply(utils.commaAndify(babelfish.available_languages))
|
||||
|
||||
def translate(self, irc, msg, args, fromLang, toLang, text):
|
||||
"""<from-language> [to] <to-language> <text>
|
||||
|
||||
Returns <text> translated from <from-language> into <to-language>.
|
||||
Beware that translating to or from languages that use multi-byte
|
||||
characters may result in some very odd results.
|
||||
"""
|
||||
chan = msg.args[0]
|
||||
try:
|
||||
(fromLang, toLang) = self._getLang(fromLang, toLang, chan)
|
||||
if not fromLang or not toLang:
|
||||
langs = self.registryValue('languages', chan)
|
||||
if not langs:
|
||||
irc.error('I do not speak any other languages.')
|
||||
return
|
||||
else:
|
||||
irc.error('I only speak %s.' % utils.commaAndify(langs))
|
||||
return
|
||||
translation = babelfish.translate(text, fromLang, toLang)
|
||||
irc.reply(utils.htmlToText(translation))
|
||||
except (KeyError, babelfish.LanguageNotAvailableError), e:
|
||||
languages = self.registryValue('languages', chan)
|
||||
if languages:
|
||||
languages = 'Valid languages include %s' % \
|
||||
utils.commaAndify(sorted(languages))
|
||||
else:
|
||||
languages = 'I do not speak any other languages.'
|
||||
irc.errorInvalid('language', str(e), languages)
|
||||
except babelfish.BabelizerIOError, e:
|
||||
irc.error(str(e))
|
||||
except babelfish.BabelfishChangedError, e:
|
||||
irc.error('Babelfish has foiled our plans by changing its '
|
||||
'webpage format.')
|
||||
translate = wrap(translate, ['something', 'to', 'something', 'text'])
|
||||
|
||||
def babelize(self, irc, msg, args, fromLang, toLang, text):
|
||||
"""<from-language> <to-language> <text>
|
||||
|
||||
Translates <text> repeatedly between <from-language> and <to-language>
|
||||
until it doesn't change anymore or 12 times, whichever is fewer. One
|
||||
of the languages must be English.
|
||||
"""
|
||||
chan = msg.args[0]
|
||||
try:
|
||||
(fromLang, toLang) = self._getLang(fromLang, toLang, chan)
|
||||
if fromLang != 'english' and toLang != 'english':
|
||||
irc.error('One language in babelize must be English.')
|
||||
return
|
||||
if not fromLang or not toLang:
|
||||
langs = self.registryValue('languages', chan)
|
||||
if not langs:
|
||||
irc.error('I do not speak any other languages.')
|
||||
return
|
||||
else:
|
||||
irc.error('I only speak %s.' % utils.commaAndify(langs,
|
||||
And='or'))
|
||||
return
|
||||
translations = babelfish.babelize(text, fromLang, toLang)
|
||||
irc.reply(utils.htmlToText(translations[-1]))
|
||||
except (KeyError, babelfish.LanguageNotAvailableError), e:
|
||||
languages = self.registryValue('languages', chan)
|
||||
if languages:
|
||||
languages = 'Valid languages include %s' % \
|
||||
utils.commaAndify(sorted(languages))
|
||||
else:
|
||||
languages = 'I do not speak any other languages.'
|
||||
irc.errorInvalid('language', str(e), languages)
|
||||
except babelfish.BabelizerIOError, e:
|
||||
irc.reply(e)
|
||||
except babelfish.BabelfishChangedError, e:
|
||||
irc.reply('Babelfish has foiled our plans by changing its '
|
||||
'webpage format.')
|
||||
babelize = wrap(babelize, ['something', 'something', 'text'])
|
||||
|
||||
def randomlanguage(self, irc, msg, args, optlist):
|
||||
"""[--allow-english]
|
||||
|
||||
Returns a random language supported by babelfish. If --allow-english
|
||||
is provided, will include English in the list of possible languages.
|
||||
"""
|
||||
allowEnglish = False
|
||||
for (option, arg) in optlist:
|
||||
if option == 'allow-english':
|
||||
allowEnglish = True
|
||||
languages = self.registryValue('languages', msg.args[0])
|
||||
if not languages:
|
||||
irc.error('I can\'t speak any other languages.', Raise=True)
|
||||
language = random.choice(languages)
|
||||
while not allowEnglish and language == 'English':
|
||||
language = random.choice(languages)
|
||||
irc.reply(language)
|
||||
randomlanguage = wrap(randomlanguage, [getopts({'allow-english': ''})])
|
||||
|
||||
Class = Babelfish
|
||||
|
||||
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78:
|
@ -1,194 +0,0 @@
|
||||
###
|
||||
# Copyright (c) 2002-2004, Jeremiah Fincher
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions, and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions, and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
# * Neither the name of the author of this software nor the name of
|
||||
# contributors to this software may be used to endorse or promote products
|
||||
# derived from this software without specific prior written consent.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
###
|
||||
|
||||
"""
|
||||
Filters bad words on outgoing messages from the bot, so the bot can't be made
|
||||
to say bad words.
|
||||
"""
|
||||
|
||||
__revision__ = "$Id$"
|
||||
|
||||
import supybot.plugins as plugins
|
||||
|
||||
import re
|
||||
import math
|
||||
import sets
|
||||
import time
|
||||
|
||||
import supybot.conf as conf
|
||||
import supybot.utils as utils
|
||||
import supybot.ircdb as ircdb
|
||||
import supybot.ircmsgs as ircmsgs
|
||||
from supybot.commands import *
|
||||
import supybot.ircutils as ircutils
|
||||
import supybot.privmsgs as privmsgs
|
||||
import supybot.registry as registry
|
||||
import supybot.callbacks as callbacks
|
||||
|
||||
def configure(advanced):
|
||||
from supybot.questions import output, expect, anything, something, yn
|
||||
conf.registerPlugin('BadWords', True)
|
||||
if yn('Would you like to add some bad words?'):
|
||||
words = anything('What words? (separate individual words by spaces)')
|
||||
conf.supybot.plugins.BadWords.words.set(words)
|
||||
|
||||
class LastModifiedSetOfStrings(registry.SpaceSeparatedListOfStrings):
|
||||
List = sets.Set
|
||||
lastModified = 0
|
||||
def setValue(self, v):
|
||||
self.lastModified = time.time()
|
||||
registry.SpaceSeparatedListOfStrings.setValue(self, v)
|
||||
|
||||
conf.registerPlugin('BadWords')
|
||||
conf.registerGlobalValue(conf.supybot.plugins.BadWords, 'words',
|
||||
LastModifiedSetOfStrings([], """Determines what words are
|
||||
considered to be 'bad' so the bot won't say them."""))
|
||||
conf.registerGlobalValue(conf.supybot.plugins.BadWords,'requireWordBoundaries',
|
||||
registry.Boolean(False, """Determines whether the bot will require bad
|
||||
words to be independent words, or whether it will censor them within other
|
||||
words. For instance, if 'darn' is a bad word, then if this is true, 'darn'
|
||||
will be censored, but 'darnit' will not. You probably want this to be
|
||||
false."""))
|
||||
|
||||
class String256(registry.String):
|
||||
def __call__(self):
|
||||
s = registry.String.__call__(self)
|
||||
return s * (1024/len(s))
|
||||
|
||||
def __str__(self):
|
||||
return self.value
|
||||
|
||||
conf.registerGlobalValue(conf.supybot.plugins.BadWords, 'nastyChars',
|
||||
String256('!@#&', """Determines what characters will replace bad words; a
|
||||
chunk of these characters matching the size of the replaced bad word will
|
||||
be used to replace the bad words you've configured."""))
|
||||
|
||||
class ReplacementMethods(registry.OnlySomeStrings):
|
||||
validStrings = ('simple', 'nastyCharacters')
|
||||
|
||||
conf.registerGlobalValue(conf.supybot.plugins.BadWords, 'replaceMethod',
|
||||
ReplacementMethods('nastyCharacters', """Determines the manner in which
|
||||
bad words will be replaced. 'nastyCharacters' (the default) will replace a
|
||||
bad word with the same number of 'nasty characters' (like those used in
|
||||
comic books; configurable by supybot.plugins.BadWords.nastyChars).
|
||||
'simple' will replace a bad word with a simple strings (regardless of the
|
||||
length of the bad word); this string is configurable via
|
||||
supybot.plugins.BadWords.simpleReplacement."""))
|
||||
conf.registerGlobalValue(conf.supybot.plugins.BadWords,'simpleReplacement',
|
||||
registry.String('[CENSORED]', """Determines what word will replace bad
|
||||
words if the replacement method is 'simple'."""))
|
||||
conf.registerGlobalValue(conf.supybot.plugins.BadWords, 'stripFormatting',
|
||||
registry.Boolean(True, """Determines whether the bot will strip
|
||||
formatting characters from messages before it checks them for bad words.
|
||||
If this is False, it will be relatively trivial to circumvent this plugin's
|
||||
filtering. If it's True, however, it will interact poorly with other
|
||||
plugins that do coloring or bolding of text."""))
|
||||
|
||||
class BadWords(privmsgs.CapabilityCheckingPrivmsg):
|
||||
priority = 1
|
||||
capability = 'admin'
|
||||
def __init__(self):
|
||||
privmsgs.CapabilityCheckingPrivmsg.__init__(self)
|
||||
# This is so we can not filter certain outgoing messages (like list,
|
||||
# which would be kinda useless if it were filtered).
|
||||
self.filtering = True
|
||||
self.lastModified = 0
|
||||
self.words = conf.supybot.plugins.BadWords.words
|
||||
|
||||
def sub(self, m):
|
||||
replaceMethod = self.registryValue('replaceMethod')
|
||||
if replaceMethod == 'simple':
|
||||
return self.registryValue('simpleReplacement')
|
||||
elif replaceMethod == 'nastyCharacters':
|
||||
return self.registryValue('nastyChars')[:len(m.group(1))]
|
||||
|
||||
def inFilter(self, irc, msg):
|
||||
self.filtering = True
|
||||
return msg
|
||||
|
||||
def outFilter(self, irc, msg):
|
||||
if self.filtering and msg.command == 'PRIVMSG':
|
||||
if self.lastModified < self.words.lastModified:
|
||||
self.makeRegexp(self.words())
|
||||
self.lastModified = time.time()
|
||||
s = msg.args[1]
|
||||
if self.registryValue('stripFormatting'):
|
||||
s = ircutils.stripFormatting(s)
|
||||
s = self.regexp.sub(self.sub, s)
|
||||
msg = ircmsgs.privmsg(msg.args[0], s, msg=msg)
|
||||
return msg
|
||||
|
||||
def makeRegexp(self, iterable):
|
||||
s = '(%s)' % '|'.join(map(re.escape, iterable))
|
||||
if self.registryValue('requireWordBoundaries'):
|
||||
s = r'\b%s\b' % s
|
||||
self.regexp = re.compile(s, re.I)
|
||||
|
||||
def list(self, irc, msg, args):
|
||||
"""takes no arguments
|
||||
|
||||
Returns the list of words being censored.
|
||||
"""
|
||||
L = list(self.words())
|
||||
if L:
|
||||
self.filtering = False
|
||||
utils.sortBy(str.lower, L)
|
||||
irc.reply(utils.commaAndify(L))
|
||||
else:
|
||||
irc.reply('I\'m not currently censoring any bad words.')
|
||||
list = wrap(list)
|
||||
|
||||
def add(self, irc, msg, args, words):
|
||||
"""<word> [<word> ...]
|
||||
|
||||
Adds all <word>s to the list of words the bot isn't to say.
|
||||
"""
|
||||
set = self.words()
|
||||
set.update(words)
|
||||
self.words.setValue(set)
|
||||
irc.replySuccess()
|
||||
add = wrap(add, [many('something')])
|
||||
|
||||
def remove(self, irc, msg, args, words):
|
||||
"""<word> [<word> ...]
|
||||
|
||||
Removes a <word>s from the list of words the bot isn't to say.
|
||||
"""
|
||||
set = self.words()
|
||||
for word in words:
|
||||
set.discard(word)
|
||||
self.words.setValue(set)
|
||||
irc.replySuccess()
|
||||
remove = wrap(remove, [many('something')])
|
||||
|
||||
|
||||
Class = BadWords
|
||||
|
||||
|
||||
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78:
|
214
plugins/Bayes.py
214
plugins/Bayes.py
@ -1,214 +0,0 @@
|
||||
###
|
||||
# Copyright (c) 2004, Jeremiah Fincher
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions, and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions, and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
# * Neither the name of the author of this software nor the name of
|
||||
# contributors to this software may be used to endorse or promote products
|
||||
# derived from this software without specific prior written consent.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
###
|
||||
|
||||
"""
|
||||
Watches for paste-floods in a channel and takes appropriate measures against
|
||||
violators.
|
||||
"""
|
||||
|
||||
import supybot
|
||||
|
||||
__revision__ = "$Id$"
|
||||
__author__ = supybot.authors.jemfinch
|
||||
__contributors__ = {}
|
||||
|
||||
import supybot.plugins as plugins
|
||||
|
||||
import glob
|
||||
import os.path
|
||||
import reverend.thomas
|
||||
from cStringIO import StringIO as sio
|
||||
|
||||
import supybot.conf as conf
|
||||
import supybot.utils as utils
|
||||
from supybot.commands import *
|
||||
import supybot.ircutils as ircutils
|
||||
import supybot.registry as registry
|
||||
import supybot.callbacks as callbacks
|
||||
|
||||
|
||||
def configure(advanced):
|
||||
# This will be called by setup.py to configure this module. Advanced is
|
||||
# a bool that specifies whether the user identified himself as an advanced
|
||||
# user or not. You should effect your configuration by manipulating the
|
||||
# registry as appropriate.
|
||||
from supybot.questions import expect, anything, something, yn
|
||||
conf.registerPlugin('Bayes', True)
|
||||
|
||||
Bayes = conf.registerPlugin('Bayes')
|
||||
conf.registerChannelValue(Bayes, 'maximumLines',
|
||||
registry.NonNegativeInteger(4, """Determines the maximum allowable number
|
||||
of consecutive messages that classify as a paste. If this value is 0, no
|
||||
checking will be done."""))
|
||||
|
||||
def tokenize(s):
|
||||
return s.lower().split()
|
||||
|
||||
class PickleBayesDB(plugins.DbiChannelDB):
|
||||
class DB(object):
|
||||
def __init__(self, filename):
|
||||
self.filename = filename
|
||||
self.nickFilename = self.filename.replace('pickle', 'nick.pickle')
|
||||
self.bayes = reverend.thomas.Bayes(tokenize)
|
||||
if os.path.exists(self.filename):
|
||||
try:
|
||||
self.bayes.load(self.filename)
|
||||
except (EOFError, EnvironmentError), e:
|
||||
log.error('Couldn\'t load bayes pickle from %s: %s',
|
||||
self.filename, utils.exnToString(e))
|
||||
self.nickBayes = reverend.thomas.Bayes(tokenize)
|
||||
if os.path.exists(self.nickFilename):
|
||||
try:
|
||||
self.nickBayes.load(self.nickFilename)
|
||||
except (EOFError, EnvironmentError), e:
|
||||
log.error('Couldn\'t load nickbayes pickle from %s: %s',
|
||||
self.nickFilename, utils.exnToString(e))
|
||||
|
||||
def close(self):
|
||||
self.bayes.save(self.filename)
|
||||
self.nickBayes.save(self.nickFilename)
|
||||
flush = close
|
||||
|
||||
def train(self, kind, s):
|
||||
self.bayes.train(kind, s)
|
||||
|
||||
def trainNick(self, nick, s):
|
||||
self.nickBayes.train(nick, s)
|
||||
|
||||
def guess(self, s):
|
||||
matches = self.bayes.guess(s)
|
||||
if matches:
|
||||
if matches[0][1] > 0.5:
|
||||
if len(matches) > 1 and \
|
||||
matches[0][1] - matches[1][1] < 0.4:
|
||||
return None
|
||||
else:
|
||||
return matches[0]
|
||||
else:
|
||||
self.bayes.train('normal', s)
|
||||
return None
|
||||
|
||||
def guessNick(self, s):
|
||||
L = [t for t in self.nickBayes.guess(s) if t[1] > 0.01]
|
||||
if len(L) > 1:
|
||||
if L[0][1] / L[1][1] > 2:
|
||||
return [L[0]]
|
||||
return L
|
||||
|
||||
BayesDB = plugins.DB('Bayes', {'pickle': PickleBayesDB})
|
||||
|
||||
class Bayes(callbacks.Privmsg):
|
||||
def __init__(self):
|
||||
self.__parent = super(Bayes, self)
|
||||
self.__parent.__init__()
|
||||
global log
|
||||
log = self.log
|
||||
self.db = BayesDB()
|
||||
|
||||
def die(self):
|
||||
self.db.close()
|
||||
|
||||
def doPrivmsg(self, irc, msg):
|
||||
(channel, text) = msg.args
|
||||
if not ircutils.isChannel(channel) or msg.guessed:
|
||||
return
|
||||
kind = self.db.guess(channel, text)
|
||||
if kind is not None:
|
||||
(kind, prob) = kind
|
||||
prob *= 100
|
||||
text = utils.ellipsisify(text, 30)
|
||||
self.log.debug('Classified %s as %s. (%.2f%%)',
|
||||
utils.quoted(text), kind, prob)
|
||||
self.db.trainNick(channel, msg.nick, text)
|
||||
|
||||
def guess(self, irc, msg, args, channel, text):
|
||||
"""[<channel>] <text>
|
||||
|
||||
Guesses how <text> should be classified according to the Bayesian
|
||||
classifier for <channel>. <channel> is only necessary if the message
|
||||
isn't sent in the channel itself, and then only if
|
||||
supybot.databases.plugins.channelSpecific is True.
|
||||
"""
|
||||
msg.tag('guessed')
|
||||
kind = self.db.guess(channel, text)
|
||||
if kind is not None:
|
||||
(kind, prob) = kind
|
||||
prob *= 100
|
||||
irc.reply('That seems to me to be %s, '
|
||||
'but I\'m only %.2f certain.' % (kind, prob))
|
||||
else:
|
||||
irc.reply('I don\'t know what the heck that is.')
|
||||
guess = wrap(guess, ['channeldb', 'something'])
|
||||
|
||||
def who(self, irc, msg, args, channel, text):
|
||||
"""[<channel>] <text>
|
||||
|
||||
Guesses who might have said <text>. <channel> is only necessary if the
|
||||
message isn't sent in the channel itself, and then only if
|
||||
supybot.databases.plugins.channelSpecific is True.
|
||||
"""
|
||||
msg.tag('guessed')
|
||||
kinds = self.db.guessNick(channel, text)
|
||||
if kinds:
|
||||
if len(kinds) == 1:
|
||||
(kind, prob) = kinds.pop()
|
||||
irc.reply('It seems to me (with %.2f%% certainty) '
|
||||
'that %s said that.' % (prob*100, kind))
|
||||
else:
|
||||
kinds = ['%s (%.2f%%)' % (k, prob*100) for (k, prob) in kinds]
|
||||
irc.reply('I\'m not quite sure who said that, but it could be '
|
||||
+ utils.commaAndify(kinds, And='or'))
|
||||
else:
|
||||
irc.reply('I have no idea who might\'ve said that.')
|
||||
who = wrap(who, ['channeldb', 'something'])
|
||||
|
||||
def train(self, irc, msg, args, channel, language, pattern):
|
||||
"""[<channel>] <language> <glob>
|
||||
|
||||
|
||||
Trains the bot to recognize text similar to that contained in the files
|
||||
matching <glob> as text of the language <language>. <channel> is only
|
||||
necessary if the message isn't sent in the channel itself, and then
|
||||
only if supybot.databases.plugins.channelSpecific is True.
|
||||
"""
|
||||
filenames = glob.glob(pattern)
|
||||
if not filenames:
|
||||
irc.errorInvalid('glob', pattern)
|
||||
for filename in filenames:
|
||||
fd = file(filename)
|
||||
for line in fd:
|
||||
self.db.train(channel, language, line)
|
||||
fd.close()
|
||||
irc.replySuccess()
|
||||
train = wrap(train, ['channeldb', 'something', 'something'])
|
||||
|
||||
|
||||
Class = Bayes
|
||||
|
||||
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78:
|
@ -1,464 +0,0 @@
|
||||
###
|
||||
# Copyright (c) 2003, Daniel Berlin
|
||||
# Based on code from kibot
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions, and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions, and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
# * Neither the name of the author of this software nor the name of
|
||||
# contributors to this software may be used to endorse or promote products
|
||||
# derived from this software without specific prior written consent.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
###
|
||||
|
||||
"""
|
||||
Bugzilla bug retriever
|
||||
"""
|
||||
|
||||
__revision__ = "$Id$"
|
||||
|
||||
import os
|
||||
import re
|
||||
import csv
|
||||
import getopt
|
||||
import urllib
|
||||
import xml.dom.minidom as minidom
|
||||
|
||||
from itertools import imap, ifilter
|
||||
from htmlentitydefs import entitydefs as entities
|
||||
|
||||
|
||||
import supybot.conf as conf
|
||||
import supybot.utils as utils
|
||||
import supybot.plugins as plugins
|
||||
from supybot.commands import *
|
||||
import supybot.ircutils as ircutils
|
||||
import supybot.privmsgs as privmsgs
|
||||
import supybot.registry as registry
|
||||
import supybot.webutils as webutils
|
||||
import supybot.callbacks as callbacks
|
||||
import supybot.structures as structures
|
||||
|
||||
statusKeys = ['unconfirmed', 'new', 'assigned', 'reopened', 'resolved',
|
||||
'verified', 'closed']
|
||||
resolutionKeys = ['fixed', 'invalid', 'worksforme', 'needinfo',
|
||||
'test-request', 'wontfix', 'cantfix', 'moved', 'duplicate',
|
||||
'remind', 'later', 'notabug', 'notgnome', 'incomplete',
|
||||
'gnome1.x', 'moved']
|
||||
priorityKeys = ['p1', 'p2', 'p3', 'p4', 'p5', 'Low', 'Normal', 'High',
|
||||
'Immediate', 'Urgent']
|
||||
severityKeys = ['enhancement', 'trivial', 'minor', 'normal', 'major',
|
||||
'critical', 'blocker']
|
||||
|
||||
class BugzillaError(Exception):
|
||||
"""A bugzilla error"""
|
||||
pass
|
||||
|
||||
def configure(advanced):
|
||||
from supybot.questions import output, expect, anything, yn
|
||||
conf.registerPlugin('Bugzilla', True)
|
||||
output("""The Bugzilla plugin has the functionality to watch for URLs
|
||||
that match a specific pattern (we call this a snarfer). When
|
||||
a Supybot sees such a URL, it will parse the web page for
|
||||
information and reply with the results.""")
|
||||
if yn('Do you want this bug snarfer enabled by default?', default=False):
|
||||
conf.supybot.plugins.Bugzilla.bugSnarfer.setValue(True)
|
||||
|
||||
conf.registerPlugin('Bugzilla')
|
||||
conf.registerChannelValue(conf.supybot.plugins.Bugzilla, 'bugSnarfer',
|
||||
registry.Boolean(False, """Determines whether the bug snarfer will be
|
||||
enabled, such that any Bugzilla URLs and bug ### seen in the channel
|
||||
will have their information reported into the channel."""))
|
||||
conf.registerChannelValue(conf.supybot.plugins.Bugzilla, 'bold',
|
||||
registry.Boolean(True, """Determines whether results are bolded."""))
|
||||
conf.registerChannelValue(conf.supybot.plugins.Bugzilla, 'replyNoBugzilla',
|
||||
registry.String('I don\'t have a bugzilla %s.', """Determines the phrase
|
||||
to use when notifying the user that there is no information about that
|
||||
bugzilla site."""))
|
||||
conf.registerChannelValue(conf.supybot.plugins.Bugzilla, 'snarfTarget',
|
||||
registry.String('', """Determines the bugzilla site to query when the
|
||||
snarf command is triggered"""))
|
||||
|
||||
class Bugzillae(registry.SpaceSeparatedListOfStrings):
|
||||
List = ircutils.IrcSet
|
||||
|
||||
conf.registerGlobalValue(conf.supybot.plugins.Bugzilla, 'bugzillae',
|
||||
Bugzillae([], """Determines what bugzillae will be added to the bot when it
|
||||
starts."""))
|
||||
|
||||
def registerBugzilla(name, url='', description=''):
|
||||
conf.supybot.plugins.Bugzilla.bugzillae().add(name)
|
||||
group = conf.registerGroup(conf.supybot.plugins.Bugzilla.bugzillae, name)
|
||||
URL = conf.registerGlobalValue(group, 'url', registry.String(url, ''))
|
||||
DESC = conf.registerGlobalValue(group, 'description',
|
||||
registry.String(description, ''))
|
||||
if url:
|
||||
URL.setValue(url)
|
||||
if description:
|
||||
DESC.setValue(description)
|
||||
|
||||
class Bugzilla(callbacks.PrivmsgCommandAndRegexp):
|
||||
"""Show a link to a bug report with a brief description"""
|
||||
threaded = True
|
||||
callBefore = ['URL']
|
||||
regexps = ['bzSnarfer', 'bugzSnarf']
|
||||
|
||||
def __init__(self):
|
||||
callbacks.PrivmsgCommandAndRegexp.__init__(self)
|
||||
self.entre = re.compile('&(\S*?);')
|
||||
# Schema: {name, [url, description]}
|
||||
self.db = ircutils.IrcDict()
|
||||
for name in self.registryValue('bugzillae'):
|
||||
registerBugzilla(name)
|
||||
group = self.registryValue('bugzillae.%s' % name, value=False)
|
||||
self.db[name] = [group.url(), group.description()]
|
||||
self.shorthand = utils.abbrev(self.db.keys())
|
||||
|
||||
def keywords2query(self, keywords):
|
||||
"""Turn a list of keywords into a URL query string"""
|
||||
query = []
|
||||
for k in keywords:
|
||||
k = k.lower()
|
||||
if k in statusKeys:
|
||||
query.append('bug_status=%s' % k.upper())
|
||||
elif k in resolutionKeys:
|
||||
query.append('resolution=%s' % k.upper())
|
||||
elif k in priorityKeys:
|
||||
query.append('priority=%s' % k.upper())
|
||||
elif k in severityKeys:
|
||||
query.append('bug_severity=%s' % k.upper())
|
||||
query.append('ctype=csv')
|
||||
return query
|
||||
|
||||
def add(self, irc, msg, args, name, url, description):
|
||||
"""<name> <url> [<description>]
|
||||
|
||||
Add a bugzilla <url> to the list of defined bugzillae. <name>
|
||||
is the name that will be used to reference the bugzilla in all
|
||||
commands. Unambiguous abbreviations of <name> will be accepted also.
|
||||
<description> is the common name for the bugzilla and will
|
||||
be listed with the bugzilla query; if not given, it defaults to <name>.
|
||||
"""
|
||||
if not description:
|
||||
description = name
|
||||
if url[-1] == '/':
|
||||
url = url[:-1]
|
||||
self.db[name] = [url, description]
|
||||
registerBugzilla(name, url, description)
|
||||
self.shorthand = utils.abbrev(self.db.keys())
|
||||
irc.replySuccess()
|
||||
add = wrap(add, ['something', 'url', additional('text')])
|
||||
|
||||
def remove(self, irc, msg, args, name):
|
||||
"""<abbreviation>
|
||||
|
||||
Remove the bugzilla associated with <abbreviation> from the list of
|
||||
defined bugzillae.
|
||||
"""
|
||||
try:
|
||||
name = self.shorthand[name]
|
||||
del self.db[name]
|
||||
self.registryValue('bugzillae').remove(name)
|
||||
self.shorthand = utils.abbrev(self.db.keys())
|
||||
irc.replySuccess()
|
||||
except KeyError:
|
||||
s = self.registryValue('replyNoBugzilla', msg.args[0])
|
||||
irc.error(s % name)
|
||||
remove = wrap(remove, ['text'])
|
||||
|
||||
def list(self, irc, msg, args, name):
|
||||
"""[<abbreviation>]
|
||||
|
||||
List defined bugzillae. If <abbreviation> is specified, list the
|
||||
information for that bugzilla.
|
||||
"""
|
||||
if name:
|
||||
try:
|
||||
name = self.shorthand[name]
|
||||
(url, description) = self.db[name]
|
||||
irc.reply('%s: %s, %s' % (name, description, url))
|
||||
except KeyError:
|
||||
s = self.registryValue('replyNoBugzilla', msg.args[0])
|
||||
irc.error(s % name)
|
||||
else:
|
||||
if self.db:
|
||||
L = self.db.keys()
|
||||
L.sort()
|
||||
irc.reply(utils.commaAndify(L))
|
||||
else:
|
||||
irc.reply('I have no defined bugzillae.')
|
||||
list = wrap(list, [additional('text')])
|
||||
|
||||
def bugzSnarf(self, irc, msg, match):
|
||||
r"""\bbug\b(?:id|ids|#)?\s+(?:id|ids|#)?(?P<bug>\d+)"""
|
||||
|
||||
snarfTarget = self.registryValue('snarfTarget')
|
||||
if not snarfTarget:
|
||||
return
|
||||
bugid = match.group('bug')
|
||||
name = self.shorthand[snarfTarget]
|
||||
try:
|
||||
(url, description) = self.db[name]
|
||||
except KeyError:
|
||||
s = self.registryValue('replyNoBugzilla', name)
|
||||
irc.error(s % name)
|
||||
return
|
||||
if not self.registryValue('bugSnarfer', name):
|
||||
return
|
||||
queryurl = '%s/xml.cgi?id=%s' % (url, bugid)
|
||||
bold = self.registryValue('bold', name)
|
||||
try:
|
||||
summary = self._get_short_bug_summary(queryurl,description,bugid)
|
||||
except BugzillaError, e:
|
||||
irc.error(str(e))
|
||||
return
|
||||
except IOError, e:
|
||||
s = '%s. Try yourself: %s' % (e, queryurl)
|
||||
irc.error(s)
|
||||
report = {}
|
||||
report['id'] = bugid
|
||||
report['url'] = str('%s/show_bug.cgi?id=%s' % (url, bugid))
|
||||
report['title'] = str(summary['title'])
|
||||
report['summary'] = str(self._mk_summary_string(summary, bold))
|
||||
report['product'] = str(summary['product'])
|
||||
s = '%(product)s bug #%(id)s: %(title)s %(summary)s' % report
|
||||
irc.reply(s, prefixName=False)
|
||||
|
||||
def bzSnarfer(self, irc, msg, match):
|
||||
r"(http://\S+)/show_bug.cgi\?id=([0-9]+)"
|
||||
if not self.registryValue('bugSnarfer', msg.args[0]):
|
||||
return
|
||||
queryurl = '%s/xml.cgi?id=%s' % (match.group(1), match.group(2))
|
||||
try:
|
||||
summary = self._get_short_bug_summary(queryurl,
|
||||
'Snarfed Bugzilla URL',
|
||||
match.group(2))
|
||||
except BugzillaError, e:
|
||||
irc.reply(str(e))
|
||||
return
|
||||
except IOError, e:
|
||||
msgtouser = '%s. Try yourself: %s' % (e, queryurl)
|
||||
irc.reply(msgtouser)
|
||||
return
|
||||
bold = self.registryValue('bold', msg.args[0])
|
||||
report = {}
|
||||
report['id'] = match.group(2)
|
||||
report['url'] = str('%s/show_bug.cgi?id=%s' % (match.group(1),
|
||||
match.group(2)))
|
||||
report['title'] = str(summary['title'])
|
||||
report['summary'] = str(self._mk_summary_string(summary, bold))
|
||||
report['product'] = str(summary['product'])
|
||||
s = '%(product)s bug #%(id)s: %(title)s %(summary)s' % report
|
||||
irc.reply(s, prefixName=False)
|
||||
bzSnarfer = urlSnarfer(bzSnarfer)
|
||||
|
||||
def urlquery2bugslist(self, url, query):
|
||||
"""Given a URL and query list for a CSV bug list, it'll return
|
||||
all the bugs in a dict
|
||||
"""
|
||||
bugs = {}
|
||||
try:
|
||||
url = '%s/buglist.cgi?%s' % (url, '&'.join(query))
|
||||
u = webutils.getUrlFd(url)
|
||||
except webutils.WebError, e:
|
||||
return bugs
|
||||
# actually read in the file
|
||||
csvreader = csv.reader(u)
|
||||
# read header
|
||||
fields = csvreader.next()
|
||||
# read the rest of the list
|
||||
for bug in csvreader:
|
||||
if isinstance(bug, basestring):
|
||||
bugid = bug
|
||||
else:
|
||||
if bug:
|
||||
bugid = bug[0]
|
||||
else:
|
||||
raise callbacks.Error, 'No bugs found.'
|
||||
try:
|
||||
bugid = int(bugid)
|
||||
except ValueError:
|
||||
pass
|
||||
bugs[bugid] = {}
|
||||
i = 1
|
||||
for f in fields[1:]:
|
||||
bugs[bugid][f] = bug[i]
|
||||
i += 1
|
||||
u.close()
|
||||
return bugs
|
||||
|
||||
def search(self, irc, msg, args, optlist, name, searchstr):
|
||||
"""[--keywords=<keyword>] <bugzilla name> <search string in desc>
|
||||
|
||||
Look for bugs with <search string in the desc>, also matching
|
||||
<keywords>. <keywords> can be statuses, severities, priorities, or
|
||||
resolutions, separated by commas"""
|
||||
keywords = None
|
||||
for (option, arguments) in optlist:
|
||||
if option == 'keywords':
|
||||
keywords = arguments.split(',')
|
||||
if not keywords:
|
||||
keywords = ['UNCONFIRMED', 'NEW', 'ASSIGNED', 'REOPENED']
|
||||
query = self.keywords2query(keywords)
|
||||
query.append('short_desc_type=allwordssubstr')
|
||||
query.append('short_desc=%s' % urllib.quote(searchstr))
|
||||
query.append('order=Bug+Number')
|
||||
try:
|
||||
name = self.shorthand[name]
|
||||
(url, description) = self.db[name]
|
||||
except KeyError:
|
||||
s = self.registryValue('replyNoBugzilla', msg.args[0])
|
||||
irc.error(s % name)
|
||||
return
|
||||
bugs = self.urlquery2bugslist(url, query)
|
||||
bugids = bugs.keys()
|
||||
bugids.sort()
|
||||
if not bugs:
|
||||
irc.error('I could not find any bugs.')
|
||||
return
|
||||
s = '%s match %s (%s): %s.' % \
|
||||
(utils.nItems('bug', len(bugs)), utils.quoted(searchstr),
|
||||
' AND '.join(keywords), utils.commaAndify(map(str, bugids)))
|
||||
irc.reply(s)
|
||||
search = wrap(search, [getopts({'keywords': 'something'}),
|
||||
'something', 'text'])
|
||||
|
||||
def bug(self, irc, msg, args, name, number):
|
||||
"""<abbreviation> <number>
|
||||
|
||||
Look up bug <number> in the bugzilla associated with <abbreviation>.
|
||||
"""
|
||||
try:
|
||||
name = self.shorthand[name]
|
||||
(url, description) = self.db[name]
|
||||
except KeyError:
|
||||
s = self.registryValue('replyNoBugzilla', msg.args[0])
|
||||
irc.error(s % utils.quoted(name))
|
||||
return
|
||||
queryurl = '%s/xml.cgi?id=%s' % (url, number)
|
||||
try:
|
||||
summary = self._get_short_bug_summary(queryurl,description,number)
|
||||
except BugzillaError, e:
|
||||
irc.error(str(e))
|
||||
return
|
||||
except IOError, e:
|
||||
s = '%s. Try yourself: %s' % (e, queryurl)
|
||||
irc.error(s)
|
||||
bold = self.registryValue('bold', msg.args[0])
|
||||
report = {}
|
||||
report['zilla'] = description
|
||||
report['id'] = number
|
||||
report['url'] = '%s/show_bug.cgi?id=%s' % (url, number)
|
||||
report['title'] = str(summary['title'])
|
||||
report['summary'] = self._mk_summary_string(summary, bold)
|
||||
s = '%(zilla)s bug #%(id)s: %(title)s %(summary)s %(url)s' % report
|
||||
irc.reply(s)
|
||||
bug = wrap(bug, ['something', ('id', 'bug')])
|
||||
|
||||
def _mk_summary_string(self, summary, bold):
|
||||
L = []
|
||||
if bold:
|
||||
decorate = lambda s: ircutils.bold(s)
|
||||
else:
|
||||
decorate = lambda s: s
|
||||
if 'product' in summary:
|
||||
L.append(decorate('Product: ') + summary['product'])
|
||||
if 'component' in summary:
|
||||
L.append(decorate('Component: ') + summary['component'])
|
||||
if 'severity' in summary:
|
||||
L.append(decorate('Severity: ') + summary['severity'])
|
||||
if 'assigned to' in summary:
|
||||
L.append(decorate('Assigned to: ') + summary['assigned to'])
|
||||
if 'status' in summary:
|
||||
L.append(decorate('Status: ') + summary['status'])
|
||||
if 'resolution' in summary:
|
||||
L.append(decorate('Resolution: ') + summary['resolution'])
|
||||
return ', '.join(imap(str, L))
|
||||
|
||||
def _get_short_bug_summary(self, url, desc, number):
|
||||
try:
|
||||
bugxml = self._getbugxml(url, desc)
|
||||
zilladom = minidom.parseString(bugxml)
|
||||
except Exception, e:
|
||||
s = 'Could not parse XML returned by %s bugzilla: %s' % (desc, e)
|
||||
raise BugzillaError, s
|
||||
bug_n = zilladom.getElementsByTagName('bug')[0]
|
||||
if bug_n.hasAttribute('error'):
|
||||
errtxt = bug_n.getAttribute('error')
|
||||
s = 'Error getting %s bug #%s: %s' % (desc, number, errtxt)
|
||||
raise BugzillaError, s
|
||||
summary = {}
|
||||
try:
|
||||
node = bug_n.getElementsByTagName('short_desc')[0]
|
||||
summary['title'] = self._getnodetxt(node)
|
||||
node = bug_n.getElementsByTagName('bug_status')[0]
|
||||
summary['status'] = self._getnodetxt(node)
|
||||
try:
|
||||
node = bug_n.getElementsByTagName('resolution')[0]
|
||||
summary['resolution'] = self._getnodetxt(node)
|
||||
except:
|
||||
pass
|
||||
node = bug_n.getElementsByTagName('assigned_to')[0]
|
||||
summary['assigned to'] = self._getnodetxt(node)
|
||||
node = bug_n.getElementsByTagName('product')[0]
|
||||
summary['product'] = self._getnodetxt(node)
|
||||
node = bug_n.getElementsByTagName('component')[0]
|
||||
summary['component'] = self._getnodetxt(node)
|
||||
node = bug_n.getElementsByTagName('bug_severity')[0]
|
||||
summary['severity'] = self._getnodetxt(node)
|
||||
except Exception, e:
|
||||
s = 'Could not parse XML returned by %s bugzilla: %s' % (desc, e)
|
||||
raise BugzillaError, s
|
||||
return summary
|
||||
|
||||
def _getbugxml(self, url, desc):
|
||||
try:
|
||||
bugxml = webutils.getUrl(url)
|
||||
except webutils.WebError, e:
|
||||
raise IOError, 'Connection to %s bugzilla failed' % desc
|
||||
if not bugxml:
|
||||
raise IOError, 'Error getting bug content from %s' % desc
|
||||
return bugxml
|
||||
|
||||
def _getnodetxt(self, node):
|
||||
L = []
|
||||
for childnode in node.childNodes:
|
||||
if childnode.nodeType == childnode.TEXT_NODE:
|
||||
L.append(childnode.data)
|
||||
val = ''.join(L)
|
||||
if node.hasAttribute('encoding'):
|
||||
encoding = node.getAttribute('encoding')
|
||||
if encoding == 'base64':
|
||||
try:
|
||||
val = val.decode('base64')
|
||||
except:
|
||||
val = 'Cannot convert bug data from base64.'
|
||||
while self.entre.search(val):
|
||||
entity = self.entre.search(val).group(1)
|
||||
if entity in entities:
|
||||
val = self.entre.sub(entities[entity], val)
|
||||
else:
|
||||
val = self.entre.sub('?', val)
|
||||
return val
|
||||
|
||||
|
||||
Class = Bugzilla
|
||||
|
||||
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78:
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user