Skip to content

Commit

Permalink
Allow optional shlex arg parsing for command plugins
Browse files Browse the repository at this point in the history
  • Loading branch information
shaunduncan committed Dec 20, 2014
1 parent e3993f8 commit f2ffd7d
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 13 deletions.
6 changes: 6 additions & 0 deletions README.rst
Expand Up @@ -84,6 +84,12 @@ basic helga settings, as outlined below:
and 'PASSWORD' if the IRC server requires authentication.
- ``LOG_LEVEL``: String for the default logging level (default: 'DEBUG')
- ``LOG_FILE``: If set, a string indicating the log file for python logs
- ``COMMAND_ARGS_SHLEX``: Control the behavior of argument parsing for command plugins
By default this is a naive str.split(' '), however a plugin may need this behavior to be a bit more
robust. By setting this value to True, shlex.split() will be used instead so that commands
like ``helga foo bar "baz qux"`` will yield an argument list like ['bar', 'baz qux'] instead
of ['bar', '"baz', 'qux"']. Shlex splits will be the default and only supported behavior
in a future version.
- ``CHANNEL_LOGGING``: If True, enable conversation logging on all channels (default: False)
- ``CHANNEL_LOGGING_DIR``: If using channel logs, the directory to which channel logs should
logs should be written. A new directory will be created for each channel in which the
Expand Down
25 changes: 23 additions & 2 deletions helga/plugins/core.py
Expand Up @@ -2,15 +2,17 @@
import pkg_resources
import random
import re
import shlex
import sys
import warnings

from collections import defaultdict
from itertools import ifilter, imap

import smokesignal

from helga import log, settings
from helga.util.encodings import to_unicode
from helga.util.encodings import from_unicode, to_unicode


logger = log.getLogger(__name__)
Expand Down Expand Up @@ -53,6 +55,10 @@
PRIORITY_HIGH = 75


if not settings.COMMAND_ARGS_SHLEX:
warnings.warn(u'Command arg parsing will default to shlex in a future version', FutureWarning)


def random_ack():
return random.choice(ACKS)

Expand Down Expand Up @@ -410,7 +416,22 @@ def parse(self, botnick, message):
# FIXME: Log here?
return u'', []

return cmd, filter(bool, argstr.strip().split(' '))
return cmd, filter(bool, self._parse_argstr(argstr))

def _parse_argstr(self, argstr):
"""
Parse an argument string for this command. If COMMAND_ARGS_SHLEX
is set to False, then naive whitespace splitting is performed on the argument
string. If not, a more robust shlex.split() is performed. For example, given
the message 'helga foo bar "baz qux"', the former would produce arguments
['bar', '"baz', 'qux"'] while the latter would produce ['bar', 'baz qux']
"""
if settings.COMMAND_ARGS_SHLEX:
argv = shlex.split(from_unicode(argstr.strip()))
else:
argv = argstr.strip().split(' ')

return map(to_unicode, argv)

def run(self, client, channel, nick, message, command, args):
"""
Expand Down
9 changes: 9 additions & 0 deletions helga/settings.py
Expand Up @@ -9,6 +9,15 @@

LOG_LEVEL = 'DEBUG'

# Control the behavior of argument parsing for command plugins
# By default this is a naive str.split(' '), however a plugin
# may need this behavior to be a bit more robust. By setting this value
# to True, shlex.split() will be used instead so that commands
# like `helga foo bar "baz qux"` will yield an argument list like
# ['bar', 'baz qux'] instead of ['bar', '"baz', 'qux"']. Shlex splits
# will be the default and only supported behavior in a future version.
COMMAND_ARGS_SHLEX = False

# Channel logging. Set CHANNEL_LOGGING_DIR
CHANNEL_LOGGING = False
CHANNEL_LOGGING_DIR = '.logs'
Expand Down
43 changes: 32 additions & 11 deletions helga/tests/plugins/test_core.py
Expand Up @@ -3,7 +3,8 @@
Tests for helga.plugins
"""
from collections import defaultdict
from unittest import TestCase

import pytest

from mock import Mock, patch
from pretend import stub
Expand All @@ -20,9 +21,9 @@
registry)


class RegistryTestCase(TestCase):
class TestRegistry(object):

def setUp(self):
def setup(self):
registry.plugins = {}
registry.enabled_plugins = defaultdict(set)

Expand Down Expand Up @@ -158,7 +159,8 @@ def test_registry_is_singleton(self):

def test_register_raises_typeerror(self):
for plugin in self.invalid_plugins:
self.assertRaises(TypeError, registry.register, 'invalid', plugin)
with pytest.raises(TypeError):
registry.register('invalid', plugin)

def test_register_valid_plugins(self):
for plugin in self.valid_plugins:
Expand All @@ -167,7 +169,8 @@ def test_register_valid_plugins(self):

def test_register_plugin_handles_unicode(self):
for plugin in self.invalid_plugins:
self.assertRaises(TypeError, registry.register, 'invalid', plugin)
with pytest.raises(TypeError):
registry.register('invalid', plugin)

for plugin in self.valid_plugins:
name = u'{0}-{1}'.format(self.snowman, repr(plugin))
Expand Down Expand Up @@ -295,9 +298,9 @@ def test_preprocess(self):
assert plugins[1].preprocess.called


class PluginTestCase(TestCase):
class TestPlugin(object):

def setUp(self):
def setup(self):
self.plugin = Plugin()
self.client = Mock(nickname='helga')

Expand Down Expand Up @@ -335,9 +338,9 @@ def test_base_plugin_process_calls_run(self):
run.assert_called_with('foo', 'bar', 'baz', 'qux')


class CommandTestCase(TestCase):
class TestCommand(object):

def setUp(self):
def setup(self):
self.cmd = Command('foo', aliases=('bar', 'baz'), help='foo cmd')
self.client = Mock(nickname='helga')

Expand Down Expand Up @@ -392,6 +395,24 @@ def test_parse_handles_unicode(self):
self.cmd.command = snowman
assert (snowman, [disapproval]) == self.cmd.parse('helga', cmd)

@pytest.mark.parametrize('argstr,expected', [
('foo bar "baz qux"', ['foo', 'bar', 'baz qux']),
(u'foo "bar ☃"', ['foo', u'bar ☃']),
])
def test_parse_argstr_shlex(self, argstr, expected):
with patch('helga.plugins.core.settings') as settings:
settings.COMMAND_ARGS_SHLEX = True
assert self.cmd._parse_argstr(argstr) == expected

@pytest.mark.parametrize('argstr,expected', [
('foo bar "baz qux"', ['foo', 'bar', '"baz', 'qux"']),
(u'foo "bar ☃"', ['foo', '"bar', u'☃"']),
])
def test_parse_argstr_whitespace(self, argstr, expected):
with patch('helga.plugins.core.settings') as settings:
settings.COMMAND_ARGS_SHLEX = False
assert self.cmd._parse_argstr(argstr) == expected

def test_process_for_different_command_returns_none(self):
assert self.cmd.process(self.client, '#bots', 'me', 'helga qux') is None

Expand Down Expand Up @@ -429,9 +450,9 @@ def foo(client, chan, nick, msg, cmd, args):
assert 'bar' == foo._plugins[0](self.client, '#bots', 'me', 'helga baz')


class MatchTestCase(TestCase):
class TestMatch(object):

def setUp(self):
def setup(self):
self.match = Match('foo')
self.client = Mock()

Expand Down

0 comments on commit f2ffd7d

Please sign in to comment.