Merge remote-tracking branch 'progval/testing' into testing

This commit is contained in:
nyuszika7h 2011-08-13 13:45:02 +02:00
commit 9477ba20fb
15 changed files with 194 additions and 65 deletions

View File

@ -34,7 +34,7 @@ import supybot.conf as conf
class ConfigTestCase(ChannelPluginTestCase): class ConfigTestCase(ChannelPluginTestCase):
# We add utilities so there's something in supybot.plugins. # We add utilities so there's something in supybot.plugins.
plugins = ('Config', 'Utilities', 'AutoMode') plugins = ('Config', 'Utilities')
def testGet(self): def testGet(self):
self.assertNotRegexp('config get supybot.reply', r'registry\.Group') self.assertNotRegexp('config get supybot.reply', r'registry\.Group')
self.assertResponse('config supybot.protocols.irc.throttleTime', '0.0') self.assertResponse('config supybot.protocols.irc.throttleTime', '0.0')
@ -49,9 +49,6 @@ class ConfigTestCase(ChannelPluginTestCase):
def testHelp(self): def testHelp(self):
self.assertError('config help alsdkfj') self.assertError('config help alsdkfj')
self.assertError('config help supybot.alsdkfj') self.assertError('config help supybot.alsdkfj')
self.assertNotRegexp('config list supybot', '.*alsdkfj.*')
self.assertError('config help supybot.plugins.AutoMode.ban.alsdkfj')
self.assertNotRegexp('config list supybot.plugins.AutoMode.ban', '.*alsdkfj.*')
self.assertNotError('config help supybot') # We tell the user to list. self.assertNotError('config help supybot') # We tell the user to list.
self.assertNotError('config help supybot.plugins') self.assertNotError('config help supybot.plugins')
self.assertNotError('config help supybot.replies.success') self.assertNotError('config help supybot.replies.success')

View File

@ -2,7 +2,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Supybot-fr\n" "Project-Id-Version: Supybot-fr\n"
"POT-Creation-Date: 2011-02-26 09:49+CET\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n"
"PO-Revision-Date: 2011-06-26 18:57+0200\n" "PO-Revision-Date: 2011-08-10 14:27+0200\n"
"Last-Translator: skizzhg <skizzhg@gmx.com>\n" "Last-Translator: skizzhg <skizzhg@gmx.com>\n"
"Language-Team: Italian <skizzhg@gmx.com>\n" "Language-Team: Italian <skizzhg@gmx.com>\n"
"Language: it\n" "Language: it\n"
@ -159,7 +159,28 @@ msgstr ""
msgid "There were no notes for %r" msgid "There were no notes for %r"
msgstr "Non ci sono note per %r" msgstr "Non ci sono note per %r"
#: plugin.py:231 #: plugin.py:212
#, docstring
msgid ""
"<nick>\n"
"\n"
" Removes the latest note you sent to <nick>.\n"
" "
msgstr ""
"<nick>\n"
"\n"
" Rimuove l'ultima nota inviata a <nick>.\n"
" "
#: plugin.py:217
msgid "There are no note waiting for %s."
msgstr "Non ci sono note in attesa per %s."
#: plugin.py:228
msgid "There are no note from you waiting for %s."
msgstr "Non ci sono note in attesa per %s da te inviate."
#: plugin.py:252
msgid "Sent %s: <%s> %s" msgid "Sent %s: <%s> %s"
msgstr "Inviata %s: <%s> %s" msgstr "Inviata %s: <%s> %s"

View File

