Commits

Jason R. Coombs committed 28de118 Draft Merge

Merge

Comments (0)

Files changed (7)

-d36dd639862031e1c28d067770beff6f18835952 1001
-8275b5cf7daec0985c372221b471e838ab8e8348 1003
-cfea277d6bde522dd631296584c43cd64fd5e257 1004
-e3024ad3375d0792aadcd2bf01df877a2da7f1f6 1004.2
-e4b3a8d6bff2133058edb997dbe591f98a06223f 1004.4
-f080b3dc8230fe1af02ba117783f31c49d139418 1005b1
-5b10b02587ac2ed0c4bc629c083153caea607529 1005b2
-5b10b02587ac2ed0c4bc629c083153caea607529 1005b2
-87567c903bbe93a7117e4f9948cfc8a8fdfc134a 1005b2
-fb3775995b8d175baec8c6bf7912cfa7308efa21 1005
-9f08fe4b3805df9dfac33718401ef080bb4213f6 1100b1
-9ed794744a2642dca05f5c7e44d4051e59530fee 1100b2
-2957e40d337107ccb7d13b5c81c150e8babecaf2 1100b3
-3494ebe55417a9bda2a4d600a9adbd4664eb6957 1100b4
-e4bd2f8facbbf8f0384e7a86fcb473ce34973d49 1100b5
-bc8ee0fa2a3e38df6373ae35101b06ae0054396d 1100b6
-6cce26886e4f9e3a139bade2e1d50796e77bc24c 1100b7
-5da5589516143c4bf46ae8a166d9604b1938473a 1100.0
-f074f6348a56ebab32845602618dd29bb4614b27 1100.1
-5ac09b365e5319a546497436c80662775d8e6426 1100.2
-c0415476c10dcf7293fc32fc04d611ba14d22e4f 1100.3
-d852f80a121ca4186c7a4d21ef536caae3400481 1100.4
-e61ab71dea60082152ca758e381bb1b03f8d8c27 1100.5
-61ae489db5ffe3d49a2b95223cb135e05aa5ee4c 1100.5.1
-db45a7ffcd5f42afc300f8dab3b210e985cd2879 1100.5.2
-71352a40a80548ecbbc8f336d291260df5960144 1100.5.3
-bfcc64f56b501a0e3bd03c9ca1fdc454c367e9f8 1100.5.4
-c17c5de790174602a1e035cf54fc5a5ccc844aa7 1100.5.5
-cc7d3be77040139014cb316d7ce3bf3b8b725c5d 1100.5.6
-460c01d283d2ac2aeeb1abfdca80aa6d2e7362bf 1100.5.7
-fc863776a9b5bcf5dbb25e202738862557950f39 1100.5.8
-3156d1de757148eb8e68f6d5bd470840e3ba3f06 1100.6
-d40b83a262a6d1e65784070edf78256271a3c90d 1100.7
-fc04b229082535080cb1bd83551a2c20378703a8 1100.8
-47b6ddcbc1b36818d8f33f2c5a29a885b0b2da2c 1100.8.1
-6a1e43a9ac22b5d384b0dc25f955a6cc8bd41627 1100.8.2
-4c21d5bce3129dd2586e97afbcfa9d586dc8cc21 1100.8.3
-7bcc8976329db2cda6772faca7ad4def5f2de029 1100.8.4
-414b120b1bb25136b6409b0a1ef0d751a1dfd2df 1100.8.5
-eb2606e5d2ac42273e17be15b9fdfe9c17b8dc3b 1101.0
+d36dd639862031e1c28d067770beff6f18835952 1001
+8275b5cf7daec0985c372221b471e838ab8e8348 1003
+cfea277d6bde522dd631296584c43cd64fd5e257 1004
+e3024ad3375d0792aadcd2bf01df877a2da7f1f6 1004.2
+e4b3a8d6bff2133058edb997dbe591f98a06223f 1004.4
+f080b3dc8230fe1af02ba117783f31c49d139418 1005b1
+5b10b02587ac2ed0c4bc629c083153caea607529 1005b2
+5b10b02587ac2ed0c4bc629c083153caea607529 1005b2
+87567c903bbe93a7117e4f9948cfc8a8fdfc134a 1005b2
+fb3775995b8d175baec8c6bf7912cfa7308efa21 1005
+9f08fe4b3805df9dfac33718401ef080bb4213f6 1100b1
+9ed794744a2642dca05f5c7e44d4051e59530fee 1100b2
+2957e40d337107ccb7d13b5c81c150e8babecaf2 1100b3
+3494ebe55417a9bda2a4d600a9adbd4664eb6957 1100b4
+e4bd2f8facbbf8f0384e7a86fcb473ce34973d49 1100b5
+bc8ee0fa2a3e38df6373ae35101b06ae0054396d 1100b6
+6cce26886e4f9e3a139bade2e1d50796e77bc24c 1100b7
+5da5589516143c4bf46ae8a166d9604b1938473a 1100.0
+f074f6348a56ebab32845602618dd29bb4614b27 1100.1
+5ac09b365e5319a546497436c80662775d8e6426 1100.2
+c0415476c10dcf7293fc32fc04d611ba14d22e4f 1100.3
+d852f80a121ca4186c7a4d21ef536caae3400481 1100.4
+e61ab71dea60082152ca758e381bb1b03f8d8c27 1100.5
+61ae489db5ffe3d49a2b95223cb135e05aa5ee4c 1100.5.1
+db45a7ffcd5f42afc300f8dab3b210e985cd2879 1100.5.2
+71352a40a80548ecbbc8f336d291260df5960144 1100.5.3
+bfcc64f56b501a0e3bd03c9ca1fdc454c367e9f8 1100.5.4
+c17c5de790174602a1e035cf54fc5a5ccc844aa7 1100.5.5
+cc7d3be77040139014cb316d7ce3bf3b8b725c5d 1100.5.6
+460c01d283d2ac2aeeb1abfdca80aa6d2e7362bf 1100.5.7
+fc863776a9b5bcf5dbb25e202738862557950f39 1100.5.8
+3156d1de757148eb8e68f6d5bd470840e3ba3f06 1100.6
+d40b83a262a6d1e65784070edf78256271a3c90d 1100.7
+fc04b229082535080cb1bd83551a2c20378703a8 1100.8
+47b6ddcbc1b36818d8f33f2c5a29a885b0b2da2c 1100.8.1
+6a1e43a9ac22b5d384b0dc25f955a6cc8bd41627 1100.8.2
+4c21d5bce3129dd2586e97afbcfa9d586dc8cc21 1100.8.3
+7bcc8976329db2cda6772faca7ad4def5f2de029 1100.8.4
+414b120b1bb25136b6409b0a1ef0d751a1dfd2df 1100.8.5
+eb2606e5d2ac42273e17be15b9fdfe9c17b8dc3b 1101.0
 31fb0d29f2f8e17075afa8bb02f32aabdd5e19ab 1101.0.1
 b4b890c2abed3e0b01149c3e4fbe9139536b6a80 1101.1
 491fda4b09f28f735fd16848c1daf2a57af8e6ea 1101.1.1

