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