@ -2,7 +2,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Supybot-fr\n" "Project-Id-Version: Supybot-fr\n"
"POT-Creation-Date: 2011-02-26 09:49+CET\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n"
"PO-Revision-Date: 2011-08-10 02:11+0200\n" "PO-Revision-Date: 2011-08-10 14:39+0200\n"
"Last-Translator: skizzhg <skizzhg@gmx.com>\n" "Last-Translator: skizzhg <skizzhg@gmx.com>\n"
"Language-Team: Italian <skizzhg@gmx.com>\n" "Language-Team: Italian <skizzhg@gmx.com>\n"
"Language: it\n" "Language: it\n"
@ -161,12 +161,12 @@ msgstr "La versione di questo Supybot è %s. %s"
msgid "" msgid ""
"takes no arguments\n" "takes no arguments\n"
"\n" "\n"
" Returns a URL saying where to get Supybot.\n" " Returns a URL saying where to get Limnoria.\n"
" " " "
msgstr "" msgstr ""
"non necessita argomenti\n" "non necessita argomenti\n"
"\n" "\n"
" Riporta un URL che dice dove ottenere Supybot.\n" " Riporta un URL che dice dove ottenere Limnoria.\n"
" " " "
#: plugin.py:243 #: plugin.py:243
@ -274,7 +274,7 @@ msgstr "Non ho mai visto %s, lascio a te l'invio del messaggio."
msgid "%s wants me to tell you: %s" msgid "%s wants me to tell you: %s"
msgstr "%s vuole che ti dica: %s" msgstr "%s vuole che ti dica: %s"
#: plugin.py:430 #: plugin.py:431
#, docstring #, docstring
msgid "" msgid ""
"takes no arguments\n" "takes no arguments\n"
@ -287,7 +287,7 @@ msgstr ""
" Controlla che il bot sia ancora vivo.\n" " Controlla che il bot sia ancora vivo.\n"
" " " "
#: plugin.py:434 #: plugin.py:435
msgid "pong" msgid "pong"
msgstr "pong" msgstr "pong"

View File

