diff --git a/src/callbacks.py b/src/callbacks.py index 06371b352..a021f13ec 100644 --- a/src/callbacks.py +++ b/src/callbacks.py @@ -925,9 +925,8 @@ class NestedCommandsIrcProxy(ReplyIrcProxy): log.warning('Truncating to %s bytes from %s bytes.', maximumLength, len(s)) s = s[:maximumLength] - s_too_long = len(s.encode()) < allowedLength \ - if minisix.PY3 else len(s) < allowedLength - if s_too_long or \ + s_size = len(s.encode()) if minisix.PY3 else len(s) + if s_size > allowedLength or \ not conf.get(conf.supybot.reply.mores, target): # In case we're truncating, we add 20 to allowedLength, # because our allowedLength is shortened for the diff --git a/src/ircutils.py b/src/ircutils.py index 8299eb93f..ccda3392d 100644 --- a/src/ircutils.py +++ b/src/ircutils.py @@ -598,10 +598,7 @@ class FormatParser(object): def wrap(s, length, break_on_hyphens = False, break_long_words = False): processed = [] - wrapper = textwrap.TextWrapper(width=length) - wrapper.break_long_words = break_long_words - wrapper.break_on_hyphens = break_on_hyphens - chunks = wrapper.wrap(s) + chunks = utils.str.byteTextWrap(s, length) context = None for chunk in chunks: if context is not None: diff --git a/src/utils/str.py b/src/utils/str.py index a110c1b3b..b2670cb18 100644 --- a/src/utils/str.py +++ b/src/utils/str.py @@ -34,6 +34,7 @@ Simple utility functions related to strings. """ import re +import sys import time import string import textwrap @@ -305,6 +306,25 @@ def perlVariableSubstitute(vars, text): return '$' + unbraced return _perlVarSubstituteRe.sub(replacer, text) +def byteTextWrap(text, size): + """Similar to textwrap.wrap(), but considers the size of strings (in bytes) + instead of their length (in characters).""" + words = textwrap.TextWrapper()._split_chunks(text) + words.reverse() # use it as a stack + if sys.version_info[0] >= 3: + words = [w.encode() for w in words] + lines = [b''] + while words: + word = words.pop(-1) + if len(lines[-1]) + len(word) <= size: + lines[-1] += word + else: + lines.append(word) + if sys.version_info[0] >= 3: + return [l.decode() for l in lines] + else: + return lines + def commaAndify(seq, comma=',', And=None): """Given a a sequence, returns an English clause for that sequence. diff --git a/test/test_ircutils.py b/test/test_ircutils.py index 41f6fe4dd..f7e573ab7 100644 --- a/test/test_ircutils.py +++ b/test/test_ircutils.py @@ -168,6 +168,34 @@ class FunctionsTestCase(SupyTestCase): s = ircutils.mircColor('[', 'blue') + ircutils.bold('09:21') self.assertEqual(ircutils.stripFormatting(s), '[09:21') + def testWrap(self): + if sys.version_info[0] < 3: + pred = len + else: + pred = lambda s:len(s.encode()) + + s = ('foo bar baz qux ' * 100)[0:-1] + + r = ircutils.wrap(s, 10) + self.assertTrue(max(map(pred, r)) <= 10) + self.assertEqual(''.join(r), s) + + r = ircutils.wrap(s, 100) + self.assertTrue(max(map(pred, r)) <= 100) + self.assertEqual(''.join(r), s) + + s = (''.join([chr(0x1f527), chr(0x1f527), chr(0x1f527), ' ']) * 100)\ + [0:-1] + + r = ircutils.wrap(s, 20) + self.assertTrue(max(map(pred, r)) <= 20, (max(map(pred, r)), repr(r))) + self.assertEqual(''.join(r), s) + + r = ircutils.wrap(s, 100) + self.assertTrue(max(map(pred, r)) <= 100) + self.assertEqual(''.join(r), s) + + def testSafeArgument(self): s = 'I have been running for 9 seconds' bolds = ircutils.bold(s)