diff --git a/plugins/Todo.py b/plugins/Todo.py index eccdee2a5..784cc24dc 100644 --- a/plugins/Todo.py +++ b/plugins/Todo.py @@ -1,7 +1,7 @@ #!/usr/bin/python ### -# Copyright (c) 2002, Jeremiah Fincher +# Copyright (c) 2003, Daniel DiPaolo # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -30,18 +30,22 @@ ### """ -Add the module docstring here. This will be used by the setup.py script. +The Todo module allows registered users to keep their own personal list of +tasks to do, with an optional priority for each. """ import plugins +import glob import time import getopt +import string import os.path import sqlite import conf +import debug import ircdb import utils import privmsgs @@ -124,16 +128,14 @@ class Todo(callbacks.Privmsg): irc.reply(msg, s) else: cursor = self.db.cursor() - cursor.execute("""SELECT id FROM todo WHERE userid = %s""", id) + cursor.execute("""SELECT id, task FROM todo + WHERE userid = %s""", id) if cursor.rowcount == 0: irc.reply(msg, 'You have no tasks in your todo list.') - else: - ids = [] - for (id,) in cursor.fetchall(): - ids.append(str(id)) - s = 'Task ids: %s' % ', '.join(ids) - irc.reply(msg, s) return + s = ['#%s: %s' % (item[0], utils.ellipsisify(item[1], 50)) \ + for item in cursor.fetchall()] + irc.reply(msg, utils.commaAndify(s)) def addtodo(self, irc, msg, args): """[--priority=] @@ -156,9 +158,10 @@ class Todo(callbacks.Privmsg): except ValueError, e: irc.error(msg, 'Invalid priority: %s' % e) return + text = privmsgs.getArgs(rest, needed=1) cursor = self.db.cursor() cursor.execute("""INSERT INTO todo VALUES (NULL, %s, %s, %s, %s)""", - priority, int(time.time()), id, ' '.join(rest)) + priority, int(time.time()), id, text) self.db.commit() irc.reply(msg, conf.replySuccess) @@ -184,6 +187,55 @@ class Todo(callbacks.Privmsg): self.db.commit() irc.reply(msg, conf.replySuccess) + _sqlTrans = string.maketrans('*?', '%_') + def searchtodo(self, irc, msg, args): + """[--{regexp,exact}=] [] + + Searches the keyspace for tasks matching . If --regexp is given, + it associated value is taken as a regexp and matched against the tasks; + if --exact is given, its associated value is taken as an exact string + to match against the task. + """ + try: + id = ircdb.users.getUserId(msg.prefix) + except KeyError: + irc.error(msg, conf.replyNotRegistered) + return + + (optlist, rest) = getopt.getopt(args, '', ['regexp=', 'exact=']) + if not optlist and not rest: + raise callbacks.ArgumentError + criteria = ['userid = %s' % id] + formats = [] + predicateName = 'p' + for (option, arg) in optlist: + if option == '--exact': + criteria.append('task LIKE %s') + formats.append('%' + arg + '%') + elif option == '--regexp': + criteria.append('%s(task)' % predicateName) + try: + r = utils.perlReToPythonRe(arg) + except ValueError, e: + irc.error(msg, 'Invalid regexp: %s' % e) + return + def p(s, r=r): + return int(bool(r.search(s))) + self.db.create_function(predicateName, 1, p) + predicateName += 'p' + for glob in rest: + criteria.append('task LIKE %s') + formats.append(glob.translate(self._sqlTrans)) + cursor = self.db.cursor() + sql = """SELECT id, task FROM todo WHERE %s""" % ' AND '.join(criteria) + cursor.execute(sql, formats) + if cursor.rowcount == 0: + irc.reply(msg, 'No tasks matched that query.') + else: + tasks = ['#%s: %s' % (item[0], utils.ellipsisify(item[1], 50)) \ + for item in cursor.fetchall()] + irc.reply(msg, utils.commaAndify(tasks)) + Class = Todo # vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: diff --git a/test/test_Todo.py b/test/test_Todo.py new file mode 100644 index 000000000..3743ab43a --- /dev/null +++ b/test/test_Todo.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python + +### +# Copyright (c) 2003, Daniel DiPaolo +# 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 test import * + +try: + import sqlite +except ImportError: + sqlite = None + +if sqlite is not None: + class TodoTestCase(PluginTestCase, PluginDocumentation): + plugins = ('Todo', 'UserCommands') + def setUp(self): + PluginTestCase.setUp(self) + # Create a valid user to use + self.prefix = 'foo!bar@baz' + self.assertNotError('register tester moo') + + def testTodo(self): + # Should not error, but no tasks yet. + self.assertNotError('todo') + self.assertRegexp('todo', 'You have no tasks in your todo list.') + # Add a task + self.assertNotError('addtodo wash my car') + self.assertRegexp('todo', '#1: wash my car') + # Check that task + self.assertNotError('todo 1') + + def testAddtodo(self): + self.assertNotError('addtodo code a new plugin') + self.assertNotError('addtodo --priority=1000 fix all bugs') + + def testRemovetodo(self): + self.assertNotError('addtodo do something else') + self.assertNotError('removetodo 1') + + def testSearchtodo(self): + self.assertNotError('addtodo task number one') + self.assertRegexp('searchtodo task*', '#1: task number one') + self.assertNotError('addtodo task number two is much longer than' + ' task number one') + self.assertRegexp('searchtodo task*', '#1: task number one and #2:' + ' task number two is much longer than task ' + 'number...') + self.assertRegexp('searchtodo --exact "task number one"', + '#1: task number one') + self.assertError('searchtodo --regexp s/bustedregex') + self.assertRegexp('searchtodo --regexp m/task/', '#1: task number' + ' one and #2: task number two is much longer ' + 'than task number...') + +# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: +