@ -43,10 +43,15 @@ import supybot.irclib as irclib
import supybot.ircmsgs as ircmsgs import supybot.ircmsgs as ircmsgs
import supybot.ircutils as ircutils import supybot.ircutils as ircutils
import supybot.callbacks as callbacks import supybot.callbacks as callbacks
from supybot import commands
from supybot.utils.iter import ifilter from supybot.utils.iter import ifilter
from supybot.i18n import PluginInternationalization, internationalizeDocstring from supybot.i18n import PluginInternationalization, internationalizeDocstring
_ = PluginInternationalization('Misc') _ = PluginInternationalization('Misc')
class RegexpTimeout(Exception):
pass
class Misc(callbacks.Plugin): class Misc(callbacks.Plugin):
def __init__(self, irc): def __init__(self, irc):
self.__parent = super(Misc, self) self.__parent = super(Misc, self)
@ -332,10 +337,27 @@ class Misc(callbacks.Plugin):
predicates.setdefault('without', []).append(f) predicates.setdefault('without', []).append(f)
elif option == 'regexp': elif option == 'regexp':
def f(m, arg=arg): def f(m, arg=arg):
def f1(s, arg):
"""Since we can't enqueue match objects into the multiprocessing queue,
we'll just wrap the function to return bools."""
if arg.search(s) is not None:
return True
else:
return False
if ircmsgs.isAction(m): if ircmsgs.isAction(m):
return arg.search(ircmsgs.unAction(m)) m1 = ircmsgs.unAction(m)
#return arg.search(ircmsgs.unAction(m))
else: else:
return arg.search(m.args[1]) m1 = m.args[1]
#return arg.search(m.args[1])
try:
# use a subprocess here, since specially crafted regexps can
# take exponential time and hang up the bot.
# timeout of 0.1 should be more than enough for any normal regexp.
v = commands.process(f1, m1, arg, timeout=0.1, pn=self.name(), cn='last')
return v
except commands.ProcessTimeoutError:
return False
predicates.setdefault('regexp', []).append(f) predicates.setdefault('regexp', []).append(f)
elif option == 'nolimit': elif option == 'nolimit':
nolimit = True nolimit = True
@ -370,8 +392,12 @@ class Misc(callbacks.Plugin):
showNick = True showNick = True
for m in iterable: for m in iterable:
for predicate in predicates: for predicate in predicates:
if not predicate(m): try:
break if not predicate(m):
break
except RegexpTimeout:
irc.error(_('The regular expression timed out.'))
return
else: else:
if nolimit: if nolimit:
resp.append(ircmsgs.prettyPrint(m, resp.append(ircmsgs.prettyPrint(m,

View File

@ -42,6 +42,7 @@ import supybot.ircmsgs as ircmsgs
import supybot.plugins as plugins import supybot.plugins as plugins
import supybot.ircutils as ircutils import supybot.ircutils as ircutils
import supybot.callbacks as callbacks import supybot.callbacks as callbacks
from supybot import commands
from supybot.i18n import PluginInternationalization, internationalizeDocstring from supybot.i18n import PluginInternationalization, internationalizeDocstring
_ = PluginInternationalization('Note') _ = PluginInternationalization('Note')
@ -294,7 +295,8 @@ class Note(callbacks.Plugin):
own = to own = to
for (option, arg) in optlist: for (option, arg) in optlist:
if option == 'regexp': if option == 'regexp':
criteria.append(arg.search) criteria.append(lambda x: commands.regexp_wrapper(x, reobj=arg,
timeout=0.1, plugin_name = self.name(), fcn_name='search'))
elif option == 'sent': elif option == 'sent':
own = frm own = frm
if glob: if glob:

View File

@ -2,7 +2,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Supybot-fr\n" "Project-Id-Version: Supybot-fr\n"
"POT-Creation-Date: 2011-02-26 09:49+CET\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n"
"PO-Revision-Date: 2011-08-10 02:27+0200\n" "PO-Revision-Date: 2011-08-10 14:43+0200\n"
"Last-Translator: skizzhg <skizzhg@gmx.com>\n" "Last-Translator: skizzhg <skizzhg@gmx.com>\n"
"Language-Team: Italian <skizzhg@gmx.com>\n" "Language-Team: Italian <skizzhg@gmx.com>\n"
"Language: it\n" "Language: it\n"
@ -10,47 +10,48 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
#: plugin.py:203 plugin.py:284 #: plugin.py:204 plugin.py:285
msgid "%s was last seen in %s %s ago: %s" msgid "%s was last seen in %s %s ago: %s"
msgstr "%s è stato visto per l'ultima volta in %s %s fa: %s" msgstr "%s è stato visto per l'ultima volta in %s %s fa: %s"
#: plugin.py:210 #: plugin.py:211
msgid "%s (%s ago)" msgid "%s (%s ago)"
msgstr "%s (%s fa)" msgstr "%s (%s fa)"
#: plugin.py:212 #: plugin.py:213
msgid "%s could be %L" msgid "%s could be %L"
msgstr "%s potrebbe essere %L" msgstr "%s potrebbe essere %L"
#: plugin.py:212 #: plugin.py:213
msgid "or" msgid "or"
msgstr "oppure" msgstr "oppure"
#: plugin.py:214 #: plugin.py:215
msgid "I haven't seen anyone matching %s." msgid "I haven't seen anyone matching %s."
msgstr "Non ho visto nessuno che corrisponda a %s." msgstr "Non ho visto nessuno che corrisponda a %s."
#: plugin.py:216 plugin.py:288 #: plugin.py:217 plugin.py:289
msgid "I have not seen %s." msgid "I have not seen %s."
msgstr "Non ho visto %s." msgstr "Non ho visto %s."
#: plugin.py:220 #: plugin.py:221
#, docstring #, docstring
msgid "" msgid ""
"[<channel>] <nick>\n" "[<channel>] <nick>\n"
"\n" "\n"
" Returns the last time <nick> was seen and what <nick> was last seen\n" " Returns the last time <nick> was seen and what <nick> was last seen\n"
" saying. <channel> is only necessary if the message isn't sent on the\n" " saying. <channel> is only necessary if the message isn't sent on the\n"
" channel itself.\n" " channel itself. <nick> may contain * as a wildcard.\n"
" " " "
msgstr "" msgstr ""
"[<canale>] <nick>\n" "[<canale>] <nick>\n"
"\n" "\n"
" Riporta l'ultima volta che <nick> è stato visto e cosa stava dicendo.\n" " Riporta l'ultima volta che <nick> è stato visto e cosa stava dicendo.\n"
" <canale> è necessario solo se il messaggio non viene inviato nel canale stesso.\n" " <canale> è necessario solo se il messaggio non viene inviato nel canale\n"
" stesso. <nick> può contenere * come wildcard.\n"
" " " "
#: plugin.py:231 #: plugin.py:232
#, docstring #, docstring
msgid "" msgid ""
"[<channel>] [--user <name>] [<nick>]\n" "[<channel>] [--user <name>] [<nick>]\n"
@ -73,15 +74,15 @@ msgstr ""
" solo se il messaggio non viene inviato nel canale stesso.\n" " solo se il messaggio non viene inviato nel canale stesso.\n"
" " " "
#: plugin.py:261 #: plugin.py:262
msgid "Someone was last seen in %s %s ago: %s" msgid "Someone was last seen in %s %s ago: %s"
msgstr "Qualcuno è stato visto per l'ultima volta in %s %s fa: %s" msgstr "Qualcuno è stato visto per l'ultima volta in %s %s fa: %s"
#: plugin.py:265 #: plugin.py:266
msgid "I have never seen anyone." msgid "I have never seen anyone."
msgstr "Non ho mai visto nessuno." msgstr "Non ho mai visto nessuno."
#: plugin.py:269 #: plugin.py:270
#, docstring #, docstring
msgid "" msgid ""
"[<channel>]\n" "[<channel>]\n"
@ -96,15 +97,15 @@ msgstr ""
" solo se il messaggio non viene inviato nel canale stesso.\n" " solo se il messaggio non viene inviato nel canale stesso.\n"
" " " "
#: plugin.py:292 #: plugin.py:293
#, docstring #, docstring
msgid "" msgid ""
"[<canale>] <name>\n" "[<channel>] <name>\n"
"\n" "\n"
" Returns the last time <name> was seen and what <name> was last seen\n" " Returns the last time <name> was seen and what <name> was last seen\n"
" saying. This looks up <name> in the user seen database, which means\n" " saying. This looks up <name> in the user seen database, which means\n"
" that it could be any nick recognized as user <name> that was seen.\n" " that it could be any nick recognized as user <name> that was seen.\n"
" <canale> is only necessary if the message isn't sent in the channel\n" " <channel> is only necessary if the message isn't sent in the channel\n"
" itself.\n" " itself.\n"
" " " "
msgstr "" msgstr ""
@ -116,7 +117,7 @@ msgstr ""
" <canale> è necessario solo se il messaggio non viene inviato nel canale stesso.\n" " <canale> è necessario solo se il messaggio non viene inviato nel canale stesso.\n"
" " " "
#: plugin.py:305 #: plugin.py:306
#, docstring #, docstring
msgid "" msgid ""
"[<channel>] <nick>\n" "[<channel>] <nick>\n"
@ -129,15 +130,15 @@ msgstr ""
" Riporta i messaggi da quando <nick> ha lasciato il canale l'ultima volta.\n" " Riporta i messaggi da quando <nick> ha lasciato il canale l'ultima volta.\n"
" " " "
#: plugin.py:312 #: plugin.py:313
msgid "You must be in %s to use this command." msgid "You must be in %s to use this command."
msgstr "Per usare questo comando bisogna essere in %s." msgstr "Per usare questo comando bisogna essere in %s."
#: plugin.py:333 #: plugin.py:334
msgid "I couldn't find in my history of %s messages where %r last left the %s" msgid "I couldn't find in my history of %s messages where %r last left the %s"
msgstr "Non trovo nella cronologia dei messaggi di %s dove %r ha lasciato %s l'ultima volta." msgstr "Non trovo nella cronologia dei messaggi di %s dove %r ha lasciato %s l'ultima volta."
#: plugin.py:342 #: plugin.py:343
msgid "Either %s didn't leave, or no messages were sent while %s was gone." msgid "Either %s didn't leave, or no messages were sent while %s was gone."
msgstr "%s non è uscito o non ha inviato alcun messaggio quando se n'é andato." msgstr "%s non è uscito o non ha inviato alcun messaggio quando se n'é andato."

View File

@ -53,4 +53,11 @@ conf.registerGlobalValue(String.levenshtein, 'max',
this variable, to limit the size of arguments passed to the levenshtein this variable, to limit the size of arguments passed to the levenshtein
command."""))) command.""")))
conf.registerGroup(String, 're')
conf.registerGlobalValue(String.re, 'timeout',
registry.PositiveFloat(0.1, """Determines the maximum time, in seconds, that
a regular expression is given to execute before being terminated. Since
there is a possibility that user input for the re command can cause it to
eat up large amounts of ram or cpu time, it's a good idea to keep this
low. Most normal regexps should not take very long at all."""))
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:

View File

@ -146,10 +146,14 @@ class String(callbacks.Plugin):
s = _('You probably don\'t want to match the empty string.') s = _('You probably don\'t want to match the empty string.')
irc.error(s) irc.error(s)
else: else:
irc.reply(f(text)) t = self.registryValue('re.timeout')
re = wrap(re, [('checkCapability', 'trusted'), try:
first('regexpMatcher', 'regexpReplacer'), v = commands.process(f, text, timeout=t, pn=self.name(), cn='re')
'text']) irc.reply(v)
except commands.ProcessTimeoutError, e:
irc.error("ProcessTimeoutError: %s" % (e,))
re = thread(wrap(re, [first('regexpMatcher', 'regexpReplacer'),
'text']))
@internationalizeDocstring @internationalizeDocstring
def xor(self, irc, msg, args, password, text): def xor(self, irc, msg, args, password, text):

View File

@ -171,8 +171,6 @@ class Time(callbacks.Plugin):
except ImportError: except ImportError:
irc.error(_('Python-tz is required by the command, but is not ' irc.error(_('Python-tz is required by the command, but is not '
'installed on this computer.')) 'installed on this computer.'))
if len(timezone.split('/')) != 2:
irc.error(_('A timezone must be in the format region/city.'))
try: try:
timezone = pytz.timezone(timezone) timezone = pytz.timezone(timezone)
except pytz.UnknownTimeZoneError: except pytz.UnknownTimeZoneError:

View File

@ -2,7 +2,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Supybot-fr\n" "Project-Id-Version: Supybot-fr\n"
"POT-Creation-Date: 2011-02-26 09:49+CET\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n"
"PO-Revision-Date: 2011-08-10 02:33+0200\n" "PO-Revision-Date: 2011-08-10 14:53+0200\n"
"Last-Translator: skizzhg <skizzhg@gmx.com>\n" "Last-Translator: skizzhg <skizzhg@gmx.com>\n"
"Language-Team: Italian <skizzhg@gmx.com>\n" "Language-Team: Italian <skizzhg@gmx.com>\n"
"Language: it\n" "Language: it\n"
@ -10,6 +10,14 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
#: config.py:50
msgid ""
"Determines whether users can read the\n"
" todo-list of another user."
msgstr ""
"Determina se un utente possa leggere la lista delle cose da fare di un altro utente."
#: plugin.py:135 #: plugin.py:135
#, docstring #, docstring
msgid "" msgid ""
@ -27,43 +35,47 @@ msgstr ""
" ha aggiunto alla sua lista delle cose da fare.\n" " ha aggiunto alla sua lista delle cose da fare.\n"
" " " "
#: plugin.py:150 #: plugin.py:146
msgid "You are not allowed to see other users todo-list."
msgstr "Non hai l'autorizzazione pe rleggere la lista delle cose da fare degli altri utenti."
#: plugin.py:153
msgid "#%i: %s" msgid "#%i: %s"
msgstr "#%i: %s" msgstr "#%i: %s"
#: plugin.py:155 #: plugin.py:158
msgid "%s for %s: %L" msgid "%s for %s: %L"
msgstr "%s per %s: %L" msgstr "%s per %s: %L"
#: plugin.py:159 #: plugin.py:162
msgid "That user has no tasks in their todo list." msgid "That user has no tasks in their todo list."
msgstr "Questo utente non ha compiti nella sua lista delle cose da fare." msgstr "Questo utente non ha compiti nella sua lista delle cose da fare."
#: plugin.py:161 #: plugin.py:164
msgid "You have no tasks in your todo list." msgid "You have no tasks in your todo list."
msgstr "Non hai compiti nella tua lista delle cose da fare." msgstr "Non hai compiti nella tua lista delle cose da fare."
#: plugin.py:168 #: plugin.py:171
msgid "Active" msgid "Active"
msgstr "Attivo" msgstr "Attivo"
#: plugin.py:170 #: plugin.py:173
msgid "Inactive" msgid "Inactive"
msgstr "Inattivo" msgstr "Inattivo"
#: plugin.py:172 #: plugin.py:175
msgid ", priority: %i" msgid ", priority: %i"
msgstr ", priorità: %i" msgstr ", priorità: %i"
#: plugin.py:175 #: plugin.py:178
msgid "%s todo for %s: %s (Added at %s)" msgid "%s todo for %s: %s (Added at %s)"
msgstr "%s compito per %s: %s (Aggiunto il %s)" msgstr "%s compito per %s: %s (Aggiunto il %s)"
#: plugin.py:179 plugin.py:260 plugin.py:274 #: plugin.py:182 plugin.py:263 plugin.py:277
msgid "task id" msgid "task id"
msgstr "id compito" msgstr "id compito"
#: plugin.py:184 #: plugin.py:187
#, docstring #, docstring
msgid "" msgid ""
"[--priority=<num>] <text>\n" "[--priority=<num>] <text>\n"
@ -80,11 +92,11 @@ msgstr ""
" Ogni numero intero è valido.\n" " Ogni numero intero è valido.\n"
" " " "
#: plugin.py:195 #: plugin.py:198
msgid "(Todo #%i added)" msgid "(Todo #%i added)"
msgstr "(Compito #%i aggiunto)" msgstr "(Compito #%i aggiunto)"
#: plugin.py:201 #: plugin.py:204
#, docstring #, docstring
msgid "" msgid ""
"<task id> [<task id> ...]\n" "<task id> [<task id> ...]\n"
@ -97,15 +109,15 @@ msgstr ""
" Rimuove <id compito> dalla lista personale delle cose da fare.\n" " Rimuove <id compito> dalla lista personale delle cose da fare.\n"
" " " "
#: plugin.py:212 #: plugin.py:215
msgid "Task %i could not be removed either because that id doesn't exist or it has been removed already." msgid "Task %i could not be removed either because that id doesn't exist or it has been removed already."
msgstr "Il compito %i non può essere rimosso in quanto l'id non esiste o è già stato rimosso." msgstr "Il compito %i non può essere rimosso in quanto l'id non esiste o è già stato rimosso."
#: plugin.py:216 #: plugin.py:219
msgid "No tasks were removed because the following tasks could not be removed: %L." msgid "No tasks were removed because the following tasks could not be removed: %L."
msgstr "Non è stato rimosso nessun compito perché i seguenti non possono essere rimossi: %L." msgstr "Non è stato rimosso nessun compito perché i seguenti non possono essere rimossi: %L."
#: plugin.py:226 #: plugin.py:229
#, docstring #, docstring
msgid "" msgid ""
"[--{regexp} <value>] [<glob> <glob> ...]\n" "[--{regexp} <value>] [<glob> <glob> ...]\n"
@ -121,11 +133,11 @@ msgstr ""
" Se --regexp è fornita, il suo valore associato è usato come regexp e confrontato con i compiti.\n" " Se --regexp è fornita, il suo valore associato è usato come regexp e confrontato con i compiti.\n"
" " " "
#: plugin.py:246 #: plugin.py:249
msgid "No tasks matched that query." msgid "No tasks matched that query."
msgstr "Nessun compito corrisponde alla richiesta." msgstr "Nessun compito corrisponde alla richiesta."
#: plugin.py:252 #: plugin.py:255
#, docstring #, docstring
msgid "" msgid ""
"<id> <priority>\n" "<id> <priority>\n"
@ -138,7 +150,7 @@ msgstr ""
" Imposta la priorità del compito con l'id fornito al valore specificato.\n" " Imposta la priorità del compito con l'id fornito al valore specificato.\n"
" " " "
#: plugin.py:266 #: plugin.py:269
#, docstring #, docstring
msgid "" msgid ""
"<task id> <regexp>\n" "<task id> <regexp>\n"

View File

@ -41,6 +41,7 @@ from supybot.commands import *
import supybot.plugins as plugins import supybot.plugins as plugins
import supybot.ircutils as ircutils import supybot.ircutils as ircutils
import supybot.callbacks as callbacks import supybot.callbacks as callbacks
from supybot import commands
from supybot.i18n import PluginInternationalization, internationalizeDocstring from supybot.i18n import PluginInternationalization, internationalizeDocstring
_ = PluginInternationalization('Todo') _ = PluginInternationalization('Todo')
@ -237,6 +238,8 @@ class Todo(callbacks.Plugin):
criteria = [] criteria = []
for (option, arg) in optlist: for (option, arg) in optlist:
if option == 'regexp': if option == 'regexp':
criteria.append(lambda x: commands.regexp_wrapper(x, reobj=arg,
timeout=0.1, plugin_name = self.name(), fcn_name='search'))
criteria.append(arg.search) criteria.append(arg.search)
for glob in globs: for glob in globs:
glob = utils.python.glob2re(glob) glob = utils.python.glob2re(glob)

View File

@ -50,6 +50,7 @@ import supybot.world as world
from supybot.commands import * from supybot.commands import *
import supybot.ircutils as ircutils import supybot.ircutils as ircutils
import supybot.callbacks as callbacks import supybot.callbacks as callbacks
from supybot import commands
## i think we don't need any of this with sqlite3 ## i think we don't need any of this with sqlite3
#try: #try:
@ -431,7 +432,9 @@ class ChannelIdDatabasePlugin(callbacks.Plugin):
if opt == 'by': if opt == 'by':
predicates.append(lambda r, arg=arg: r.by == arg.id) predicates.append(lambda r, arg=arg: r.by == arg.id)
elif opt == 'regexp': elif opt == 'regexp':
predicates.append(lambda r, arg=arg: arg.search(r.text)) predicates.append(lambda x: commands.regexp_wrapper(x.text, reobj=arg,
timeout=0.1, plugin_name = self.name(), fcn_name='search'))
#predicates.append(lambda r, arg=arg: arg.search(r.text))
if glob: if glob:
def globP(r, glob=glob.lower()): def globP(r, glob=glob.lower()):
return fnmatch.fnmatch(r.text.lower(), glob) return fnmatch.fnmatch(r.text.lower(), glob)

View File

@ -69,6 +69,61 @@ def thread(f):
f(self, irc, msg, args, *L, **kwargs) f(self, irc, msg, args, *L, **kwargs)
return utils.python.changeFunctionName(newf, f.func_name, f.__doc__) return utils.python.changeFunctionName(newf, f.func_name, f.__doc__)
class ProcessTimeoutError(Exception):
"""Gets raised when a process is killed due to timeout."""
pass
def process(f, *args, **kwargs):
"""Runs a function <f> in a subprocess.
Several extra keyword arguments can be supplied.
<pn>, the pluginname, and <cn>, the command name, are strings used to
create the process name, for identification purposes.
<timeout>, if supplied, limits the length of execution of target
function to <timeout> seconds."""
timeout = kwargs.pop('timeout', None)
q = multiprocessing.Queue()
def newf(f, q, *args, **kwargs):
try:
r = f(*args, **kwargs)
q.put(r)
except Exception as e:
q.put(e)
targetArgs = (f, q,) + args
p = callbacks.CommandProcess(target=newf,
args=targetArgs, kwargs=kwargs)
p.start()
p.join(timeout)
if p.is_alive():
p.terminate()
raise ProcessTimeoutError, "%s aborted due to timeout." % (p.name,)
try:
v = q.get(block=False)
except Queue.Empty:
v = "Nothing returned."
if isinstance(v, Exception):
v = "Error: " + str(v)
return v
def regexp_wrapper(s, reobj, timeout, plugin_name, fcn_name):
'''A convenient wrapper to stuff regexp search queries through a subprocess.
This is used because specially-crafted regexps can use exponential time
and hang the bot.'''
def re_bool(s, reobj):
"""Since we can't enqueue match objects into the multiprocessing queue,
we'll just wrap the function to return bools."""
if reobj.search(s) is not None:
return True
else:
return False
try:
v = process(re_bool, s, reobj, timeout=timeout, pn=plugin_name, cn=fcn_name)
return v
except ProcessTimeoutError:
return False
class UrlSnarfThread(world.SupyThread): class UrlSnarfThread(world.SupyThread):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
assert 'url' in kwargs assert 'url' in kwargs

View File

@ -206,7 +206,7 @@ class Group(object):
def __getattr__(self, attr): def __getattr__(self, attr):
if attr in self._children: if attr in self._children:
return self._children[attr] return self._children[attr]
elif self._supplyDefault and attr.startswith('#'): elif self._supplyDefault:
return self.__makeChild(attr, str(self)) return self.__makeChild(attr, str(self))
else: else:
self.__nonExistentEntry(attr) self.__nonExistentEntry(attr)

View File

@ -1,3 +1,3 @@
"""stick the various versioning attributes in here, so we only have to change """stick the various versioning attributes in here, so we only have to change
them once.""" them once."""
version = '0.83.4.1+limnoria (2011-08-10T12:00:42+0200)' version = '0.83.4.1+limnoria (2011-08-13T01:59:18+0200)'