pmxbot/botbase.py

 # vim:ts=4:sw=4:noexpandtab
 
+from __future__ import absolute_import
+
 import sys
 import datetime
 import os
 import traceback
 import time
 import random
-import StringIO
 import collections
 import textwrap
+import functools
 
 import irc.bot
 
+import pmxbot.itertools
 from . import karma
 from . import quotes
 from .logging import init_logger
 		c.privmsg(channel, "You summoned me, master %s?" % nick)
 
 	def _handle_output(self, channel, output):
-		if not output:
-			return
-
-		if isinstance(output, basestring):
-			# turn the string into an iterable of lines
-			output = StringIO.StringIO(output)
-
 		for secret, item in NoLog.secret_items(output):
 			self.out(channel, item, not secret)
 
 		"""
 		Wrapper to run scheduled type tasks cleanly.
 		"""
-		try:
-			self._handle_output(channel, func(c, None, *args))
-		except:
+		def on_error(exception):
 			print datetime.datetime.now(), "Error in background runner for ", func
 			traceback.print_exc()
+		func = functools.partial(func, c, None, *args)
+		self._handle_output(channel, pmxbot.itertools.trap_exceptions(
+			pmxbot.itertools.generate_results(func),
+			on_error))
+
+	def _handle_exception(self, exception, **kwargs):
+		explitives = ['Yikes!', 'Zoiks!', 'Ouch!']
+		explitive = random.choice(explitives)
+		res = ["{explitive} An error occurred: {exception}"
+			.format(**vars())]
+		res.append('!{name} {doc}'.format(**vars()))
+		print datetime.datetime.now(), ("Error with command {type}"
+			.format(**vars()))
+		traceback.print_exc()
+		return res
 
 	def handle_action(self, c, e, channel, nick, msg):
 		"""Core message parser and dispatcher"""
 		lc_msg = msg.lower()
