diff --git a/IPython/frontend/consoleapp.py b/IPython/frontend/consoleapp.py index e932a4f..2776eef 100644 --- a/IPython/frontend/consoleapp.py +++ b/IPython/frontend/consoleapp.py @@ -33,7 +33,7 @@ import uuid from IPython.config.application import boolean_flag from IPython.config.configurable import Configurable from IPython.core.profiledir import ProfileDir -from IPython.lib.kernel import tunnel_to_kernel, find_connection_file +from IPython.lib.kernel import tunnel_to_kernel, find_connection_file, swallow_argv from IPython.zmq.blockingkernelmanager import BlockingKernelManager from IPython.utils.path import filefind from IPython.utils.py3compat import str_to_bytes @@ -136,6 +136,9 @@ class IPythonConsoleApp(Configurable): kernel_manager_class = BlockingKernelManager kernel_argv = List(Unicode) + # frontend flags&aliases to be stripped when building kernel_argv + frontend_flags = Any(app_flags) + frontend_aliases = Any(app_aliases) pure = CBool(False, config=True, help="Use a pure Python kernel instead of an IPython kernel.") @@ -182,45 +185,13 @@ class IPythonConsoleApp(Configurable): ) - def parse_command_line(self, argv=None): - #super(PythonBaseConsoleApp, self).parse_command_line(argv) - # make this stuff after this a function, in case the super stuff goes - # away. Also, Min notes that this functionality should be moved to a - # generic library of kernel stuff - self.swallow_args(app_aliases,app_flags,argv=argv) - - def swallow_args(self, aliases,flags, argv=None): + def build_kernel_argv(self, argv=None): + """build argv to be passed to kernel subprocess""" if argv is None: argv = sys.argv[1:] - self.kernel_argv = list(argv) # copy + self.kernel_argv = swallow_argv(argv, self.frontend_aliases, self.frontend_flags) # kernel should inherit default config file from frontend self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name) - # Scrub frontend-specific flags - swallow_next = False - was_flag = False - for a in argv: - if swallow_next: - swallow_next = False - # last arg was an alias, remove the next one - # *unless* the last alias has a no-arg flag version, in which - # case, don't swallow the next arg if it's also a flag: - if not (was_flag and a.startswith('-')): - self.kernel_argv.remove(a) - continue - if a.startswith('-'): - split = a.lstrip('-').split('=') - alias = split[0] - if alias in aliases: - self.kernel_argv.remove(a) - if len(split) == 1: - # alias passed with arg via space - swallow_next = True - # could have been a flag that matches an alias, e.g. `existing` - # in which case, we might not swallow the next arg - was_flag = alias in flags - elif alias in flags: - # strip flag, but don't swallow next, as flags don't take args - self.kernel_argv.remove(a) def init_connection_file(self): """find the connection file, and load the info if found. diff --git a/IPython/frontend/html/notebook/notebookapp.py b/IPython/frontend/html/notebook/notebookapp.py index d2bdcf6..e7a00c0 100644 --- a/IPython/frontend/html/notebook/notebookapp.py +++ b/IPython/frontend/html/notebook/notebookapp.py @@ -55,6 +55,7 @@ from .notebookmanager import NotebookManager from IPython.config.application import catch_config_error from IPython.core.application import BaseIPythonApplication from IPython.core.profiledir import ProfileDir +from IPython.lib.kernel import swallow_argv from IPython.zmq.session import Session, default_secure from IPython.zmq.zmqshell import ZMQInteractiveShell from IPython.zmq.ipkernel import ( @@ -283,27 +284,10 @@ class NotebookApp(BaseIPythonApplication): if argv is None: argv = sys.argv[1:] - self.kernel_argv = list(argv) # copy + # Scrub frontend-specific flags + self.kernel_argv = swallow_argv(argv, notebook_aliases, notebook_flags) # Kernel should inherit default config file from frontend self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name) - # Scrub frontend-specific flags - for a in argv: - if a.startswith('-') and a.lstrip('-') in notebook_flags: - self.kernel_argv.remove(a) - swallow_next = False - for a in argv: - if swallow_next: - self.kernel_argv.remove(a) - swallow_next = False - continue - if a.startswith('-'): - split = a.lstrip('-').split('=') - alias = split[0] - if alias in notebook_aliases: - self.kernel_argv.remove(a) - if len(split) == 1: - # alias passed with arg via space - swallow_next = True def init_configurables(self): # Don't let Qt or ZMQ swallow KeyboardInterupts. diff --git a/IPython/frontend/qt/console/qtconsoleapp.py b/IPython/frontend/qt/console/qtconsoleapp.py index 4c6c9f0..7b3e608 100644 --- a/IPython/frontend/qt/console/qtconsoleapp.py +++ b/IPython/frontend/qt/console/qtconsoleapp.py @@ -144,6 +144,8 @@ class IPythonQtConsoleApp(BaseIPythonApplication, IPythonConsoleApp): classes = [IPKernelApp, IPythonWidget, ZMQInteractiveShell, ProfileDir, Session] flags = Dict(flags) aliases = Dict(aliases) + frontend_flags = Any(qt_flags) + frontend_aliases = Any(qt_aliases) kernel_manager_class = QtKernelManager stylesheet = Unicode('', config=True, @@ -169,7 +171,7 @@ class IPythonQtConsoleApp(BaseIPythonApplication, IPythonConsoleApp): def parse_command_line(self, argv=None): super(IPythonQtConsoleApp, self).parse_command_line(argv) - self.swallow_args(qt_aliases,qt_flags,argv=argv) + self.build_kernel_argv(argv) def new_frontend_master(self): diff --git a/IPython/frontend/terminal/console/app.py b/IPython/frontend/terminal/console/app.py index 020ee95..b836abe 100644 --- a/IPython/frontend/terminal/console/app.py +++ b/IPython/frontend/terminal/console/app.py @@ -101,11 +101,14 @@ class ZMQTerminalIPythonApp(TerminalIPythonApp, IPythonConsoleApp): classes = List([IPKernelApp, ZMQTerminalInteractiveShell, Session]) flags = Dict(flags) aliases = Dict(aliases) + frontend_aliases = Any(frontend_aliases) + frontend_flags = Any(frontend_flags) + subcommands = Dict() def parse_command_line(self, argv=None): super(ZMQTerminalIPythonApp, self).parse_command_line(argv) - self.swallow_args(frontend_aliases,frontend_flags,argv=argv) + self.build_kernel_argv(argv) def init_shell(self): IPythonConsoleApp.initialize(self) diff --git a/IPython/lib/kernel.py b/IPython/lib/kernel.py index df9ec6b..345ecee 100644 --- a/IPython/lib/kernel.py +++ b/IPython/lib/kernel.py @@ -253,3 +253,63 @@ def tunnel_to_kernel(connection_info, sshserver, sshkey=None): return tuple(lports) +def swallow_argv(argv, aliases=None, flags=None): + """strip frontend-specific aliases and flags from an argument list + + For use primarily in frontend apps that want to pass a subset of command-line + arguments through to a subprocess, where frontend-specific flags and aliases + should be removed from the list. + + Parameters + ---------- + + argv : list(str) + The starting argv, to be filtered + aliases : container of aliases (dict, list, set, etc.) + The frontend-specific aliases to be removed + flags : container of flags (dict, list, set, etc.) + The frontend-specific flags to be removed + + Returns + ------- + + argv : list(str) + The argv list, excluding flags and aliases that have been stripped + """ + + if aliases is None: + aliases = set() + if flags is None: + flags = set() + + stripped = list(argv) # copy + + swallow_next = False + was_flag = False + for a in argv: + if swallow_next: + swallow_next = False + # last arg was an alias, remove the next one + # *unless* the last alias has a no-arg flag version, in which + # case, don't swallow the next arg if it's also a flag: + if not (was_flag and a.startswith('-')): + stripped.remove(a) + continue + if a.startswith('-'): + split = a.lstrip('-').split('=') + alias = split[0] + if alias in aliases: + stripped.remove(a) + if len(split) == 1: + # alias passed with arg via space + swallow_next = True + # could have been a flag that matches an alias, e.g. `existing` + # in which case, we might not swallow the next arg + was_flag = alias in flags + elif alias in flags and len(split) == 1: + # strip flag, but don't swallow next, as flags don't take args + stripped.remove(a) + + # return shortened list + return stripped + diff --git a/IPython/lib/tests/test_kernel.py b/IPython/lib/tests/test_kernel.py new file mode 100644 index 0000000..87a2a1b --- /dev/null +++ b/IPython/lib/tests/test_kernel.py @@ -0,0 +1,63 @@ +"""Tests for kernel utility functions + +Authors +------- +* MinRK +""" +#----------------------------------------------------------------------------- +# Copyright (c) 2011, the IPython Development Team. +# +# Distributed under the terms of the Modified BSD License. +# +# The full license is in the file COPYING.txt, distributed with this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +# Stdlib imports +from unittest import TestCase + +# Third-party imports +import nose.tools as nt + +# Our own imports +from IPython.testing import decorators as dec +from IPython.lib import kernel + +#----------------------------------------------------------------------------- +# Classes and functions +#----------------------------------------------------------------------------- + +@dec.parametric +def test_swallow_argv(): + tests = [ + # expected , argv , aliases, flags + (['-a', '5'], ['-a', '5'], None, None), + (['5'], ['-a', '5'], None, ['a']), + ([], ['-a', '5'], ['a'], None), + ([], ['-a', '5'], ['a'], ['a']), + ([], ['--foo'], None, ['foo']), + (['--foo'], ['--foo'], ['foobar'], []), + ([], ['--foo', '5'], ['foo'], []), + ([], ['--foo=5'], ['foo'], []), + (['--foo=5'], ['--foo=5'], [], ['foo']), + (['5'], ['--foo', '5'], [], ['foo']), + (['bar'], ['--foo', '5', 'bar'], ['foo'], ['foo']), + (['bar'], ['--foo=5', 'bar'], ['foo'], ['foo']), + (['5','bar'], ['--foo', '5', 'bar'], None, ['foo']), + (['bar'], ['--foo', '5', 'bar'], ['foo'], None), + (['bar'], ['--foo=5', 'bar'], ['foo'], None), + ] + for expected, argv, aliases, flags in tests: + stripped = kernel.swallow_argv(argv, aliases=aliases, flags=flags) + message = '\n'.join(['', + "argv: %r" % argv, + "aliases: %r" % aliases, + "flags : %r" % flags, + "expected : %r" % expected, + "returned : %r" % stripped, + ]) + yield nt.assert_equal(expected, stripped, message) +