Hello community,

here is the log from the commit of package python-limnoria for openSUSE:Factory 
checked in at 2018-05-01 23:27:57
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-limnoria (Old)
 and      /work/SRC/openSUSE:Factory/.python-limnoria.new (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-limnoria"

Tue May  1 23:27:57 2018 rev:2 rq:602624 version:2018.04.21

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-limnoria/python-limnoria.changes  
2018-04-20 17:31:17.110589531 +0200
+++ /work/SRC/openSUSE:Factory/.python-limnoria.new/python-limnoria.changes     
2018-05-01 23:27:59.690998459 +0200
@@ -1,0 +2,7 @@
+Mon Apr 30 18:58:58 UTC 2018 - [email protected]
+
+- Update to version 2018-04-21:
+  * Do not break UTF-8 characters in long words. Closes
+    gh#ProgVal/Limnoria#1333.
+
+-------------------------------------------------------------------

Old:
----
  master-2018-01-28.tar.gz

New:
----
  master-2018-04-21.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-limnoria.spec ++++++
--- /var/tmp/diff_new_pack.2GQ0fA/_old  2018-05-01 23:28:00.478969808 +0200
+++ /var/tmp/diff_new_pack.2GQ0fA/_new  2018-05-01 23:28:00.482969663 +0200
@@ -18,9 +18,9 @@
 
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
 %define appname limnoria
-%define srcver 2018-01-28
+%define srcver 2018-04-21
 Name:           python-limnoria
-Version:        2018.01.28
+Version:        2018.04.21
 Release:        0
 Summary:        A modified version of Supybot (an IRC bot and framework)
 License:        BSD-3-Clause
@@ -40,13 +40,13 @@
 BuildRequires:  %{python_module setuptools}
 BuildRequires:  fdupes
 BuildRequires:  python-rpm-macros
-Requires:       python-SQLAlchemy
 Requires:       python-PySocks
+Requires:       python-SQLAlchemy
 Requires:       python-chardet
 Requires:       python-ecdsa
 Requires:       python-feedparser
-Requires:       python-python-gnupg
 Requires:       python-python-dateutil
+Requires:       python-python-gnupg
 Requires:       python-pytz
 Provides:       Supybot = %{version}
 Obsoletes:      Supybot < 1.0

++++++ master-2018-01-28.tar.gz -> master-2018-04-21.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Limnoria-master-2018-01-28/plugins/Config/plugin.py 
new/Limnoria-master-2018-04-21/plugins/Config/plugin.py
--- old/Limnoria-master-2018-01-28/plugins/Config/plugin.py     2018-01-25 
14:09:19.000000000 +0100
+++ new/Limnoria-master-2018-04-21/plugins/Config/plugin.py     2018-04-14 
22:31:30.000000000 +0200
@@ -65,9 +65,15 @@
 
 def getCapability(name):
     capability = 'owner' # Default to requiring the owner capability.
+    if not name.startswith('supybot') and not name.startswith('users'):
+        name = 'supybot.' + name
     parts = registry.split(name)
+    group = getattr(conf, parts.pop(0))
     while parts:
-        part = parts.pop()
+        part = parts.pop(0)
+        group = group.get(part)
+        if not getattr(group, '_opSettable', True):
+            return 'owner'
         if ircutils.isChannel(part):
             # If a registry value has a channel in it, it requires a
             # 'channel,op' capability, or so we assume.  We'll see if we're
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Limnoria-master-2018-01-28/plugins/Config/test.py 
new/Limnoria-master-2018-04-21/plugins/Config/test.py
--- old/Limnoria-master-2018-01-28/plugins/Config/test.py       2018-01-25 
14:09:19.000000000 +0100
+++ new/Limnoria-master-2018-04-21/plugins/Config/test.py       2018-04-14 
22:31:30.000000000 +0200
@@ -28,13 +28,23 @@
 # POSSIBILITY OF SUCH DAMAGE.
 ###
 
-from supybot.test import *
+import random
 
+from supybot.test import *
 import supybot.conf as conf
 
+_letters = 'abcdefghijklmnopqrstuvwxyz'
+def random_string():
+    return ''.join(random.choice(_letters) for _ in range(16))
+
 class ConfigTestCase(ChannelPluginTestCase):
     # We add utilities so there's something in supybot.plugins.
-    plugins = ('Config', 'Utilities')
+    plugins = ('Config', 'User', 'Utilities')
+
+    prefix1 = '[email protected]'
+    prefix2 = '[email protected]'
+    prefix3 = '[email protected]__no_testcap__'
+
     def testGet(self):
         self.assertNotRegexp('config get supybot.reply', r'registry\.Group')
         self.assertResponse('config supybot.protocols.irc.throttleTime', '0.0')
@@ -110,5 +120,52 @@
             conf.supybot.commands.allowShell.setValue(True)
             conf.supybot.directories.plugins.setValue(old_plugins_dirs)
 
+    def testOpEditable(self):
+        var_name = 'testOpEditable' + random_string()
+        conf.registerChannelValue(conf.supybot.plugins.Config, var_name,
+                registry.Integer(0, 'help'))
+        self.assertNotError('register bar passwd', frm=self.prefix3,
+                private=True)
+        self.assertRegexp('whoami', 'bar', frm=self.prefix3)
+        ircdb.users.getUser('bar').addCapability(self.channel + ',op')
+
+        self.assertRegexp('config plugins.Config.%s 1' % var_name,
+                '^Completely: Error: ',
+                frm=self.prefix3)
+        self.assertResponse('config plugins.Config.%s' % var_name,
+                'Global: 0; #test: 0')
+
+        self.assertNotRegexp('config channel plugins.Config.%s 1' % var_name,
+                '^Completely: Error: ',
+                frm=self.prefix3)
+        self.assertResponse('config plugins.Config.%s' % var_name,
+                'Global: 0; #test: 1')
+
+    def testOpNonEditable(self):
+        var_name = 'testOpNonEditable' + random_string()
+        conf.registerChannelValue(conf.supybot.plugins.Config, var_name,
+                registry.Integer(0, 'help'), opSettable=False)
+        self.assertNotError('register bar passwd', frm=self.prefix3,
+                private=True)
+        self.assertRegexp('whoami', 'bar', frm=self.prefix3)
+        ircdb.users.getUser('bar').addCapability(self.channel + ',op')
+
+        self.assertRegexp('config plugins.Config.%s 1' % var_name,
+                '^Completely: Error: ',
+                frm=self.prefix3)
+        self.assertResponse('config plugins.Config.%s' % var_name,
+                'Global: 0; #test: 0')
+
+        self.assertRegexp('config channel plugins.Config.%s 1' % var_name,
+                '^Completely: Error: ',
+                frm=self.prefix3)
+        self.assertResponse('config plugins.Config.%s' % var_name,
+                'Global: 0; #test: 0')
+
+        self.assertNotRegexp('config channel plugins.Config.%s 1' % var_name,
+                '^Completely: Error: ')
+        self.assertResponse('config plugins.Config.%s' % var_name,
+                'Global: 0; #test: 1')
+
 # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Limnoria-master-2018-01-28/plugins/Games/test.py 
new/Limnoria-master-2018-04-21/plugins/Games/test.py
--- old/Limnoria-master-2018-01-28/plugins/Games/test.py        2018-01-25 
14:09:19.000000000 +0100
+++ new/Limnoria-master-2018-04-21/plugins/Games/test.py        2018-04-14 
22:31:30.000000000 +0200
@@ -36,7 +36,7 @@
         self.irc.feedMsg(ircmsgs.op(self.channel, self.irc.nick))
         sawKick = False
         for i in range(100):
-            m = self.getMsg('roulette', frm='someoneElse')
+            m = self.getMsg('roulette', frm='someoneElse!foo@bar')
             if m.command == 'PRIVMSG':
                 self.failUnless(self._nonKickRe.search(m.args[1]),
                                 'Got a msg without bang|click|spin: %r' % m)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Limnoria-master-2018-01-28/plugins/RSS/plugin.py 
new/Limnoria-master-2018-04-21/plugins/RSS/plugin.py
--- old/Limnoria-master-2018-01-28/plugins/RSS/plugin.py        2018-01-25 
14:09:19.000000000 +0100
+++ new/Limnoria-master-2018-04-21/plugins/RSS/plugin.py        2018-04-14 
22:31:30.000000000 +0200
@@ -403,12 +403,15 @@
             all = __builtins__['all']
             any = __builtins__['any']
 
+        title = getattr(entry, 'title', '')
+        description = getattr(entry, 'description', '')
+
         if whitelist:
-            if all(kw not in entry.title and kw not in entry.description
+            if all(kw not in title and kw not in description
                    for kw in whitelist):
                 return False
         if blacklist:
-            if any(kw in entry.title or kw in entry.description
+            if any(kw in title or kw in description
                    for kw in blacklist):
                 return False
         return True
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/Limnoria-master-2018-01-28/plugins/Services/plugin.py 
new/Limnoria-master-2018-04-21/plugins/Services/plugin.py
--- old/Limnoria-master-2018-01-28/plugins/Services/plugin.py   2018-01-25 
14:09:19.000000000 +0100
+++ new/Limnoria-master-2018-04-21/plugins/Services/plugin.py   2018-04-14 
22:31:30.000000000 +0200
@@ -239,7 +239,7 @@
             elif chanserv and ircutils.strEqual(msg.nick, chanserv):
                 self.doChanservNotice(irc, msg)
 
-    _chanRe = re.compile('\x02(.*?)\x02')
+    _chanRe = re.compile('\x02(#.*?)\x02')
     def doChanservNotice(self, irc, msg):
         if self.disabled(irc):
             return
@@ -250,9 +250,13 @@
         on = 'on %s' % irc.network
         if m is not None:
             channel = m.group(1)
-        if 'all bans' in s or 'unbanned from' in s:
-            # All bans removed (freenode)
-            # You have been unbanned from (oftc)
+        if 'all bans' in s or 'unbanned from' in s or \
+                ('unbanned %s' % irc.nick.lower()) in \
+                ircutils.stripFormatting(s):
+            # All bans removed (old freenode?)
+            # You have been unbanned from (oftc, anope)
+            # "Unbanned \x02someuser\x02 from \x02#channel\x02 (\x02N\x02
+            # ban(s) removed)" (atheme 7.x)
             irc.sendMsg(networkGroup.channels.join(channel))
         elif 'isn\'t registered' in s:
             self.log.warning('Received "%s isn\'t registered" from ChanServ 
%s',
@@ -345,7 +349,8 @@
                              'NickServ %s.  Check email at %s and send the '
                              'auth command to NickServ.', on, email)
         else:
-            self.log.info('Received notice from NickServ %s: %q.', on, s)
+            self.log.info('Received notice from NickServ %s: %q.', on,
+                          ircutils.stripFormatting(msg.args[1]))
 
     def checkPrivileges(self, irc, channel):
         if self.disabled(irc):
@@ -404,7 +409,7 @@
         chanserv = self.registryValue('ChanServ')
         if chanserv:
             msg = ircmsgs.privmsg(chanserv,
-                                  ' '.join([command, channel, irc.nick]))
+                                  ' '.join([command, channel]))
             irc.sendMsg(msg)
         else:
             if log:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Limnoria-master-2018-01-28/plugins/Web/config.py 
new/Limnoria-master-2018-04-21/plugins/Web/config.py
--- old/Limnoria-master-2018-01-28/plugins/Web/config.py        2018-01-25 
14:09:19.000000000 +0100
+++ new/Limnoria-master-2018-04-21/plugins/Web/config.py        2018-04-14 
22:31:30.000000000 +0200
@@ -55,10 +55,16 @@
 conf.registerChannelValue(Web, 'snarferShowDomain',
     registry.Boolean(True, _("""Determines whether domain names should be
     displayed by the title snarfer.""")))
+conf.registerChannelValue(Web, 'snarfMultipleUrls',
+    registry.Boolean(False, _("""Determines whether the title snarfer will
+    query all URLs in a message, or only the first one.""")))
 conf.registerChannelValue(Web, 'snarferShowTargetDomain',
     registry.Boolean(False, _("""Determines whether the domain name displayed
     by the snarfer will be the original one (posted on IRC) or the target one
     (got after following redirects, if any).""")))
+conf.registerChannelValue(Web, 'snarferPrefix',
+    registry.String(_('Title: '), _("""Determines the string used at before
+    a web page's title.""")))
 conf.registerChannelValue(Web, 'nonSnarfingRegexp',
     registry.Regexp(None, _("""Determines what URLs matching the given regexp
     will not be snarfed.  Give the empty string if you have no URLs that you'd
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Limnoria-master-2018-01-28/plugins/Web/plugin.py 
new/Limnoria-master-2018-04-21/plugins/Web/plugin.py
--- old/Limnoria-master-2018-01-28/plugins/Web/plugin.py        2018-01-25 
14:09:19.000000000 +0100
+++ new/Limnoria-master-2018-04-21/plugins/Web/plugin.py        2018-04-14 
22:31:30.000000000 +0200
@@ -30,6 +30,7 @@
 
 import re
 import sys
+import string
 import socket
 
 import supybot.conf as conf
@@ -198,10 +199,14 @@
                 domain = utils.web.getDomain(target
                         if self.registryValue('snarferShowTargetDomain', 
channel)
                         else url)
-                s = format(_('Title: %s'), title)
+                prefix = self.registryValue('snarferPrefix', channel)
+                s = prefix + title
                 if self.registryValue('snarferShowDomain', channel):
                     s += format(_(' (at %s)'), domain)
                 irc.reply(s, prefixNick=False)
+        if self.registryValue('snarfMultipleUrls', channel):
+            # FIXME: hack
+            msg.tag('repliedTo', False)
     titleSnarfer = urlSnarfer(titleSnarfer)
     titleSnarfer.__doc__ = utils.web._httpUrlRe
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Limnoria-master-2018-01-28/plugins/Web/test.py 
new/Limnoria-master-2018-04-21/plugins/Web/test.py
--- old/Limnoria-master-2018-01-28/plugins/Web/test.py  2018-01-25 
14:09:19.000000000 +0100
+++ new/Limnoria-master-2018-04-21/plugins/Web/test.py  2018-04-14 
22:31:30.000000000 +0200
@@ -83,16 +83,32 @@
             finally:
                 conf.supybot.plugins.Web.titleSnarfer.setValue(False)
 
+        def testMultipleTitleSnarfer(self):
+            try:
+                conf.supybot.plugins.Web.titleSnarfer.setValue(True)
+                conf.supybot.plugins.Web.snarfMultipleUrls.setValue(True)
+                self.feedMsg(
+                        'https://microsoft.com/ https://google.com/')
+                m1 = self.getMsg(' ')
+                m2 = self.getMsg(' ')
+                self.assertTrue(('Microsoft' in m1.args[1]) ^
+                        ('Microsoft' in m2.args[1]))
+                self.assertTrue(('Google' in m1.args[1]) ^
+                        ('Google' in m2.args[1]))
+            finally:
+                conf.supybot.plugins.Web.titleSnarfer.setValue(False)
+                conf.supybot.plugins.Web.snarfMultipleUrls.setValue(False)
+
         def testNonSnarfing(self):
             snarf = conf.supybot.plugins.Web.nonSnarfingRegexp()
             title = conf.supybot.plugins.Web.titleSnarfer()
             try:
-                conf.supybot.plugins.Web.nonSnarfingRegexp.set('m/sf/')
+                conf.supybot.plugins.Web.nonSnarfingRegexp.set('m/fr/')
                 try:
                     conf.supybot.plugins.Web.titleSnarfer.setValue(True)
-                    self.assertSnarfNoResponse('http://sf.net/', 2)
-                    self.assertSnarfRegexp('http://www.sourceforge.net/',
-                                           r'Sourceforge\.net')
+                    self.assertSnarfNoResponse('https://www.google.fr/', 2)
+                    self.assertSnarfRegexp('https://www.google.com/',
+                                           r'Google')
                 finally:
                     conf.supybot.plugins.Web.titleSnarfer.setValue(title)
             finally:
@@ -116,10 +132,10 @@
             conf.supybot.plugins.Web.checkIgnored.setValue(False)
             (oldprefix, self.prefix) = (self.prefix, 'foo!bar@baz')
             try:
-                self.assertSnarfRegexp('https://google.com/', 'Google')
+                self.assertSnarfRegexp('https://google.it/', 'Google')
                 self.assertNotError('admin ignore add %s' % self.prefix)
-                self.assertSnarfRegexp('https://www.google.com/', 'Google')
-                self.assertNoResponse('title http://www.google.com/')
+                self.assertSnarfRegexp('https://www.google.it/', 'Google')
+                self.assertNoResponse('title http://www.google.it/')
             finally:
                 conf.supybot.plugins.Web.titleSnarfer.setValue(False)
                 conf.supybot.plugins.Web.checkIgnored.setValue(True)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Limnoria-master-2018-01-28/scripts/supybot-wizard 
new/Limnoria-master-2018-04-21/scripts/supybot-wizard
--- old/Limnoria-master-2018-01-28/scripts/supybot-wizard       2018-01-25 
14:09:19.000000000 +0100
+++ new/Limnoria-master-2018-04-21/scripts/supybot-wizard       2018-04-14 
22:31:30.000000000 +0200
@@ -184,6 +184,14 @@
                            'run directly in the HOME directory. '
                            'You should not do this unless you want it to '
                            'create multiple files in your HOME directory.')
+    parser.add_option('', '--allow-bin', action='store_true',
+                      dest='allowBin',
+                      help='Determines whether the wizard will be allowed to '
+                           'run directly in a directory for binaries (eg. '
+                           '/usr/bin, /usr/local/bin, or ~/.local/bin). '
+                           'You should not do this unless you want it to '
+                           'create multiple non-binary files in directory '
+                           'that should only contain binaries.')
     parser.add_option('', '--no-network', action='store_false',
                       dest='network',
                       help='Determines whether the wizard will be allowed to '
@@ -195,6 +203,8 @@
     if os.name == 'posix':
         if (os.getcwd() == os.path.expanduser('~')) and not options.allowHome:
             error('Please, don\'t run this in your HOME directory.')
+        if (os.path.split(os.getcwd())[-1] == 'bin') and not options.allowBin:
+            error('Please, don\'t run this in a "bin" directory.')
     if os.path.isfile(os.path.join('scripts', 'supybot-wizard')) or \
             os.path.isfile(os.path.join('..', 'scripts', 'supybot-wizard')):
         print('')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Limnoria-master-2018-01-28/setup.py 
new/Limnoria-master-2018-04-21/setup.py
--- old/Limnoria-master-2018-01-28/setup.py     2018-01-25 14:09:19.000000000 
+0100
+++ new/Limnoria-master-2018-04-21/setup.py     2018-04-14 22:31:30.000000000 
+0200
@@ -37,7 +37,6 @@
 import datetime
 import tempfile
 import subprocess
-from math import ceil
 
 warnings.filterwarnings('always', category=DeprecationWarning)
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Limnoria-master-2018-01-28/src/callbacks.py 
new/Limnoria-master-2018-04-21/src/callbacks.py
--- old/Limnoria-master-2018-01-28/src/callbacks.py     2018-01-25 
14:09:19.000000000 +0100
+++ new/Limnoria-master-2018-04-21/src/callbacks.py     2018-04-14 
22:31:30.000000000 +0200
@@ -383,36 +383,28 @@
     return ' '.join(command)
 
 def checkCommandCapability(msg, cb, commandName):
+    plugin = cb.name().lower()
     if not isinstance(commandName, minisix.string_types):
+        assert commandName[0] == plugin, ('checkCommandCapability no longer '
+                'accepts command names that do not start with the callback\'s '
+                'name (%s): %s') % (plugin, commandName)
         commandName = '.'.join(commandName)
-    plugin = cb.name().lower()
-    pluginCommand = '%s.%s' % (plugin, commandName)
     def checkCapability(capability):
         assert ircdb.isAntiCapability(capability)
         if ircdb.checkCapability(msg.prefix, capability):
             log.info('Preventing %s from calling %s because of %s.',
-                     msg.prefix, pluginCommand, capability)
+                     msg.prefix, commandName, capability)
             raise RuntimeError(capability)
     try:
-        antiPlugin = ircdb.makeAntiCapability(plugin)
         antiCommand = ircdb.makeAntiCapability(commandName)
-        antiPluginCommand = ircdb.makeAntiCapability(pluginCommand)
-        checkCapability(antiPlugin)
         checkCapability(antiCommand)
-        checkCapability(antiPluginCommand)
-        checkAtEnd = [commandName, pluginCommand]
+        checkAtEnd = [commandName]
         default = conf.supybot.capabilities.default()
         if ircutils.isChannel(msg.args[0]):
             channel = msg.args[0]
             checkCapability(ircdb.makeChannelCapability(channel, antiCommand))
-            checkCapability(ircdb.makeChannelCapability(channel, antiPlugin))
-            checkCapability(ircdb.makeChannelCapability(channel,
-                                                        antiPluginCommand))
-            chanPlugin = ircdb.makeChannelCapability(channel, plugin)
             chanCommand = ircdb.makeChannelCapability(channel, commandName)
-            chanPluginCommand = ircdb.makeChannelCapability(channel,
-                                                            pluginCommand)
-            checkAtEnd += [chanCommand, chanPlugin, chanPluginCommand]
+            checkAtEnd += [chanCommand]
             default &= ircdb.channels.getChannel(channel).defaultAllow
         return not (default or \
                     any(lambda x: ircdb.checkCapability(msg.prefix, x),
@@ -925,9 +917,8 @@
                         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
@@ -950,8 +941,7 @@
                                   stripCtcp=stripCtcp)
                         sendMsg(m)
                         return m
-                    msgs = ircutils.wrap(s, allowedLength,
-                            break_long_words=True)
+                    msgs = ircutils.wrap(s, allowedLength)
                     msgs.reverse()
                     instant = conf.get(conf.supybot.reply.mores.instant,target)
                     while instant > 1 and msgs:
@@ -1302,18 +1292,29 @@
         else:
             self.log.info('%s called on %s by %q.', formatCommand(command),
                     msg.args[0], msg.prefix)
-        # XXX I'm being extra-special-careful here, but we need to refactor
-        #     this.
         try:
-            cap = checkCommandCapability(msg, self, command)
+            if len(command) == 1 or command[0] != self.canonicalName():
+                fullCommandName = [self.canonicalName()] + command
+            else:
+                fullCommandName = command
+            # Let "P" be the plugin and "X Y" the command name. The
+            # fullCommandName is "P X Y"
+
+            # check "Y"
+            cap = checkCommandCapability(msg, self, command[-1])
             if cap:
                 irc.errorNoCapability(cap)
                 return
-            for name in command:
-                cap = checkCommandCapability(msg, self, name)
+
+            # check "P", "P.X", and "P.X.Y"
+            prefix = []
+            for name in fullCommandName:
+                prefix.append(name)
+                cap = checkCommandCapability(msg, self, prefix)
                 if cap:
                     irc.errorNoCapability(cap)
                     return
+
             try:
                 self.callingCommand = command
                 self.callCommand(command, irc, msg, *args, **kwargs)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Limnoria-master-2018-01-28/src/conf.py 
new/Limnoria-master-2018-04-21/src/conf.py
--- old/Limnoria-master-2018-01-28/src/conf.py  2018-01-25 14:09:19.000000000 
+0100
+++ new/Limnoria-master-2018-04-21/src/conf.py  2018-04-14 22:31:30.000000000 
+0200
@@ -86,9 +86,10 @@
     value.channelValue = False
     return group.register(name, value)
 
-def registerChannelValue(group, name, value):
+def registerChannelValue(group, name, value, opSettable=True):
     value._supplyDefault = True
     value.channelValue = True
+    value._opSettable = opSettable
     g = group.register(name, value)
     gname = g._name.lower()
     for name in registry._cache.keys():
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Limnoria-master-2018-01-28/src/ircutils.py 
new/Limnoria-master-2018-04-21/src/ircutils.py
--- old/Limnoria-master-2018-01-28/src/ircutils.py      2018-01-25 
14:09:19.000000000 +0100
+++ new/Limnoria-master-2018-04-21/src/ircutils.py      2018-04-14 
22:31:30.000000000 +0200
@@ -254,9 +254,9 @@
     if not args:
         return []
     modes = args[0]
-    assert modes[0] in '+-', 'Invalid args: %r' % args
     args = list(args[1:])
     ret = []
+    last = '+'
     for c in modes:
         if c in '+-':
             last = c
@@ -596,12 +596,9 @@
         else:
             self.ungetChar(c)
 
-def wrap(s, length, break_on_hyphens = False, break_long_words = False):
+def wrap(s, length, break_on_hyphens = 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:
@@ -819,7 +816,7 @@
         'm': localtime[4], 'min': localtime[4], 'minute': localtime[4],
         's': localtime[5], 'sec': localtime[5], 'second': localtime[5],
         'tz': time.strftime('%Z', localtime),
-        'version': 'Limnoria %s' % version,
+        'version': version,
         })
     if irc:
         vars.update({
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Limnoria-master-2018-01-28/src/utils/str.py 
new/Limnoria-master-2018-04-21/src/utils/str.py
--- old/Limnoria-master-2018-01-28/src/utils/str.py     2018-01-25 
14:09:19.000000000 +0100
+++ new/Limnoria-master-2018-04-21/src/utils/str.py     2018-04-14 
22:31:30.000000000 +0200
@@ -34,6 +34,7 @@
 """
 
 import re
+import sys
 import time
 import string
 import textwrap
@@ -305,6 +306,46 @@
                 return '$' + unbraced
     return _perlVarSubstituteRe.sub(replacer, text)
 
+def splitBytes(word, size):
+    # I'm going to hell for this function
+    for i in range(4): # a character takes at most 4 bytes in UTF-8
+        try:
+            if sys.version_info[0] >= 3:
+                word[size-i:].decode()
+            else:
+                word[size-i:].encode('utf8')
+        except UnicodeDecodeError:
+            continue
+        else:
+            return (word[0:size-i], word[size-i:])
+    assert False, (word, size)
+
+def byteTextWrap(text, size, break_on_hyphens=False):
+    """Similar to textwrap.wrap(), but considers the size of strings (in bytes)
+    instead of their length (in characters)."""
+    try:
+        words = textwrap.TextWrapper()._split_chunks(text)
+    except AttributeError: # Python 2
+        words = textwrap.TextWrapper()._split(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(word) > size:
+            (before, after) = splitBytes(word, size)
+            words.append(after)
+            word = before
+        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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Limnoria-master-2018-01-28/src/world.py 
new/Limnoria-master-2018-04-21/src/world.py
--- old/Limnoria-master-2018-01-28/src/world.py 2018-01-25 14:09:19.000000000 
+0100
+++ new/Limnoria-master-2018-04-21/src/world.py 2018-04-14 22:31:30.000000000 
+0200
@@ -99,6 +99,7 @@
 ircs = [] # A list of all the IRCs.
 
 def getIrc(network):
+    """Returns Irc object of the given network. <network> is string and not 
case-sensitive."""
     network = network.lower()
     for irc in ircs:
         if irc.network.lower() == network:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Limnoria-master-2018-01-28/test/test_ircutils.py 
new/Limnoria-master-2018-04-21/test/test_ircutils.py
--- old/Limnoria-master-2018-01-28/test/test_ircutils.py        2018-01-25 
14:09:19.000000000 +0100
+++ new/Limnoria-master-2018-04-21/test/test_ircutils.py        2018-04-14 
22:31:30.000000000 +0200
@@ -168,6 +168,66 @@
         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)
+
+        if sys.version_info[0] < 3:
+            uchr = unichr
+            u = lambda s: s.decode('utf8')
+        else:
+            uchr = chr
+            u = lambda x: x
+        s = (u('').join([uchr(0x1f527), uchr(0x1f527), uchr(0x1f527), u(' ')]) 
* 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)
+
+        s = ('foobarbazqux ' * 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 = ('foobarbazqux' * 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 = uchr(233)*500
+        r = ircutils.wrap(s, 500)
+        self.assertTrue(max(map(pred, r)) <= 500)
+        r = ircutils.wrap(s, 139)
+        self.assertTrue(max(map(pred, r)) <= 139)
+
+
     def testSafeArgument(self):
         s = 'I have been running for 9 seconds'
         bolds = ircutils.bold(s)


Reply via email to