##// END OF EJS Templates
BUG: when given unicode inputs, arg_split should return unicode outputs. Always use utf-8 to encode the string instead of relying on sys.stdin.encoding, which may not be able to accept the full range of Unicode characters. When given unicode strings, arg_split is probably not receiving input from a terminal.
Robert Kern -
Show More
@@ -1,140 +1,146 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 Utilities for working with external processes.
3 Utilities for working with external processes.
4 """
4 """
5
5
6 #-----------------------------------------------------------------------------
6 #-----------------------------------------------------------------------------
7 # Copyright (C) 2008-2009 The IPython Development Team
7 # Copyright (C) 2008-2009 The IPython Development Team
8 #
8 #
9 # Distributed under the terms of the BSD License. The full license is in
9 # Distributed under the terms of the BSD License. The full license is in
10 # the file COPYING, distributed as part of this software.
10 # the file COPYING, distributed as part of this software.
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 from __future__ import print_function
16 from __future__ import print_function
17
17
18 # Stdlib
18 # Stdlib
19 import os
19 import os
20 import sys
20 import sys
21 import shlex
21 import shlex
22
22
23 # Our own
23 # Our own
24 if sys.platform == 'win32':
24 if sys.platform == 'win32':
25 from ._process_win32 import _find_cmd, system, getoutput, AvoidUNCPath
25 from ._process_win32 import _find_cmd, system, getoutput, AvoidUNCPath
26 else:
26 else:
27 from ._process_posix import _find_cmd, system, getoutput
27 from ._process_posix import _find_cmd, system, getoutput
28
28
29 from ._process_common import getoutputerror
29 from ._process_common import getoutputerror
30
30
31 #-----------------------------------------------------------------------------
31 #-----------------------------------------------------------------------------
32 # Code
32 # Code
33 #-----------------------------------------------------------------------------
33 #-----------------------------------------------------------------------------
34
34
35
35
36 class FindCmdError(Exception):
36 class FindCmdError(Exception):
37 pass
37 pass
38
38
39
39
40 def find_cmd(cmd):
40 def find_cmd(cmd):
41 """Find absolute path to executable cmd in a cross platform manner.
41 """Find absolute path to executable cmd in a cross platform manner.
42
42
43 This function tries to determine the full path to a command line program
43 This function tries to determine the full path to a command line program
44 using `which` on Unix/Linux/OS X and `win32api` on Windows. Most of the
44 using `which` on Unix/Linux/OS X and `win32api` on Windows. Most of the
45 time it will use the version that is first on the users `PATH`. If
45 time it will use the version that is first on the users `PATH`. If
46 cmd is `python` return `sys.executable`.
46 cmd is `python` return `sys.executable`.
47
47
48 Warning, don't use this to find IPython command line programs as there
48 Warning, don't use this to find IPython command line programs as there
49 is a risk you will find the wrong one. Instead find those using the
49 is a risk you will find the wrong one. Instead find those using the
50 following code and looking for the application itself::
50 following code and looking for the application itself::
51
51
52 from IPython.utils.path import get_ipython_module_path
52 from IPython.utils.path import get_ipython_module_path
53 from IPython.utils.process import pycmd2argv
53 from IPython.utils.process import pycmd2argv
54 argv = pycmd2argv(get_ipython_module_path('IPython.frontend.terminal.ipapp'))
54 argv = pycmd2argv(get_ipython_module_path('IPython.frontend.terminal.ipapp'))
55
55
56 Parameters
56 Parameters
57 ----------
57 ----------
58 cmd : str
58 cmd : str
59 The command line program to look for.
59 The command line program to look for.
60 """
60 """
61 if cmd == 'python':
61 if cmd == 'python':
62 return os.path.abspath(sys.executable)
62 return os.path.abspath(sys.executable)
63 try:
63 try:
64 path = _find_cmd(cmd).rstrip()
64 path = _find_cmd(cmd).rstrip()
65 except OSError:
65 except OSError:
66 raise FindCmdError('command could not be found: %s' % cmd)
66 raise FindCmdError('command could not be found: %s' % cmd)
67 # which returns empty if not found
67 # which returns empty if not found
68 if path == '':
68 if path == '':
69 raise FindCmdError('command could not be found: %s' % cmd)
69 raise FindCmdError('command could not be found: %s' % cmd)
70 return os.path.abspath(path)
70 return os.path.abspath(path)
71
71
72
72
73 def pycmd2argv(cmd):
73 def pycmd2argv(cmd):
74 r"""Take the path of a python command and return a list (argv-style).
74 r"""Take the path of a python command and return a list (argv-style).
75
75
76 This only works on Python based command line programs and will find the
76 This only works on Python based command line programs and will find the
77 location of the ``python`` executable using ``sys.executable`` to make
77 location of the ``python`` executable using ``sys.executable`` to make
78 sure the right version is used.
78 sure the right version is used.
79
79
80 For a given path ``cmd``, this returns [cmd] if cmd's extension is .exe,
80 For a given path ``cmd``, this returns [cmd] if cmd's extension is .exe,
81 .com or .bat, and [, cmd] otherwise.
81 .com or .bat, and [, cmd] otherwise.
82
82
83 Parameters
83 Parameters
84 ----------
84 ----------
85 cmd : string
85 cmd : string
86 The path of the command.
86 The path of the command.
87
87
88 Returns
88 Returns
89 -------
89 -------
90 argv-style list.
90 argv-style list.
91 """
91 """
92 ext = os.path.splitext(cmd)[1]
92 ext = os.path.splitext(cmd)[1]
93 if ext in ['.exe', '.com', '.bat']:
93 if ext in ['.exe', '.com', '.bat']:
94 return [cmd]
94 return [cmd]
95 else:
95 else:
96 if sys.platform == 'win32':
96 if sys.platform == 'win32':
97 # The -u option here turns on unbuffered output, which is required
97 # The -u option here turns on unbuffered output, which is required
98 # on Win32 to prevent wierd conflict and problems with Twisted.
98 # on Win32 to prevent wierd conflict and problems with Twisted.
99 # Also, use sys.executable to make sure we are picking up the
99 # Also, use sys.executable to make sure we are picking up the
100 # right python exe.
100 # right python exe.
101 return [sys.executable, '-u', cmd]
101 return [sys.executable, '-u', cmd]
102 else:
102 else:
103 return [sys.executable, cmd]
103 return [sys.executable, cmd]
104
104
105
105
106 def arg_split(s, posix=False):
106 def arg_split(s, posix=False):
107 """Split a command line's arguments in a shell-like manner.
107 """Split a command line's arguments in a shell-like manner.
108
108
109 This is a modified version of the standard library's shlex.split()
109 This is a modified version of the standard library's shlex.split()
110 function, but with a default of posix=False for splitting, so that quotes
110 function, but with a default of posix=False for splitting, so that quotes
111 in inputs are respected."""
111 in inputs are respected."""
112
112
113 # Unfortunately, python's shlex module is buggy with unicode input:
113 # Unfortunately, python's shlex module is buggy with unicode input:
114 # http://bugs.python.org/issue1170
114 # http://bugs.python.org/issue1170
115 # At least encoding the input when it's unicode seems to help, but there
115 # At least encoding the input when it's unicode seems to help, but there
116 # may be more problems lurking. Apparently this is fixed in python3.
116 # may be more problems lurking. Apparently this is fixed in python3.
117 is_unicode = False
117 if isinstance(s, unicode):
118 if isinstance(s, unicode):
118 s = s.encode(sys.stdin.encoding)
119 is_unicode = True
120 s = s.encode('utf-8')
119 lex = shlex.shlex(s, posix=posix)
121 lex = shlex.shlex(s, posix=posix)
120 lex.whitespace_split = True
122 lex.whitespace_split = True
121 return list(lex)
123 tokens = list(lex)
124 if is_unicode:
125 # Convert the tokens back to unicode.
126 tokens = [x.decode('utf-8') for x in tokens]
127 return tokens
122
128
123
129
124 def abbrev_cwd():
130 def abbrev_cwd():
125 """ Return abbreviated version of cwd, e.g. d:mydir """
131 """ Return abbreviated version of cwd, e.g. d:mydir """
126 cwd = os.getcwd().replace('\\','/')
132 cwd = os.getcwd().replace('\\','/')
127 drivepart = ''
133 drivepart = ''
128 tail = cwd
134 tail = cwd
129 if sys.platform == 'win32':
135 if sys.platform == 'win32':
130 if len(cwd) < 4:
136 if len(cwd) < 4:
131 return cwd
137 return cwd
132 drivepart,tail = os.path.splitdrive(cwd)
138 drivepart,tail = os.path.splitdrive(cwd)
133
139
134
140
135 parts = tail.split('/')
141 parts = tail.split('/')
136 if len(parts) > 2:
142 if len(parts) > 2:
137 tail = '/'.join(parts[-2:])
143 tail = '/'.join(parts[-2:])
138
144
139 return (drivepart + (
145 return (drivepart + (
140 cwd == '/' and '/' or tail))
146 cwd == '/' and '/' or tail))
@@ -1,95 +1,98 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 Tests for platutils.py
3 Tests for platutils.py
4 """
4 """
5
5
6 #-----------------------------------------------------------------------------
6 #-----------------------------------------------------------------------------
7 # Copyright (C) 2008-2009 The IPython Development Team
7 # Copyright (C) 2008-2009 The IPython Development Team
8 #
8 #
9 # Distributed under the terms of the BSD License. The full license is in
9 # Distributed under the terms of the BSD License. The full license is in
10 # the file COPYING, distributed as part of this software.
10 # the file COPYING, distributed as part of this software.
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16
16
17 import sys
17 import sys
18 from unittest import TestCase
18 from unittest import TestCase
19
19
20 import nose.tools as nt
20 import nose.tools as nt
21
21
22 from IPython.utils.process import (find_cmd, FindCmdError, arg_split,
22 from IPython.utils.process import (find_cmd, FindCmdError, arg_split,
23 system, getoutput, getoutputerror)
23 system, getoutput, getoutputerror)
24 from IPython.testing import decorators as dec
24 from IPython.testing import decorators as dec
25 from IPython.testing import tools as tt
25 from IPython.testing import tools as tt
26
26
27 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
28 # Tests
28 # Tests
29 #-----------------------------------------------------------------------------
29 #-----------------------------------------------------------------------------
30
30
31 def test_find_cmd_python():
31 def test_find_cmd_python():
32 """Make sure we find sys.exectable for python."""
32 """Make sure we find sys.exectable for python."""
33 nt.assert_equals(find_cmd('python'), sys.executable)
33 nt.assert_equals(find_cmd('python'), sys.executable)
34
34
35
35
36 @dec.skip_win32
36 @dec.skip_win32
37 def test_find_cmd_ls():
37 def test_find_cmd_ls():
38 """Make sure we can find the full path to ls."""
38 """Make sure we can find the full path to ls."""
39 path = find_cmd('ls')
39 path = find_cmd('ls')
40 nt.assert_true(path.endswith('ls'))
40 nt.assert_true(path.endswith('ls'))
41
41
42
42
43 def has_pywin32():
43 def has_pywin32():
44 try:
44 try:
45 import win32api
45 import win32api
46 except ImportError:
46 except ImportError:
47 return False
47 return False
48 return True
48 return True
49
49
50
50
51 @dec.onlyif(has_pywin32, "This test requires win32api to run")
51 @dec.onlyif(has_pywin32, "This test requires win32api to run")
52 def test_find_cmd_pythonw():
52 def test_find_cmd_pythonw():
53 """Try to find pythonw on Windows."""
53 """Try to find pythonw on Windows."""
54 path = find_cmd('pythonw')
54 path = find_cmd('pythonw')
55 nt.assert_true(path.endswith('pythonw.exe'))
55 nt.assert_true(path.endswith('pythonw.exe'))
56
56
57
57
58 @dec.onlyif(lambda : sys.platform != 'win32' or has_pywin32(),
58 @dec.onlyif(lambda : sys.platform != 'win32' or has_pywin32(),
59 "This test runs on posix or in win32 with win32api installed")
59 "This test runs on posix or in win32 with win32api installed")
60 def test_find_cmd_fail():
60 def test_find_cmd_fail():
61 """Make sure that FindCmdError is raised if we can't find the cmd."""
61 """Make sure that FindCmdError is raised if we can't find the cmd."""
62 nt.assert_raises(FindCmdError,find_cmd,'asdfasdf')
62 nt.assert_raises(FindCmdError,find_cmd,'asdfasdf')
63
63
64
64
65 def test_arg_split():
65 def test_arg_split():
66 """Ensure that argument lines are correctly split like in a shell."""
66 """Ensure that argument lines are correctly split like in a shell."""
67 tests = [['hi', ['hi']],
67 tests = [['hi', ['hi']],
68 [u'hi', [u'hi']],
68 [u'hi', [u'hi']],
69 ['hello there', ['hello', 'there']],
70 [u'h\N{LATIN SMALL LETTER A WITH CARON}llo', [u'h\N{LATIN SMALL LETTER A WITH CARON}llo']],
71 ['something "with quotes"', ['something', '"with quotes"']],
69 ]
72 ]
70 for argstr, argv in tests:
73 for argstr, argv in tests:
71 nt.assert_equal(arg_split(argstr), argv)
74 nt.assert_equal(arg_split(argstr), argv)
72
75
73
76
74 class SubProcessTestCase(TestCase, tt.TempFileMixin):
77 class SubProcessTestCase(TestCase, tt.TempFileMixin):
75 def setUp(self):
78 def setUp(self):
76 """Make a valid python temp file."""
79 """Make a valid python temp file."""
77 lines = ["from __future__ import print_function",
80 lines = ["from __future__ import print_function",
78 "import sys",
81 "import sys",
79 "print('on stdout', end='', file=sys.stdout)",
82 "print('on stdout', end='', file=sys.stdout)",
80 "print('on stderr', end='', file=sys.stderr)",
83 "print('on stderr', end='', file=sys.stderr)",
81 "sys.stdout.flush()",
84 "sys.stdout.flush()",
82 "sys.stderr.flush()"]
85 "sys.stderr.flush()"]
83 self.mktmp('\n'.join(lines))
86 self.mktmp('\n'.join(lines))
84
87
85 def test_system(self):
88 def test_system(self):
86 system('python "%s"' % self.fname)
89 system('python "%s"' % self.fname)
87
90
88 def test_getoutput(self):
91 def test_getoutput(self):
89 out = getoutput('python "%s"' % self.fname)
92 out = getoutput('python "%s"' % self.fname)
90 self.assertEquals(out, 'on stdout')
93 self.assertEquals(out, 'on stdout')
91
94
92 def test_getoutput(self):
95 def test_getoutput(self):
93 out, err = getoutputerror('python "%s"' % self.fname)
96 out, err = getoutputerror('python "%s"' % self.fname)
94 self.assertEquals(out, 'on stdout')
97 self.assertEquals(out, 'on stdout')
95 self.assertEquals(err, 'on stderr')
98 self.assertEquals(err, 'on stderr')
General Comments 0
You need to be logged in to leave comments. Login now