summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsand <daniel@spatof.org>2013-05-16 16:55:42 (GMT)
committer sand <daniel@spatof.org>2013-05-16 16:55:42 (GMT)
commitc801306406594ed401b0af3fffcc21a806f7ca8b (patch)
tree50a1c89844ae573ebfb58cd71ba93e288049ca4d
parent17489225e6ae7cf8c892e4ec0149c508a5bd7066 (diff)
documentazione
-rw-r--r--README.md42
-rw-r--r--doc/conf.py4
-rw-r--r--doc/index.rst7
-rw-r--r--doc/plugins.rst141
-rw-r--r--doc/requirements.txt8
-rw-r--r--pinolo/bot.py9
-rw-r--r--pinolo/plugins/__init__.py26
7 files changed, 65 insertions, 172 deletions
diff --git a/README.md b/README.md
index 9dbaea0..3ad4568 100644
--- a/README.md
+++ b/README.md
@@ -1,51 +1,51 @@
# pinolo: The naughty IRC bot
-pinolo is an IRC bot written for fun.
+pinolo is an IRC bot written for fun; it has support for multiple
+connections, SSL servers, plugins and some other nice features.
## Requirements
-- python >= 2.5
-- SQLAlchemy
-- Xapian
-- gevent
+It has been written and tested with Python 2.6 and 2.7.
+- python >= 2.6, untested with 3.x
+- SQLAlchemy 0.7.x (only needed by plugins)
+
+- requests 1.2.x (for http based plugins)
+- beautifulsoup4 4.1.x (for http and html based plugins)
+- Whoosh 2.4.x (for quotes plugins)
+- PyStemmer 1.3.x (for quotes plugin)
## Getting started
+See `sample.cfg` for an example of the configuration file, then create
+one and remember to create the `datadir` directory.
### From source
- Check out the pinolo source code using git:
- git clone git://git.dyne.org/pinolo.git
-
- - Install the needed python libraries (including the development
- libraries); with Debian/Ubuntu this is usually:
-
- apt-get intall python-gevent python-sqlalchemy python-xapian
-
- Or with pip:
-
- pip install -r doc/requirements.txt
+ git clone http://git.spatof.org/pinolo.git
- - Install pinolo:
+ - Install pinolo (better in a virtualenv):
python setup.py install
-
### Development mode
- Install pinolo in development mode:
cd pinolo/
- pip install -e ./
-
+ python setup.py develop
## Notes
-The code is messy and ugly.
-
+Pinolo has been recently rewritten to avoid `gevent` so some part of
+the codes still need to be polished.
## License
BSD, see included *LICENSE* file.
+
+Author(s):
+
+- Daniel Kertesz <daniel@spatof.org>
diff --git a/doc/conf.py b/doc/conf.py
index ed15c27..a0dad51 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -48,9 +48,9 @@ copyright = u'2011, sand'
# built documents.
#
# The short X.Y version.
-version = '0.1'
+version = '0.10'
# The full version, including alpha/beta/rc tags.
-release = '0.1.1'
+release = '0.10.1'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
diff --git a/doc/index.rst b/doc/index.rst
index 46a44b1..40f59df 100644
--- a/doc/index.rst
+++ b/doc/index.rst
@@ -6,6 +6,9 @@
Welcome to pinolo's documentation!
==================================
+pinolo is an IRC bot written for fun; it has support for multiple
+connections, SSL servers, plugins and some other nice features.
+
Contents:
.. toctree::
@@ -13,10 +16,6 @@ Contents:
plugins
-.. .. autoclass:: pinolo.irc2.IRCServer
-
-.. .. autoclass:: pinolo.irc2.Pinolo
-
Indices and tables
==================
diff --git a/doc/plugins.rst b/doc/plugins.rst
index b6b7b5c..a031285 100644
--- a/doc/plugins.rst
+++ b/doc/plugins.rst
@@ -1,138 +1,7 @@
-Come scrivere un plugin per pinolo
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Plugin system
+~~~~~~~~~~~~~
-Introduzione
-============
-Il sistema di plugin e' attualmente gestito con la libreria Yapsy_ (almeno
-finche' qualcuno non scrivera' o proporra' un sistema migliore); la
-documentazione (orrenda) di base per la scrittura di un plugin Yapsy_ la si puo'
-trovare `qui <http://yapsy.sourceforge.net/PluginManager.html>`_ e `qui
-<http://yapsy.sourceforge.net/IPlugin.html>`_, ma come al solito la soluzione
-migliore e' *Use the source, Luke!* guardando i sorgenti dei plugin di pinolo
-gia' esistenti.
+To create a new plugin you must subclass `Plugin`.
-
-Descrizione di un tipico plugin
-===============================
-Verra' ora descritto uno dei plugin inclusi in pinolo: il plugin **prcd**.
-
-Ogni plugin deve eseguire i seguenti *import*::
-
- from pinolo.main import CommandPlugin, PluginActivationError
- from pinolo import MyOptionParser, OptionParserError
-
-La classe del plugin invece deve derivare da ``CommandPlugin`` e se vuole
-utilizzare ``getopt`` deve creare un attributo di istanza per ogni *comando* da
-gestire; la sintassi per ``MyOptionParser`` e' identica a quella di
-``OptionParser`` incluso nel Pitone standard.
-
-::
-
- class Prcd(CommandPlugin):
- """This is the PRCD plugin"""
-
- prcd_opt = MyOptionParser(usage="!prcd [options]")
- cowsay_opt = MyOptionParser(usage="!PRCD [options]")
-
-L'inizializzazione del plugin puo' essere gestita nel metodo ``__init__`` della
-classe, ma se il plugin necessita di una configurazione esterna (ad esempio
-attraverso il file di configurazione principale) si dovra' usare il metodo
-``activate`` che riceve come parametro opzionale ``config``.
-
-L'oggetto ``config`` attualmente e' una ``namedtuple`` i cui attributi
-contengono la configurazione; ad esempio::
-
- >>> config.quotes_db
- "/path/to/quotes.sqlite3"
-
-Un esempio di ``activate`` che utilizza il parametro ``config`` per
-inizializzare il plugin::
-
- def activate(self, config):
- super(QuotesDb, self).activate()
-
- self.db_file = 'sqlite:///' + config.quotes_db
-
-Un plugin interagisce con gli eventi esterni attraverso il metodo ``handle``,
-che viene chiamato per ogni plugin **attivo** passandogli il parametro
-``request``, classe ``pinolo.Request`` descritta di seguito:
-
-.. autoclass:: pinolo.Request
- :members:
-
-Prima di tutto e' necessario verificare se il comando IRC specificato in
-``request.command`` verra' gestito da questo plugin; di seguito e' riportato
-l'esempio di un metodo ``handle`` che gestisce il comando IRC **prcd**.
-In questo metodo ``handle`` viene anche utilizzata la libreria ``getopt`` per
-gestire le opzioni in stile *linea di comando* del comando IRC; la soluzione
-utilizzata, poco elegante, e' quella di gestire l'*Exception*
-``OptionParserError`` e riportare direttamente l'errore all'utente, o in caso di
-opzioni valide, chiamare il metodo che gestisce il comando, passandogli le
-variabili ``request``, ``options`` e ``args``.
-
-::
-
- def handle(self, request):
- if request.command in [ 'prcd' ]:
- try:
- (options, args) = self.prcd_opt.parse_args(request.arguments)
- except OptionParserError, e:
- request.reply(str(e))
- else:
- self.simple_prcd(request, options, args)
-
-Un metodo per gestire finalmente il comando sara' tipo il seguente; il parametro
-``options`` viene usato per verificare la presenza di opzioni, mentre
-``request.reply`` viene utilizzato per rispondere in maniera *smart* a chi ha
-chiamato il comando IRC (*smart* perche' ``reply`` rispondera' pubblicamente in
-canale per le richieste pubbliche, e in query per le richieste private).
-
-::
-
- def simple_prcd(self, request, options, args):
- moccolo = None
-
- if options.category:
- if not options.category in self.prcd_db:
- request.reply("Categoria non trovata :(")
- return
-
- moccolo = random.choice(self.prcd_db[options.category])
-
- else:
- category = random.choice(self.prcd_db.keys())
- moccolo = random.choice(self.prcd_db[category])
- moccolo = "%s, %s" % (category, moccolo)
-
- request.reply(moccolo)
-
-.. warning::
-
- Il testo inviato su IRC con le chiamate a ``request.reply`` **non** deve
- essere ``Unicode``.
-
-
-Segnali
-=======
-I plugin di pinolo possono anche fare uso di un sistema di segnali implementato
-grazie alla libreria PyPubSub_::
-
- from twisted.python import log
- from pubsub import Publisher as pubsub
-
-In questo esempio il plugin salva dei nuovi dati nel database e invia il segnale
-``get_quote`` passando come parametro ``data`` i suddetti dati (ehm)::
-
- pubsub.sendMessage('add_quote', data=result)
-
-Un plugin puo' *ascoltare* uno o piu' segnali chiamando il metodo
-``subscribe``::
-
- pubsub.subscribe(self.add_quote_signal, 'add_quote')
-
- def add_quote_signal(self, event):
- log.msg("Adding a quote from a signal")
- self.add_quote(event.data)
-
-.. _Yapsy: http://pypi.python.org/pypi/Yapsy/
-.. _PyPubSub: http://pypi.python.org/pypi/PyPubSub
+.. autoclass:: pinolo.plugins.Plugin
+ :members: __init__, activate, deactivate
diff --git a/doc/requirements.txt b/doc/requirements.txt
index fc01736..0f9c6a5 100644
--- a/doc/requirements.txt
+++ b/doc/requirements.txt
@@ -1,5 +1,5 @@
-SQLAlchemy==0.7.8
+SQLAlchemy==0.7.10
+PyStemmer==1.3.0
+Whoosh==2.4.1
+requests==1.2.0
beautifulsoup4==4.1.3
-gevent==0.13.8
-greenlet==0.4.0
-requests==0.14.0
diff --git a/pinolo/bot.py b/pinolo/bot.py
index 3c0428e..54563e4 100644
--- a/pinolo/bot.py
+++ b/pinolo/bot.py
@@ -67,7 +67,7 @@ class Bot(SignalDispatcher):
self.signal_emit("pre_connect")
for conn_name, conn_obj in self.connections.iteritems():
- print "Connecting to server: {0}".format(conn_name)
+ log.info("Connecting to server: %s" % conn_name)
conn_obj.connect()
for conn_obj in self.connections.values():
@@ -118,7 +118,7 @@ class Bot(SignalDispatcher):
# This is ugly. XXX
if not in_sockets:
- log.error("No more active connections. exiting...")
+ log.warning("No more active connections. exiting...")
self.running = False
return
@@ -151,7 +151,7 @@ class Bot(SignalDispatcher):
if chunk == '':
conn_obj.connected = False
conn_obj.active = False
- print "{0} disconnected (EOF from server)".format(conn_obj.name)
+ log.error("{0} disconnected (EOF from server)".format(conn_obj.name))
break
else:
conn_obj.in_buffer += chunk
@@ -165,6 +165,7 @@ class Bot(SignalDispatcher):
# If this is the first time we get a "writable" status then
# we are actually connected to the remote server.
if conn_obj.connected == False:
+ log.info("Connected to %s" % conn_obj.name)
conn_obj.connected = True
# SSL socket setup
@@ -227,6 +228,7 @@ class Bot(SignalDispatcher):
def quit(self, message="Ctrl-C"):
"""Quit all connected clients"""
+
log.info("Shutting down all connections")
for conn_obj in self.connections.itervalues():
conn_obj.quit(message)
@@ -275,6 +277,7 @@ class Bot(SignalDispatcher):
def activate_plugins(self):
"""Call the activate method on all loaded plugins"""
+
for plugin_name, plugin_class in pinolo.plugins.registry:
log.info("Activating plugin %s" % plugin_name)
p_obj = plugin_class(self)
diff --git a/pinolo/plugins/__init__.py b/pinolo/plugins/__init__.py
index f508651..eb14a59 100644
--- a/pinolo/plugins/__init__.py
+++ b/pinolo/plugins/__init__.py
@@ -15,7 +15,19 @@ registry = []
class Plugin(object):
- """Base class for plugins"""
+ """Base class for plugins
+
+ This class use a `metaclass` to save a registry of loaded (aka imported)
+ plugins classes that can later be used to create the plugin instances.
+
+ A plugin loading sequence is divided in two steps: in the first step the
+ plugin is imported and __init__() executed. After all plugins have been
+ loaded the main class will load and activate the database support, so any
+ SQLAlchemy model defined in the plugins will be loaded.
+
+ When the main class have finished initialization it will call
+ :meth:`activate` on every plugin instance.
+ """
COMMAND_ALIASES = {}
@@ -30,12 +42,22 @@ class Plugin(object):
def __init__(self, bot, enabled=True):
"""Initialize the plugin instance with a pointer to the bot object"""
+
self.bot = bot
self.enabled = enabled
def activate(self):
- """Activate the plugin"""
+ """Activate the plugin
+
+ Here you can load the plugin state from the filesystem, initialize stuff,
+ etc.
+ """
pass
def deactivate(self):
+ """Deactivate the plugin
+
+ Here you can save the plugin state on the filesystem, or perform some
+ cleanup.
+ """
pass