-		lc_cmd = msg.split(' ', 1)[0]
-		res = None
+		cmd, _, cmd_args = msg.partition(' ')
+
+		messages = ()
 		for typ, name, f, doc, channels, exclude, rate, priority in _handler_registry:
-			if typ in ('command', 'alias') and lc_cmd == '!%s' % name:
-				# grab everything after the command
-				msg = msg.partition(' ')[2].strip()
-				try:
-					res = f(c, e, channel, nick, msg)
-				except Exception as exc:
-					explitives = ['Yikes!', 'Zoiks!', 'Ouch!']
-					explitive = random.choice(explitives)
-					res = ["{explitive} An error occurred: {exc}".format(**vars())]
-					res.append('!{name} {doc}'.format(**vars()))
-					print datetime.datetime.now(), "Error with command %s" % name
-					traceback.print_exc()
+			exception_handler = functools.partial(
+				self._handle_exception,
+				type = typ,
+				name = name,
+				doc = doc,
+				)
+			if typ in ('command', 'alias') and cmd.lower() == '!%s' % name:
+				f = functools.partial(f, c, e, channel, nick, cmd_args)
+				messages = pmxbot.itertools.trap_exceptions(
+					pmxbot.itertools.generate_results(f),
+					exception_handler
+				)
 				break
 			elif typ in ('contains', '#') and name in lc_msg:
+				f = functools.partial(f, c, e, channel, nick, msg)
 				if (not channels and not exclude) \
 				or channel in channels \
 				or (exclude and channel not in exclude) \
 				or (channels == "unlogged" and channel in self._nolog) \
 				or (exclude == "logged" and channel in self._nolog) \
 				or (exclude == "unlogged" and channel in self._channels and channel not in self._nolog):
-					if random.random() <= rate:
-						try:
-							res = f(c, e, channel, nick, msg)
-						except Exception, e:
-							print datetime.datetime.now(), "Error with contains  %s" % name
-							traceback.print_exc()
-						break
-		self._handle_output(channel, res)
+					if random.random() > rate:
+						continue
+					messages = pmxbot.itertools.trap_exceptions(
+						pmxbot.itertools.generate_results(f),
+						exception_handler
+					)
+					break
+		self._handle_output(channel, messages)
 
 
 class SilentCommandBot(LoggingCommandBot):

pmxbot/itertools.py

+from __future__ import unicode_literals
+
+import io
+
+def always_iterable(item):
+	r"""
+	Given an item from a pmxbot handler, always return an iterable.
+
+	If the item is None, return an empty iterable.
+	>>> list(always_iterable(None))
+	[]
+
+	If the item is a string, return an iterable of the lines in the string.
+	>>> list(always_iterable('foo'))
+	[u'foo']
+	>>> list(always_iterable('foo\nbar'))
+	[u'foo\n', u'bar']
+
+	>>> list(always_iterable([1,2,3]))
+	[1, 2, 3]
+	>>> always_iterable(xrange(10))
+	xrange(10)
+
+	And any other non-iterable objects are returned as single-tuples of that
+	item.
+	>>> list(always_iterable(object()))  # doctest: +ELLIPSIS
+	[<object object at ...>]
+	"""
+	if item is None:
+		item = ()
+
+	if isinstance(item, basestring):
+		item = io.StringIO(unicode(item))
+
+	if not hasattr(item, '__iter__'):
+		item = item,
+
+	return item
+
+def generate_results(function):
+	"""
+	Take a function, which may return an iterator or a static result
+	and convert it to a late-dispatched generator.
+	"""
+	for item in always_iterable(function()):
+		yield item
+
+def trap_exceptions(results, handler, exceptions=Exception):
+	"""
+	Iterate through the results, but if an exception occurs, stop
+	processing the results and instead replace
+	the results with the output from the exception handler.
+	"""
+	try:
+		for result in results:
+			yield result
+	except exceptions as exc:
+		for result in always_iterable(handler(exc)):
+			yield result
 # vim:ts=4:sw=4:noexpandtab
 
+from __future__ import print_function
+
 import itertools
 import re
 import importlib

pmxbot/logging.py

+
+from __future__ import absolute_import
+
 import re
 import random
 import datetime

pmxbot/saysomething.py

 # vim:ts=4:sw=4:noexpandtab
+
+from __future__ import absolute_import
+
 import random
 from itertools import chain
 

pmxbot/storage.py

-import os
+from __future__ import absolute_import
+
 import itertools
 import urlparse
 import importlib