mirror of
https://github.com/Mikaela/Limnoria.git
synced 2024-11-27 05:09:23 +01:00
Merge branch 'aka' into testing
This commit is contained in:
commit
7c30936ee1
@ -7,7 +7,7 @@ python:
|
||||
- "pypy"
|
||||
# command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors
|
||||
install:
|
||||
- pip install pytz feedparser charade --use-mirrors
|
||||
- pip install pytz feedparser charade sqlalchemy --use-mirrors
|
||||
# command to run tests, e.g. python setup.py test
|
||||
script:
|
||||
- echo $TRAVIS_PYTHON_VERSION
|
||||
|
1
plugins/Aka/README.txt
Normal file
1
plugins/Aka/README.txt
Normal file
@ -0,0 +1 @@
|
||||
Insert a description of your plugin here, with any notes, etc. about using it.
|
69
plugins/Aka/__init__.py
Normal file
69
plugins/Aka/__init__.py
Normal file
@ -0,0 +1,69 @@
|
||||
###
|
||||
# Copyright (c) 2013, Valentin Lorentz
|
||||
# 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 a description of the plugin (to be presented to the user inside the wizard)
|
||||
here. This should describe *what* the plugin does.
|
||||
"""
|
||||
|
||||
import supybot
|
||||
import supybot.world as world
|
||||
|
||||
# Use this for the version of this plugin. You may wish to put a CVS keyword
|
||||
# in here if you're keeping the plugin in CVS or some similar system.
|
||||
__version__ = ""
|
||||
|
||||
# XXX Replace this with an appropriate author or supybot.Author instance.
|
||||
__author__ = supybot.authors.unknown
|
||||
|
||||
# This is a dictionary mapping supybot.Author instances to lists of
|
||||
# contributions.
|
||||
__contributors__ = {}
|
||||
|
||||
# This is a url where the most recent plugin package can be downloaded.
|
||||
__url__ = '' # 'http://supybot.com/Members/yourname/Aka/download'
|
||||
|
||||
from . import config
|
||||
from . import plugin
|
||||
from imp import reload
|
||||
# In case we're being reloaded.
|
||||
reload(config)
|
||||
reload(plugin)
|
||||
# Add more reloads here if you add third-party modules and want them to be
|
||||
# reloaded when this plugin is reloaded. Don't forget to import them as well!
|
||||
|
||||
if world.testing:
|
||||
from . import test
|
||||
|
||||
Class = plugin.Class
|
||||
configure = config.configure
|
||||
|
||||
|
||||
# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79:
|
56
plugins/Aka/config.py
Normal file
56
plugins/Aka/config.py
Normal file
@ -0,0 +1,56 @@
|
||||
###
|
||||
# Copyright (c) 2013, Valentin Lorentz
|
||||
# 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.conf as conf
|
||||
import supybot.registry as registry
|
||||
try:
|
||||
from supybot.i18n import PluginInternationalization
|
||||
_ = PluginInternationalization('Aka')
|
||||
except:
|
||||
# Placeholder that allows to run the plugin on a bot
|
||||
# without the i18n module
|
||||
_ = lambda x:x
|
||||
|
||||
def configure(advanced):
|
||||
# This will be called by supybot 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('Aka', True)
|
||||
|
||||
|
||||
Aka = conf.registerPlugin('Aka')
|
||||
# This is where your configuration variables (if any) should go. For example:
|
||||
# conf.registerGlobalValue(Aka, 'someConfigVariableName',
|
||||
# registry.Boolean(False, _("""Help for someConfigVariableName.""")))
|
||||
|
||||
|
||||
# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79:
|
1
plugins/Aka/local/__init__.py
Normal file
1
plugins/Aka/local/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
# Stub so local is a module, used for third-party modules
|
136
plugins/Aka/messages.pot
Normal file
136
plugins/Aka/messages.pot
Normal file
@ -0,0 +1,136 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR ORGANIZATION
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"POT-Creation-Date: 2013-07-31 19:09+CEST\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=CHARSET\n"
|
||||
"Content-Transfer-Encoding: ENCODING\n"
|
||||
"Generated-By: pygettext.py 1.5\n"
|
||||
|
||||
|
||||
#: plugin.py:124 plugin.py:334
|
||||
msgid "This Aka already exists."
|
||||
msgstr ""
|
||||
|
||||
#: plugin.py:149 plugin.py:165 plugin.py:181
|
||||
msgid "This Aka does not exist"
|
||||
msgstr ""
|
||||
|
||||
#: plugin.py:151
|
||||
msgid "This Aka is already locked."
|
||||
msgstr ""
|
||||
|
||||
#: plugin.py:167
|
||||
msgid "This Aka is already unlocked."
|
||||
msgstr ""
|
||||
|
||||
#: plugin.py:226
|
||||
#, docstring
|
||||
msgid ""
|
||||
"Add the help for \"@plugin help Aka\" here\n"
|
||||
" This should describe *how* to use this plugin."
|
||||
msgstr ""
|
||||
|
||||
#: plugin.py:312
|
||||
msgid " at least"
|
||||
msgstr ""
|
||||
|
||||
#: plugin.py:321
|
||||
msgid "Locked by %s at %s"
|
||||
msgstr ""
|
||||
|
||||
#: plugin.py:324
|
||||
msgid ""
|
||||
"<an alias,%s %n>\n"
|
||||
"\n"
|
||||
"Alias for %q.%s"
|
||||
msgstr ""
|
||||
|
||||
#: plugin.py:325
|
||||
msgid "argument"
|
||||
msgstr ""
|
||||
|
||||
#: plugin.py:331
|
||||
msgid "You can't overwrite commands in this plugin."
|
||||
msgstr ""
|
||||
|
||||
#: plugin.py:344
|
||||
msgid "This Aka is locked."
|
||||
msgstr ""
|
||||
|
||||
#: plugin.py:348
|
||||
#, docstring
|
||||
msgid ""
|
||||
"[--channel <#channel>] <name> <command>\n"
|
||||
"\n"
|
||||
" Defines an alias <name> that executes <command>. The <command>\n"
|
||||
" should be in the standard \"command argument [nestedcommand argument]\"\n"
|
||||
" arguments to the alias; they'll be filled with the first, second, etc.\n"
|
||||
" arguments. $1, $2, etc. can be used for required arguments. @1, @2,\n"
|
||||
" etc. can be used for optional arguments. $* simply means \"all\n"
|
||||
" arguments that have not replaced $1, $2, etc.\", ie. it will also\n"
|
||||
" include optional arguments.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
|
||||
#: plugin.py:362 plugin.py:388 plugin.py:420 plugin.py:443
|
||||
msgid "%r is not a valid channel."
|
||||
msgstr ""
|
||||
|
||||
#: plugin.py:380
|
||||
#, docstring
|
||||
msgid ""
|
||||
"[--channel <#channel>] <name>\n"
|
||||
"\n"
|
||||
" Removes the given alias, if unlocked.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
|
||||
#: plugin.py:402
|
||||
#, docstring
|
||||
msgid ""
|
||||
"Check if the user has any of the required capabilities to manage\n"
|
||||
" the regexp database."
|
||||
msgstr ""
|
||||
|
||||
#: plugin.py:412
|
||||
#, docstring
|
||||
msgid ""
|
||||
"[--channel <#channel>] <alias>\n"
|
||||
"\n"
|
||||
" Locks an alias so that no one else can change it.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
|
||||
#: plugin.py:435
|
||||
#, docstring
|
||||
msgid ""
|
||||
"[--channel <#channel>] <alias>\n"
|
||||
"\n"
|
||||
" Unlocks an alias so that people can define new aliases over it.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
|
||||
#: plugin.py:458
|
||||
#, docstring
|
||||
msgid ""
|
||||
"takes no arguments\n"
|
||||
"\n"
|
||||
" Imports the Alias database into Aka's, and clean the former."
|
||||
msgstr ""
|
||||
|
||||
#: plugin.py:463
|
||||
msgid "Alias plugin is not loaded."
|
||||
msgstr ""
|
||||
|
||||
#: plugin.py:473
|
||||
msgid "Error occured when importing the %n: %L"
|
||||
msgstr ""
|
||||
|
485
plugins/Aka/plugin.py
Normal file
485
plugins/Aka/plugin.py
Normal file
@ -0,0 +1,485 @@
|
||||
###
|
||||
# Copyright (c) 2013, Valentin Lorentz
|
||||
# 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 re
|
||||
import os
|
||||
import sys
|
||||
import datetime
|
||||
|
||||
import supybot.utils as utils
|
||||
import supybot.ircdb as ircdb
|
||||
from supybot.commands import *
|
||||
import supybot.plugins as plugins
|
||||
import supybot.ircutils as ircutils
|
||||
import supybot.callbacks as callbacks
|
||||
from supybot.i18n import PluginInternationalization
|
||||
_ = PluginInternationalization('Aka')
|
||||
|
||||
try:
|
||||
import sqlalchemy
|
||||
import sqlalchemy.ext
|
||||
import sqlalchemy.ext.declarative
|
||||
except ImportError:
|
||||
sqlalchemy = None
|
||||
|
||||
if sqlalchemy:
|
||||
|
||||
Base = sqlalchemy.ext.declarative.declarative_base()
|
||||
class Alias(Base):
|
||||
__tablename__ = 'aliases'
|
||||
|
||||
id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True)
|
||||
name = sqlalchemy.Column(sqlalchemy.String, unique=True, nullable=False)
|
||||
alias = sqlalchemy.Column(sqlalchemy.String, nullable=False)
|
||||
|
||||
locked = sqlalchemy.Column(sqlalchemy.Boolean, nullable=False)
|
||||
locked_by = sqlalchemy.Column(sqlalchemy.String, nullable=True)
|
||||
locked_at = sqlalchemy.Column(sqlalchemy.DateTime, nullable=True)
|
||||
|
||||
def __init__(self, name, alias):
|
||||
self.name = name
|
||||
self.alias = alias
|
||||
self.locked = False
|
||||
self.locked_by = None
|
||||
self.locked_at = None
|
||||
def __repr__(self):
|
||||
return "<Alias('%r', '%r')>" % (self.name, self.alias)
|
||||
|
||||
# TODO: Add table for usage statistics
|
||||
|
||||
class SqlAlchemyAkaDB(object):
|
||||
def __init__(self, filename):
|
||||
self.engines = ircutils.IrcDict()
|
||||
self.filename = filename
|
||||
self.sqlalchemy = sqlalchemy
|
||||
|
||||
def close(self):
|
||||
self.dbs.clear()
|
||||
|
||||
def get_db(self, channel):
|
||||
if channel in self.engines:
|
||||
engine = self.engines[channel]
|
||||
else:
|
||||
filename = plugins.makeChannelFilename(self.filename, channel)
|
||||
exists = os.path.exists(filename)
|
||||
engine = sqlalchemy.create_engine('sqlite:///' + filename)
|
||||
if not exists:
|
||||
Base.metadata.create_all(engine)
|
||||
self.engines[channel] = engine
|
||||
assert engine.execute("select 1").scalar() == 1
|
||||
Session = sqlalchemy.orm.sessionmaker()
|
||||
Session.configure(bind=engine)
|
||||
return Session()
|
||||
|
||||
|
||||
def has_aka(self, channel, name):
|
||||
if sys.version_info[0] < 3 and isinstance(name, str):
|
||||
name = name.decode('utf8')
|
||||
count = self.get_db(channel).query(Alias) \
|
||||
.filter(Alias.name == name) \
|
||||
.count()
|
||||
return bool(count)
|
||||
def get_aka_list(self, channel):
|
||||
list_ = list(self.get_db(channel).query(Alias.name))
|
||||
return list_
|
||||
|
||||
def get_alias(self, channel, name):
|
||||
if sys.version_info[0] < 3 and isinstance(name, str):
|
||||
name = name.decode('utf8')
|
||||
try:
|
||||
return self.get_db(channel).query(Alias.alias) \
|
||||
.filter(Alias.name == name).one()[0]
|
||||
except sqlalchemy.orm.exc.NoResultFound:
|
||||
return None
|
||||
|
||||
def add_aka(self, channel, name, alias):
|
||||
if self.has_aka(channel, name):
|
||||
raise AkaError(_('This Aka already exists.'))
|
||||
if sys.version_info[0] < 3:
|
||||
if isinstance(name, str):
|
||||
name = name.decode('utf8')
|
||||
if isinstance(alias, str):
|
||||
alias = alias.decode('utf8')
|
||||
db = self.get_db(channel)
|
||||
db.add(Alias(name, alias))
|
||||
db.commit()
|
||||
|
||||
def remove_aka(self, channel, name):
|
||||
if sys.version_info[0] < 3 and isinstance(name, str):
|
||||
name = name.decode('utf8')
|
||||
db = self.get_db(channel)
|
||||
db.query(Alias).filter(Alias.name == name).delete()
|
||||
db.commit()
|
||||
|
||||
def lock_aka(self, channel, name, by):
|
||||
if sys.version_info[0] < 3 and isinstance(name, str):
|
||||
name = name.decode('utf8')
|
||||
db = self.get_db(channel)
|
||||
try:
|
||||
aka = db.query(Alias) \
|
||||
.filter(Alias.name == name).one()
|
||||
except sqlalchemy.orm.exc.NoResultFound:
|
||||
raise AkaError(_('This Aka does not exist'))
|
||||
if aka.locked:
|
||||
raise AkaError(_('This Aka is already locked.'))
|
||||
aka.locked = True
|
||||
aka.locked_by = by
|
||||
aka.locked_at = datetime.datetime.now()
|
||||
db.commit()
|
||||
|
||||
def unlock_aka(self, channel, name, by):
|
||||
if sys.version_info[0] < 3 and isinstance(name, str):
|
||||
name = name.decode('utf8')
|
||||
db = self.get_db(channel)
|
||||
try:
|
||||
aka = db.query(Alias) \
|
||||
.filter(Alias.name == name).one()
|
||||
except sqlalchemy.orm.exc.NoResultFound:
|
||||
raise AkaError(_('This Aka does not exist'))
|
||||
if not aka.locked:
|
||||
raise AkaError(_('This Aka is already unlocked.'))
|
||||
aka.locked = False
|
||||
aka.locked_by = by
|
||||
aka.locked_at = datetime.datetime.now()
|
||||
db.commit()
|
||||
|
||||
def get_aka_lock(self, channel, name):
|
||||
if sys.version_info[0] < 3 and isinstance(name, str):
|
||||
name = name.decode('utf8')
|
||||
try:
|
||||
return self.get_db(channel) \
|
||||
.query(Alias.locked, Alias.locked_by, Alias.locked_at)\
|
||||
.filter(Alias.name == name).one()
|
||||
except sqlalchemy.orm.exc.NoResultFound:
|
||||
raise AkaError(_('This Aka does not exist'))
|
||||
|
||||
|
||||
def getArgs(args, required=1, optional=0, wildcard=0):
|
||||
if len(args) < required:
|
||||
raise callbacks.ArgumentError
|
||||
if len(args) < required + optional:
|
||||
ret = list(args) + ([''] * (required + optional - len(args)))
|
||||
elif len(args) >= required + optional:
|
||||
if not wildcard:
|
||||
ret = list(args[:required + optional - 1])
|
||||
ret.append(' '.join(args[required + optional - 1:]))
|
||||
else:
|
||||
ret = list(args)
|
||||
return ret
|
||||
|
||||
class AkaError(Exception):
|
||||
pass
|
||||
|
||||
class RecursiveAlias(AkaError):
|
||||
pass
|
||||
|
||||
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
|
||||
|
||||
AkaDB = plugins.DB('Aka', {'sqlalchemy': SqlAlchemyAkaDB})
|
||||
|
||||
class Aka(callbacks.Plugin):
|
||||
"""Add the help for "@plugin help Aka" here
|
||||
This should describe *how* to use this plugin."""
|
||||
|
||||
def __init__(self, irc):
|
||||
self.__parent = super(Aka, self)
|
||||
self.__parent.__init__(irc)
|
||||
self._db = AkaDB()
|
||||
|
||||
def isCommandMethod(self, name):
|
||||
if sys.version_info[0] < 3 and isinstance(name, str):
|
||||
name = name.decode('utf8')
|
||||
channel = dynamic.channel or 'global'
|
||||
return self._db.has_aka(channel, name) or \
|
||||
self._db.has_aka('global', name) or \
|
||||
self.__parent.isCommandMethod(name)
|
||||
isCommand = isCommandMethod
|
||||
|
||||
def listCommands(self):
|
||||
channel = dynamic.channel or 'global'
|
||||
return list(set(map(callbacks.formatCommand,
|
||||
self._db.get_aka_list(channel) +
|
||||
self._db.get_aka_list('global')) +
|
||||
self.__parent.listCommands()))
|
||||
|
||||
def getCommand(self, args):
|
||||
canonicalName = callbacks.canonicalName
|
||||
# All the code from here to the 'for' loop is copied from callbacks.py
|
||||
assert args == map(canonicalName, args)
|
||||
first = args[0]
|
||||
for cb in self.cbs:
|
||||
if first == cb.canonicalName():
|
||||
return cb.getCommand(args)
|
||||
if first == self.canonicalName() and len(args) > 1:
|
||||
ret = self.getCommand(args[1:])
|
||||
if ret:
|
||||
return [first] + ret
|
||||
for i in xrange(len(args), 0, -1):
|
||||
if self.isCommandMethod(callbacks.formatCommand(args[0:i])):
|
||||
return args[0:i]
|
||||
return []
|
||||
|
||||
def getCommandMethod(self, command):
|
||||
if len(command) == 1 or command[0] == self.canonicalName():
|
||||
try:
|
||||
return self.__parent.getCommandMethod(command)
|
||||
except AttributeError:
|
||||
pass
|
||||
name = callbacks.formatCommand(command)
|
||||
channel = dynamic.channel or 'global'
|
||||
original = self._db.get_alias(channel, name)
|
||||
if not original:
|
||||
original = self._db.get_alias('global', name)
|
||||
biggestDollar = findBiggestDollar(original)
|
||||
biggestAt = findBiggestAt(original)
|
||||
wildcard = '$*' in original
|
||||
def f(irc, msg, args):
|
||||
tokens = callbacks.tokenize(original)
|
||||
if biggestDollar or biggestAt:
|
||||
args = getArgs(args, required=biggestDollar, optional=biggestAt,
|
||||
wildcard=wildcard)
|
||||
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))
|
||||
args = args[biggestDollar:]
|
||||
if biggestAt:
|
||||
replace(tokens, lambda s: atRe.sub(regexpReplace, s))
|
||||
if wildcard:
|
||||
def everythingReplace(tokens):
|
||||
for (i, token) in enumerate(tokens):
|
||||
if isinstance(token, list):
|
||||
if everythingReplace(token):
|
||||
return
|
||||
if token == '$*':
|
||||
tokens[i:i+1] = args
|
||||
elif '$*' in token:
|
||||
tokens[i] = token.replace('$*', ' '.join(args))
|
||||
return False
|
||||
everythingReplace(tokens)
|
||||
self.Proxy(irc, msg, tokens)
|
||||
if biggestDollar and (wildcard or biggestAt):
|
||||
flexargs = _(' at least')
|
||||
else:
|
||||
flexargs = ''
|
||||
try:
|
||||
lock = self._db.get_aka_lock(channel, name)
|
||||
except AkaError:
|
||||
lock = self._db.get_aka_lock('global', name)
|
||||
(locked, locked_by, locked_at) = lock
|
||||
if locked:
|
||||
lock = ' ' + _('Locked by %s at %s') % (locked_by, locked_at)
|
||||
else:
|
||||
lock = ''
|
||||
doc = format(_('<an alias,%s %n>\n\nAlias for %q.%s'),
|
||||
flexargs, (biggestDollar, _('argument')), original, lock)
|
||||
f = utils.python.changeFunctionName(f, name, doc)
|
||||
return f
|
||||
|
||||
def _add_aka(self, channel, name, alias):
|
||||
if self.__parent.isCommandMethod(name):
|
||||
raise AkaError(_('You can\'t overwrite commands in '
|
||||
'this plugin.'))
|
||||
if self._db.has_aka(channel, name):
|
||||
raise AkaError(_('This Aka already exists.'))
|
||||
biggestDollar = findBiggestDollar(alias)
|
||||
biggestAt = findBiggestAt(alias)
|
||||
wildcard = '$*' in alias
|
||||
self._db.add_aka(channel, name, alias)
|
||||
|
||||
def _remove_aka(self, channel, name, evenIfLocked=False):
|
||||
if not evenIfLocked:
|
||||
(locked, by, at) = self._db.get_aka_lock(channel, name)
|
||||
if locked:
|
||||
raise AkaError(_('This Aka is locked.'))
|
||||
self._db.remove_aka(channel, name)
|
||||
|
||||
def add(self, irc, msg, args, optlist, name, alias):
|
||||
"""[--channel <#channel>] <name> <command>
|
||||
|
||||
Defines an alias <name> that executes <command>. The <command>
|
||||
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
|
||||
arguments that have not replaced $1, $2, etc.", ie. it will also
|
||||
include optional arguments.
|
||||
"""
|
||||
channel = 'global'
|
||||
for (option, arg) in optlist:
|
||||
if option == 'channel':
|
||||
if not ircutils.isChannel(arg):
|
||||
irc.error(_('%r is not a valid channel.') % arg,
|
||||
Raise=True)
|
||||
channel = arg
|
||||
if ' ' not in alias:
|
||||
# If it's a single word, they probably want $*.
|
||||
alias += ' $*'
|
||||
try:
|
||||
self._add_aka(channel, name, alias)
|
||||
self.log.info('Adding Aka %r for %r (from %s)' % (
|
||||
name, alias, msg.prefix))
|
||||
irc.replySuccess()
|
||||
except AkaError as e:
|
||||
irc.error(str(e))
|
||||
add = wrap(add, [getopts({
|
||||
'channel': 'somethingWithoutSpaces',
|
||||
}), 'something', 'text'])
|
||||
|
||||
def remove(self, irc, msg, args, optlist, name):
|
||||
"""[--channel <#channel>] <name>
|
||||
|
||||
Removes the given alias, if unlocked.
|
||||
"""
|
||||
channel = 'global'
|
||||
for (option, arg) in optlist:
|
||||
if option == 'channel':
|
||||
if not ircutils.isChannel(arg):
|
||||
irc.error(_('%r is not a valid channel.') % arg,
|
||||
Raise=True)
|
||||
channel = arg
|
||||
try:
|
||||
self._remove_aka(channel, name)
|
||||
self.log.info('Removing Aka %r (from %s)' % (name, msg.prefix))
|
||||
irc.replySuccess()
|
||||
except AkaError as e:
|
||||
irc.error(str(e))
|
||||
remove = wrap(remove, [getopts({
|
||||
'channel': 'somethingWithoutSpaces',
|
||||
}), 'something'])
|
||||
|
||||
def _checkManageCapabilities(self, irc, msg, channel):
|
||||
"""Check if the user has any of the required capabilities to manage
|
||||
the regexp database."""
|
||||
if channel != 'global':
|
||||
capability = ircdb.makeChannelCapability(channel, 'op')
|
||||
else:
|
||||
capability = 'admin'
|
||||
if not ircdb.checkCapability(msg.prefix, capability):
|
||||
irc.errorNoCapability(capability, Raise=True)
|
||||
|
||||
def lock(self, irc, msg, args, optlist, user, name):
|
||||
"""[--channel <#channel>] <alias>
|
||||
|
||||
Locks an alias so that no one else can change it.
|
||||
"""
|
||||
channel = 'global'
|
||||
for (option, arg) in optlist:
|
||||
if option == 'channel':
|
||||
if not ircutils.isChannel(arg):
|
||||
irc.error(_('%r is not a valid channel.') % arg,
|
||||
Raise=True)
|
||||
channel = arg
|
||||
self._checkManageCapabilities(irc, msg, channel)
|
||||
try:
|
||||
self._db.lock_aka(channel, name, user.name)
|
||||
except AkaError as e:
|
||||
irc.error(str(e))
|
||||
else:
|
||||
irc.replySuccess()
|
||||
lock = wrap(lock, [getopts({
|
||||
'channel': 'somethingWithoutSpaces',
|
||||
}), 'user', 'something'])
|
||||
|
||||
def unlock(self, irc, msg, args, optlist, user, name):
|
||||
"""[--channel <#channel>] <alias>
|
||||
|
||||
Unlocks an alias so that people can define new aliases over it.
|
||||
"""
|
||||
channel = 'global'
|
||||
for (option, arg) in optlist:
|
||||
if option == 'channel':
|
||||
if not ircutils.isChannel(arg):
|
||||
irc.error(_('%r is not a valid channel.') % arg,
|
||||
Raise=True)
|
||||
channel = arg
|
||||
self._checkManageCapabilities(irc, msg, channel)
|
||||
try:
|
||||
self._db.unlock_aka(channel, name, user.name)
|
||||
except AkaError as e:
|
||||
irc.error(str(e))
|
||||
else:
|
||||
irc.replySuccess()
|
||||
unlock = wrap(unlock, [getopts({
|
||||
'channel': 'somethingWithoutSpaces',
|
||||
}), 'user', 'something'])
|
||||
|
||||
def importaliasdatabase(self, irc, msg, args):
|
||||
"""takes no arguments
|
||||
|
||||
Imports the Alias database into Aka's, and clean the former."""
|
||||
alias_plugin = irc.getCallback('Alias')
|
||||
if alias_plugin is None:
|
||||
irc.error(_('Alias plugin is not loaded.'), Raise=True)
|
||||
errors = {}
|
||||
for (name, (command, locked, func)) in alias_plugin.aliases.items():
|
||||
try:
|
||||
self._add_aka('global', name, command)
|
||||
except AkaError as e:
|
||||
errors[name] = e.args[0]
|
||||
else:
|
||||
alias_plugin.removeAlias(name)
|
||||
if errors:
|
||||
irc.error(format(_('Error occured when importing the %n: %L'),
|
||||
(len(errors), 'following', 'command'),
|
||||
map(lambda x:'%s (%s)' % x, errors.items())))
|
||||
else:
|
||||
irc.replySuccess()
|
||||
importaliasdatabase = wrap(importaliasdatabase, ['owner'])
|
||||
|
||||
|
||||
Class = Aka
|
||||
|
||||
|
||||
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:
|
203
plugins/Aka/test.py
Normal file
203
plugins/Aka/test.py
Normal file
@ -0,0 +1,203 @@
|
||||
# -*- coding: utf8 -*-
|
||||
###
|
||||
# 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.
|
||||
###
|
||||
|
||||
from supybot.test import *
|
||||
|
||||
import supybot.conf as conf
|
||||
import supybot.plugin as plugin
|
||||
import supybot.registry as registry
|
||||
|
||||
import plugin as Aka
|
||||
|
||||
class FunctionsTest(SupyTestCase):
|
||||
def testFindBiggestDollar(self):
|
||||
self.assertEqual(Aka.findBiggestDollar(''), 0)
|
||||
self.assertEqual(Aka.findBiggestDollar('foo'), 0)
|
||||
self.assertEqual(Aka.findBiggestDollar('$0'), 0)
|
||||
self.assertEqual(Aka.findBiggestDollar('$1'), 1)
|
||||
self.assertEqual(Aka.findBiggestDollar('$2'), 2)
|
||||
self.assertEqual(Aka.findBiggestDollar('$2 $10'), 10)
|
||||
self.assertEqual(Aka.findBiggestDollar('$3'), 3)
|
||||
self.assertEqual(Aka.findBiggestDollar('$3 $2 $1'), 3)
|
||||
self.assertEqual(Aka.findBiggestDollar('foo bar $1'), 1)
|
||||
self.assertEqual(Aka.findBiggestDollar('foo $2 $1'), 2)
|
||||
self.assertEqual(Aka.findBiggestDollar('foo $0 $1'), 1)
|
||||
self.assertEqual(Aka.findBiggestDollar('foo $1 $3'), 3)
|
||||
self.assertEqual(Aka.findBiggestDollar('$10 bar $1'), 10)
|
||||
|
||||
class AkaChannelTestCase(ChannelPluginTestCase):
|
||||
plugins = ('Aka', 'Conditional', 'Filter', 'Math', 'Utilities',
|
||||
'Format', 'Reply')
|
||||
|
||||
def testDoesNotOverwriteCommands(self):
|
||||
# We don't have dispatcher commands anymore
|
||||
#self.assertError('aka add aka "echo foo bar baz"')
|
||||
self.assertError('aka add add "echo foo bar baz"')
|
||||
self.assertError('aka add remove "echo foo bar baz"')
|
||||
self.assertError('aka add lock "echo foo bar baz"')
|
||||
self.assertError('aka add unlock "echo foo bar baz"')
|
||||
|
||||
def testAkaHelp(self):
|
||||
self.assertNotError('aka add slashdot foo')
|
||||
self.assertRegexp('help slashdot', "Alias for .*foo")
|
||||
self.assertNotError('aka add nonascii echo éé')
|
||||
self.assertRegexp('help nonascii', "Alias for .*echo éé")
|
||||
|
||||
def testRemove(self):
|
||||
self.assertNotError('aka add foo echo bar')
|
||||
self.assertResponse('foo', 'bar')
|
||||
self.assertNotError('aka remove foo')
|
||||
self.assertError('foo')
|
||||
|
||||
def testDollars(self):
|
||||
self.assertNotError('aka add rot26 "rot13 [rot13 $1]"')
|
||||
self.assertResponse('rot26 foobar', 'foobar')
|
||||
|
||||
def testMoreDollars(self):
|
||||
self.assertNotError('aka add rev "echo $3 $2 $1"')
|
||||
self.assertResponse('rev foo bar baz', 'baz bar foo')
|
||||
|
||||
def testAllArgs(self):
|
||||
self.assertNotError('aka add swap "echo $2 $1 $*"')
|
||||
self.assertResponse('swap 1 2 3 4 5', '2 1 3 4 5')
|
||||
self.assertNotError('aka add foo "echo $1 @1 $*"')
|
||||
self.assertResponse('foo bar baz qux', 'bar baz baz qux')
|
||||
self.assertNotError('aka remove foo')
|
||||
self.assertNotError('aka add foo "echo $* $2 $*"')
|
||||
self.assertResponse('foo bar baz qux quux', 'qux quux baz qux quux')
|
||||
self.assertNotError('aka add moo echo $1 $*')
|
||||
self.assertError('moo')
|
||||
self.assertResponse('moo foo', 'foo')
|
||||
self.assertResponse('moo foo bar', 'foo bar')
|
||||
|
||||
def testChannel(self):
|
||||
self.assertNotError('aka add channel echo $channel')
|
||||
self.assertResponse('aka channel', self.channel)
|
||||
|
||||
def testAddRemoveAka(self):
|
||||
cb = self.irc.getCallback('Aka')
|
||||
cb._add_aka('global', 'foobar', 'echo sbbone')
|
||||
cb._db.lock_aka('global', 'foobar', 'evil_admin')
|
||||
self.assertResponse('foobar', 'sbbone')
|
||||
self.assertRegexp('list Aka', 'foobar')
|
||||
self.assertRaises(Aka.AkaError, cb._remove_aka, 'global', 'foobar')
|
||||
cb._remove_aka('global', 'foobar', evenIfLocked=True)
|
||||
self.assertNotRegexp('list Aka', 'foobar')
|
||||
self.assertError('foobar')
|
||||
|
||||
def testOptionalArgs(self):
|
||||
self.assertNotError('aka add myrepr "repr @1"')
|
||||
self.assertResponse('myrepr foo', '"foo"')
|
||||
self.assertResponse('myrepr ""', '""')
|
||||
|
||||
def testNoExtraSpaces(self):
|
||||
self.assertNotError('aka add foo "action takes $1\'s money"')
|
||||
self.assertResponse('foo bar', '\x01ACTION takes bar\'s money\x01')
|
||||
|
||||
def testNoExtraQuotes(self):
|
||||
self.assertNotError('aka add myre "echo s/$1/$2/g"')
|
||||
self.assertResponse('myre foo bar', 's/foo/bar/g')
|
||||
|
||||
def testSimpleAkaWithoutArgsImpliesDollarStar(self):
|
||||
self.assertNotError('aka add exo echo')
|
||||
self.assertResponse('exo foo bar baz', 'foo bar baz')
|
||||
|
||||
def testChannelPriority(self):
|
||||
self.assertNotError('aka add spam "echo foo"')
|
||||
self.assertNotError('aka add --channel %s spam "echo bar"' %
|
||||
self.channel)
|
||||
self.assertResponse('spam', 'bar')
|
||||
|
||||
self.assertNotError('aka add --channel %s egg "echo baz"' %
|
||||
self.channel)
|
||||
self.assertNotError('aka add egg "echo qux"')
|
||||
self.assertResponse('egg', 'baz')
|
||||
|
||||
def testComplicatedNames(self):
|
||||
self.assertNotError(u'aka add café "echo coffee"')
|
||||
self.assertResponse(u'café', 'coffee')
|
||||
|
||||
self.assertNotError('aka add "foo bar" "echo spam"')
|
||||
self.assertResponse('foo bar', 'spam')
|
||||
self.assertNotError('aka add "foo" "echo egg"')
|
||||
self.assertResponse('foo bar', 'spam')
|
||||
self.assertResponse('foo', 'egg')
|
||||
|
||||
def testRecursivity(self):
|
||||
self.assertNotError('aka add fact '
|
||||
r'"cif [nceq $1 0] \"echo 1\" '
|
||||
r'\"calc $1 * [fact [calc $1 - 1]]\""')
|
||||
self.assertResponse('fact 4', '24')
|
||||
self.assertRegexp('fact 50', 'more nesting')
|
||||
|
||||
class AkaTestCase(PluginTestCase):
|
||||
plugins = ('Aka', 'Alias', 'User', 'Utilities')
|
||||
|
||||
def testAkaLockedHelp(self):
|
||||
self.assertNotError('register evil_admin foo')
|
||||
|
||||
self.assertNotError('aka add slashdot foo')
|
||||
self.assertRegexp('help slashdot', "Alias for .*foo")
|
||||
self.assertNotRegexp('help slashdot', 'Locked by')
|
||||
self.assertNotError('aka lock slashdot')
|
||||
self.assertRegexp('help slashdot', 'Locked by evil_admin')
|
||||
self.assertNotError('aka unlock slashdot')
|
||||
self.assertNotRegexp('help slashdot', 'Locked by')
|
||||
|
||||
def testAliasImport(self):
|
||||
self.assertNotError('alias add foo "echo bar"')
|
||||
self.assertNotError(u'alias add baz "echo café"')
|
||||
self.assertNotError('aka add qux "echo quux"')
|
||||
self.assertResponse('alias foo', 'bar')
|
||||
self.assertResponse('alias baz', 'café')
|
||||
self.assertRegexp('aka foo', 'there is no command named')
|
||||
self.assertResponse('aka qux', 'quux')
|
||||
|
||||
self.assertNotError('aka importaliasdatabase')
|
||||
|
||||
self.assertRegexp('alias foo', 'there is no command named')
|
||||
self.assertResponse('aka foo', 'bar')
|
||||
self.assertResponse('aka baz', 'café')
|
||||
self.assertResponse('aka qux', 'quux')
|
||||
|
||||
self.assertNotError('alias add foo "echo test"')
|
||||
self.assertNotError('alias add spam "echo egg"')
|
||||
|
||||
self.assertRegexp('aka importaliasdatabase',
|
||||
r'the 1 following command: foo \(This Aka already exists.\)$')
|
||||
self.assertResponse('aka foo', 'bar')
|
||||
self.assertResponse('alias foo', 'test')
|
||||
self.assertRegexp('alias spam', 'there is no command named')
|
||||
self.assertResponse('aka spam', 'egg')
|
||||
|
||||
|
||||
|
||||
|
||||
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:
|
@ -847,6 +847,8 @@ class Databases(registry.SpaceSeparatedListOfStrings):
|
||||
v.insert(0, 'sqlite')
|
||||
if 'sqlite3' in sys.modules:
|
||||
v.insert(0, 'sqlite3')
|
||||
if 'sqlalchemy' in sys.modules:
|
||||
v.insert(0, 'sqlalchemy')
|
||||
return v
|
||||
|
||||
def serialize(self):
|
||||
|
Loading…
Reference in New Issue
Block a user